diff options
788 files changed, 23999 insertions, 11360 deletions
diff --git a/Android.bp b/Android.bp index 7d02d7d50f84..ee5db70b3ce1 100644 --- a/Android.bp +++ b/Android.bp @@ -109,6 +109,7 @@ filegroup { ":platform-compat-native-aidl", // AIDL sources from external directories + ":android.hardware.graphics.common-V3-java-source", ":android.hardware.security.keymint-V1-java-source", ":android.hardware.security.secureclock-V1-java-source", ":android.hardware.tv.tuner-V1-java-source", @@ -288,6 +289,7 @@ java_defaults { // TODO: remove when moved to the below package "frameworks/base/packages/ConnectivityT/framework-t/aidl-export", "packages/modules/Connectivity/framework/aidl-export", + "hardware/interfaces/graphics/common/aidl", ], }, dxflags: [ @@ -537,6 +539,7 @@ stubs_defaults { // TODO: remove when moved to the below package "frameworks/base/packages/ConnectivityT/framework-t/aidl-export", "packages/modules/Connectivity/framework/aidl-export", + "hardware/interfaces/graphics/common/aidl", ], }, // These are libs from framework-internal-utils that are required (i.e. being referenced) diff --git a/INPUT_OWNERS b/INPUT_OWNERS new file mode 100644 index 000000000000..6041f637f9c1 --- /dev/null +++ b/INPUT_OWNERS @@ -0,0 +1,3 @@ +michaelwr@google.com +prabirmsp@google.com +svv@google.com diff --git a/apex/OWNERS b/apex/OWNERS index b3e81b925ddc..e867586c11f9 100644 --- a/apex/OWNERS +++ b/apex/OWNERS @@ -1 +1 @@ -file:platform/packages/modules/common:/OWNERS +file:platform/packages/modules/common:/OWNERS #{LAST_RESORT_SUGGESTION} diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java index 96114dcb66a7..ffa534ec2053 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -1376,6 +1376,11 @@ public class BlobStoreManagerService extends SystemService { } } + private boolean isAllowedBlobAccess(int uid, String packageName) { + return (!Process.isSupplemental(uid) && !Process.isIsolated(uid) + && !mPackageManagerInternal.isInstantApp(packageName, UserHandle.getUserId(uid))); + } + private class PackageChangedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -1437,8 +1442,7 @@ public class BlobStoreManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); - if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp( - packageName, UserHandle.getUserId(callingUid))) { + if (!isAllowedBlobAccess(callingUid, packageName)) { throw new SecurityException("Caller not allowed to create session; " + "callingUid=" + callingUid + ", callingPackage=" + packageName); } @@ -1487,8 +1491,7 @@ public class BlobStoreManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); - if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp( - packageName, UserHandle.getUserId(callingUid))) { + if (!isAllowedBlobAccess(callingUid, packageName)) { throw new SecurityException("Caller not allowed to open blob; " + "callingUid=" + callingUid + ", callingPackage=" + packageName); } @@ -1519,8 +1522,7 @@ public class BlobStoreManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); - if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp( - packageName, UserHandle.getUserId(callingUid))) { + if (!isAllowedBlobAccess(callingUid, packageName)) { throw new SecurityException("Caller not allowed to open blob; " + "callingUid=" + callingUid + ", callingPackage=" + packageName); } @@ -1544,8 +1546,7 @@ public class BlobStoreManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); - if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp( - packageName, UserHandle.getUserId(callingUid))) { + if (!isAllowedBlobAccess(callingUid, packageName)) { throw new SecurityException("Caller not allowed to open blob; " + "callingUid=" + callingUid + ", callingPackage=" + packageName); } @@ -1628,8 +1629,7 @@ public class BlobStoreManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); - if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp( - packageName, UserHandle.getUserId(callingUid))) { + if (!isAllowedBlobAccess(callingUid, packageName)) { throw new SecurityException("Caller not allowed to open blob; " + "callingUid=" + callingUid + ", callingPackage=" + packageName); } diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java index 6d2bd47f60bd..2682dd75ca73 100644 --- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java @@ -16,7 +16,7 @@ package com.android.server.job; -import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.util.proto.ProtoOutputStream; @@ -47,9 +47,9 @@ public interface JobSchedulerInternal { void removeBackingUpUid(int uid); void clearAllBackingUpUids(); - /** Returns the package responsible for backing up media on the device. */ - @NonNull - String getMediaBackupPackage(); + /** Returns the package responsible for providing media from the cloud to the device. */ + @Nullable + String getCloudMediaProviderPackage(int userId); /** * The user has started interacting with the app. Take any appropriate action. diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 1620983c4137..a5c2bcc2d9e2 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -72,6 +72,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.PermissionChecker; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.database.ContentObserver; @@ -1839,6 +1840,9 @@ public class AlarmManagerService extends SystemService { if (!isExactAlarmChangeEnabled(a.packageName, UserHandle.getUserId(a.uid))) { return false; } + if (hasUseExactAlarmPermission(a.packageName, a.uid)) { + return false; + } return !isExemptFromExactAlarmPermission(a.uid); }; removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED); @@ -1900,6 +1904,9 @@ public class AlarmManagerService extends SystemService { || !isExactAlarmChangeEnabled(packageName, userId)) { return; } + if (hasUseExactAlarmPermission(packageName, uid)) { + return; + } final boolean requested = mExactAlarmCandidates.contains( UserHandle.getAppId(uid)); @@ -2534,6 +2541,8 @@ public class AlarmManagerService extends SystemService { private static boolean getScheduleExactAlarmState(boolean requested, boolean denyListed, int appOpMode) { + // This does not account for the state of the USE_EXACT_ALARM permission. + // The caller should do that separately. if (!requested) { return false; } @@ -2543,7 +2552,16 @@ public class AlarmManagerService extends SystemService { return appOpMode == AppOpsManager.MODE_ALLOWED; } + boolean hasUseExactAlarmPermission(String packageName, int uid) { + return PermissionChecker.checkPermissionForPreflight(getContext(), + Manifest.permission.USE_EXACT_ALARM, PermissionChecker.PID_UNKNOWN, uid, + packageName) == PermissionChecker.PERMISSION_GRANTED; + } + boolean hasScheduleExactAlarmInternal(String packageName, int uid) { + if (hasUseExactAlarmPermission(packageName, uid)) { + return true; + } // Not using getScheduleExactAlarmState as this can avoid some calls to AppOpsService. // Not using #mLastOpScheduleExactAlarm as it may contain stale values. // No locking needed as all internal containers being queried are immutable. @@ -3759,6 +3777,9 @@ public class AlarmManagerService extends SystemService { if (!isExactAlarmChangeEnabled(changedPackage, userId)) { continue; } + if (hasUseExactAlarmPermission(changedPackage, uid)) { + continue; + } final int appOpMode; synchronized (mLock) { appOpMode = mLastOpScheduleExactAlarm.get(uid, @@ -3778,7 +3799,8 @@ public class AlarmManagerService extends SystemService { } if (added) { synchronized (mLock) { - removeExactAlarmsOnPermissionRevokedLocked(uid, changedPackage); + removeExactAlarmsOnPermissionRevokedLocked(uid, + changedPackage, /*killUid = */ true); } } else { sendScheduleExactAlarmPermissionStateChangedBroadcast(changedPackage, userId); @@ -3794,7 +3816,7 @@ public class AlarmManagerService extends SystemService { * This is not expected to get called frequently. */ @GuardedBy("mLock") - void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) { + void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName, boolean killUid) { if (isExemptFromExactAlarmPermission(uid) || !isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) { return; @@ -3805,7 +3827,7 @@ public class AlarmManagerService extends SystemService { && a.windowLength == 0); removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED); - if (mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) { + if (killUid && mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) { PermissionManagerService.killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid), "schedule_exact_alarm revoked"); } @@ -4617,6 +4639,7 @@ public class AlarmManagerService extends SystemService { public static final int EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED = 10; public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11; public static final int TARE_AFFORDABILITY_CHANGED = 12; + public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13; AlarmHandler() { super(Looper.myLooper()); @@ -4715,10 +4738,11 @@ public class AlarmManagerService extends SystemService { break; case REMOVE_EXACT_ALARMS: - final int uid = msg.arg1; - final String packageName = (String) msg.obj; + int uid = msg.arg1; + String packageName = (String) msg.obj; synchronized (mLock) { - removeExactAlarmsOnPermissionRevokedLocked(uid, packageName); + removeExactAlarmsOnPermissionRevokedLocked(uid, packageName, /*killUid = */ + true); } break; case EXACT_ALARM_DENY_LIST_PACKAGES_ADDED: @@ -4730,6 +4754,16 @@ public class AlarmManagerService extends SystemService { case REFRESH_EXACT_ALARM_CANDIDATES: refreshExactAlarmCandidates(); break; + case CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE: + packageName = (String) msg.obj; + uid = msg.arg1; + if (!hasScheduleExactAlarmInternal(packageName, uid)) { + synchronized (mLock) { + removeExactAlarmsOnPermissionRevokedLocked(uid, + packageName, /*killUid = */false); + } + } + break; default: // nope, just ignore it break; @@ -4914,6 +4948,12 @@ public class AlarmManagerService extends SystemService { } break; case Intent.ACTION_PACKAGE_ADDED: + if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + final String packageUpdated = intent.getData().getSchemeSpecificPart(); + mHandler.obtainMessage( + AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE, uid, -1, + packageUpdated).sendToTarget(); + } mHandler.sendEmptyMessage(AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES); return; } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index cea19451f005..bdfdd5573064 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -52,6 +52,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; +import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.BatteryManager; @@ -70,6 +71,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.WorkSource; +import android.os.storage.StorageManagerInternal; import android.provider.DeviceConfig; import android.provider.Settings; import android.text.format.DateUtils; @@ -86,10 +88,10 @@ import android.util.SparseSetArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; -import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; +import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; @@ -237,6 +239,7 @@ public class JobSchedulerService extends com.android.server.SystemService static final int MSG_UID_ACTIVE = 6; static final int MSG_UID_IDLE = 7; static final int MSG_CHECK_CHANGED_JOB_LIST = 8; + static final int MSG_CHECK_MEDIA_EXEMPTION = 9; /** * Track Services that have currently active or pending jobs. The index is provided by @@ -271,8 +274,8 @@ public class JobSchedulerService extends com.android.server.SystemService @GuardedBy("mLock") private final BatteryStateTracker mBatteryStateTracker; - @NonNull - private final String mSystemGalleryPackage; + @GuardedBy("mLock") + private final SparseArray<String> mCloudMediaProviderPackages = new SparseArray<>(); private final CountQuotaTracker mQuotaTracker; private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()"; @@ -417,6 +420,9 @@ public class JobSchedulerService extends com.android.server.SystemService break; case Constants.KEY_CONN_CONGESTION_DELAY_FRAC: case Constants.KEY_CONN_PREFETCH_RELAX_FRAC: + case Constants.KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC: + case Constants.KEY_CONN_USE_CELL_SIGNAL_STRENGTH: + case Constants.KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS: mConstants.updateConnectivityConstantsLocked(); break; case Constants.KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS: @@ -489,6 +495,12 @@ public class JobSchedulerService extends com.android.server.SystemService private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms"; private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac"; private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac"; + private static final String KEY_CONN_USE_CELL_SIGNAL_STRENGTH = + "conn_use_cell_signal_strength"; + private static final String KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = + "conn_update_all_jobs_min_interval_ms"; + private static final String KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = + "conn_low_signal_strength_relax_frac"; private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = "prefetch_force_batch_relax_threshold_ms"; private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas"; @@ -514,6 +526,9 @@ public class JobSchedulerService extends com.android.server.SystemService private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS; private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f; private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f; + private static final boolean DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH = true; + private static final long DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = MINUTE_IN_MILLIS; + private static final float DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = 0.5f; private static final long DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = HOUR_IN_MILLIS; private static final boolean DEFAULT_ENABLE_API_QUOTAS = true; private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250; @@ -569,6 +584,23 @@ public class JobSchedulerService extends com.android.server.SystemService * we consider matching it against a metered network. */ public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC; + /** + * Whether to use the cell signal strength to determine if a particular job is eligible to + * run. + */ + public boolean CONN_USE_CELL_SIGNAL_STRENGTH = DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH; + /** + * When throttling updating all tracked jobs, make sure not to update them more frequently + * than this value. + */ + public long CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = + DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS; + /** + * The fraction of a job's running window that must pass before we consider running it on + * low signal strength networks. + */ + public float CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = + DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC; /** * The amount of time within which we would consider the app to be launching relatively soon @@ -661,6 +693,18 @@ public class JobSchedulerService extends com.android.server.SystemService CONN_PREFETCH_RELAX_FRAC = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_CONN_PREFETCH_RELAX_FRAC, DEFAULT_CONN_PREFETCH_RELAX_FRAC); + CONN_USE_CELL_SIGNAL_STRENGTH = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_JOB_SCHEDULER, + KEY_CONN_USE_CELL_SIGNAL_STRENGTH, + DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH); + CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = DeviceConfig.getLong( + DeviceConfig.NAMESPACE_JOB_SCHEDULER, + KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS, + DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS); + CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = DeviceConfig.getFloat( + DeviceConfig.NAMESPACE_JOB_SCHEDULER, + KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC, + DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC); } private void updatePrefetchConstantsLocked() { @@ -739,6 +783,11 @@ public class JobSchedulerService extends com.android.server.SystemService pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println(); pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println(); pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println(); + pw.print(KEY_CONN_USE_CELL_SIGNAL_STRENGTH, CONN_USE_CELL_SIGNAL_STRENGTH).println(); + pw.print(KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS, CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS) + .println(); + pw.print(KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC, CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC) + .println(); pw.print(KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS, PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS).println(); @@ -1737,9 +1786,6 @@ public class JobSchedulerService extends com.android.server.SystemService mJobRestrictions = new ArrayList<>(); mJobRestrictions.add(new ThermalStatusRestriction(this)); - mSystemGalleryPackage = Objects.requireNonNull( - context.getString(R.string.config_systemGallery)); - // If the job store determined that it can't yet reschedule persisted jobs, // we need to start watching the clock. if (!mJobs.jobTimesInflatedValid()) { @@ -1809,6 +1855,9 @@ public class JobSchedulerService extends com.android.server.SystemService mAppStateTracker = (AppStateTrackerImpl) Objects.requireNonNull( LocalServices.getService(AppStateTracker.class)); + LocalServices.getService(StorageManagerInternal.class) + .registerCloudProviderChangeListener(new CloudProviderChangeListener()); + // Register br for package removals and user removals. final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); @@ -2307,6 +2356,15 @@ public class JobSchedulerService extends com.android.server.SystemService break; } + case MSG_CHECK_MEDIA_EXEMPTION: { + final SomeArgs args = (SomeArgs) message.obj; + synchronized (mLock) { + updateMediaBackupExemptionLocked( + args.argi1, (String) args.arg1, (String) args.arg2); + } + args.recycle(); + break; + } } maybeRunPendingJobsLocked(); } @@ -2603,12 +2661,31 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "Check changed jobs..."); } + if (mChangedJobList.size() == 0) { + return; + } mChangedJobList.forEach(mMaybeQueueFunctor); mMaybeQueueFunctor.postProcessLocked(); mChangedJobList.clear(); } + @GuardedBy("mLock") + private void updateMediaBackupExemptionLocked(int userId, @Nullable String oldPkg, + @Nullable String newPkg) { + final Predicate<JobStatus> shouldProcessJob = + (job) -> job.getSourceUserId() == userId + && (job.getSourcePackageName().equals(oldPkg) + || job.getSourcePackageName().equals(newPkg)); + mJobs.forEachJob(shouldProcessJob, + (job) -> { + if (job.updateMediaBackupExemptionStatus()) { + mChangedJobList.add(job); + } + }); + mHandler.sendEmptyMessage(MSG_CHECK_CHANGED_JOB_LIST); + } + /** Returns true if both the calling and source users for the job are started. */ @GuardedBy("mLock") public boolean areUsersStartedLocked(final JobStatus job) { @@ -3004,8 +3081,8 @@ public class JobSchedulerService extends com.android.server.SystemService } @Override - public String getMediaBackupPackage() { - return mSystemGalleryPackage; + public String getCloudMediaProviderPackage(int userId) { + return mCloudMediaProviderPackages.get(userId); } @Override @@ -3113,6 +3190,35 @@ public class JobSchedulerService extends com.android.server.SystemService return bucket; } + private class CloudProviderChangeListener implements + StorageManagerInternal.CloudProviderChangeListener { + + @Override + public void onCloudProviderChanged(int userId, @Nullable String authority) { + final PackageManager pm = getContext() + .createContextAsUser(UserHandle.of(userId), 0) + .getPackageManager(); + final ProviderInfo pi = pm.resolveContentProvider( + authority, PackageManager.ComponentInfoFlags.of(0)); + final String newPkg = (pi == null) ? null : pi.packageName; + synchronized (mLock) { + final String oldPkg = mCloudMediaProviderPackages.get(userId); + if (!Objects.equals(oldPkg, newPkg)) { + if (DEBUG) { + Slog.d(TAG, "Cloud provider of user " + userId + " changed from " + oldPkg + + " to " + newPkg); + } + mCloudMediaProviderPackages.put(userId, newPkg); + SomeArgs args = SomeArgs.obtain(); + args.argi1 = userId; + args.arg1 = oldPkg; + args.arg2 = newPkg; + mHandler.obtainMessage(MSG_CHECK_MEDIA_EXEMPTION, args).sendToTarget(); + } + } + } + } + /** * Binder stub trampoline implementation */ @@ -3753,6 +3859,12 @@ public class JobSchedulerService extends com.android.server.SystemService pw.println(); pw.println("Started users: " + Arrays.toString(mStartedUsers)); + pw.println(); + + pw.print("Media Cloud Providers: "); + pw.println(mCloudMediaProviderPackages); + pw.println(); + pw.print("Registered "); pw.print(mJobs.size()); pw.println(" jobs:"); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index c678755f73da..892e0c0280c0 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -35,6 +35,10 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.UserHandle; +import android.telephony.CellSignalStrength; +import android.telephony.SignalStrength; +import android.telephony.TelephonyCallback; +import android.telephony.TelephonyManager; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -60,6 +64,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.function.Predicate; /** @@ -213,9 +218,16 @@ public final class ConnectivityController extends RestrictingController implemen * is only done in {@link #maybeAdjustRegisteredCallbacksLocked()} and may sometimes be stale. */ private final List<UidStats> mSortedStats = new ArrayList<>(); + @GuardedBy("mLock") private long mLastCallbackAdjustmentTimeElapsed; + @GuardedBy("mLock") + private final SparseArray<CellSignalStrengthCallback> mSignalStrengths = new SparseArray<>(); + + @GuardedBy("mLock") + private long mLastAllJobUpdateTimeElapsed; private static final int MSG_ADJUST_CALLBACKS = 0; + private static final int MSG_UPDATE_ALL_TRACKED_JOBS = 1; private final Handler mHandler; @@ -529,11 +541,7 @@ public final class ConnectivityController extends RestrictingController implemen @GuardedBy("mLock") public void onBatteryStateChangedLocked() { // Update job bookkeeping out of band to avoid blocking broadcast progress. - JobSchedulerBackgroundThread.getHandler().post(() -> { - synchronized (mLock) { - updateTrackedJobsLocked(-1, null); - } - }); + mHandler.sendEmptyMessage(MSG_UPDATE_ALL_TRACKED_JOBS); } private boolean isUsable(NetworkCapabilities capabilities) { @@ -650,6 +658,82 @@ public final class ConnectivityController extends RestrictingController implemen } } + @GuardedBy("mLock") + private boolean isStrongEnough(JobStatus jobStatus, NetworkCapabilities capabilities, + Constants constants) { + final int priority = jobStatus.getEffectivePriority(); + if (priority >= JobInfo.PRIORITY_HIGH) { + return true; + } + if (!constants.CONN_USE_CELL_SIGNAL_STRENGTH) { + return true; + } + if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + return true; + } + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) { + // Exclude VPNs because it's currently not possible to determine the VPN's underlying + // network, and thus the correct signal strength of the VPN's network. + // Transmitting data over a VPN is generally more battery-expensive than on the + // underlying network, so: + // TODO: find a good way to reduce job use of VPN when it'll be very expensive + // For now, we just pretend VPNs are always strong enough + return true; + } + + // VCNs running over WiFi will declare TRANSPORT_CELLULAR. When connected, a VCN will + // most likely be the default network. We ideally don't want this to restrict jobs when the + // VCN incorrectly declares the CELLULAR transport, but there's currently no way to + // determine if a network is a VCN. When there is: + // TODO(216127782): exclude VCN running over WiFi from this check + + int signalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + // Use the best strength found. + final Set<Integer> subscriptionIds = capabilities.getSubscriptionIds(); + for (int subId : subscriptionIds) { + CellSignalStrengthCallback callback = mSignalStrengths.get(subId); + if (callback != null) { + signalStrength = Math.max(signalStrength, callback.signalStrength); + } else { + Slog.wtf(TAG, + "Subscription ID " + subId + " doesn't have a registered callback"); + } + } + if (DEBUG) { + Slog.d(TAG, "Cell signal strength for job=" + signalStrength); + } + // Treat "NONE_OR_UNKNOWN" as "NONE". + if (signalStrength <= CellSignalStrength.SIGNAL_STRENGTH_POOR) { + // If signal strength is poor, don't run MIN or LOW priority jobs, and only + // run DEFAULT priority jobs if the device is charging or the job has been waiting + // long enough. + if (priority > JobInfo.PRIORITY_DEFAULT) { + return true; + } + if (priority < JobInfo.PRIORITY_DEFAULT) { + return false; + } + // DEFAULT job. + return (mService.isBatteryCharging() && mService.isBatteryNotLow()) + || jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC; + } + if (signalStrength <= CellSignalStrength.SIGNAL_STRENGTH_MODERATE) { + // If signal strength is moderate, only run MIN priority jobs when the device + // is charging, or the job is already running. + if (priority >= JobInfo.PRIORITY_LOW) { + return true; + } + // MIN job. + if (mService.isBatteryCharging() && mService.isBatteryNotLow()) { + return true; + } + final UidStats uidStats = getUidStats( + jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true); + return uidStats.runningJobs.contains(jobStatus); + } + return true; + } + private static NetworkCapabilities.Builder copyCapabilities( @NonNull final NetworkRequest request) { final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(); @@ -717,10 +801,12 @@ public final class ConnectivityController extends RestrictingController implemen // Second, is the network congested? if (isCongestionDelayed(jobStatus, network, capabilities, constants)) return false; - // Third, is the network a strict match? + if (!isStrongEnough(jobStatus, capabilities, constants)) return false; + + // Is the network a strict match? if (isStrictSatisfied(jobStatus, network, capabilities, constants)) return true; - // Third, is the network a relaxed match? + // Is the network a relaxed match? if (isRelaxedSatisfied(jobStatus, network, capabilities, constants)) return true; return false; @@ -985,6 +1071,24 @@ public final class ConnectivityController extends RestrictingController implemen return changed; } + @GuardedBy("mLock") + private void updateAllTrackedJobsLocked(boolean allowThrottle) { + if (allowThrottle) { + final long throttleTimeLeftMs = + (mLastAllJobUpdateTimeElapsed + mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS) + - sElapsedRealtimeClock.millis(); + if (throttleTimeLeftMs > 0) { + Message msg = mHandler.obtainMessage(MSG_UPDATE_ALL_TRACKED_JOBS, 1, 0); + mHandler.sendMessageDelayed(msg, throttleTimeLeftMs); + return; + } + } + + mHandler.removeMessages(MSG_UPDATE_ALL_TRACKED_JOBS); + updateTrackedJobsLocked(-1, null); + mLastAllJobUpdateTimeElapsed = sElapsedRealtimeClock.millis(); + } + /** * Update any jobs tracked by this controller that match given filters. * @@ -1088,7 +1192,11 @@ public final class ConnectivityController extends RestrictingController implemen Slog.v(TAG, "onCapabilitiesChanged: " + network); } synchronized (mLock) { - mAvailableNetworks.put(network, capabilities); + final NetworkCapabilities oldCaps = mAvailableNetworks.put(network, capabilities); + if (oldCaps != null) { + maybeUnregisterSignalStrengthCallbackLocked(oldCaps); + } + maybeRegisterSignalStrengthCallbackLocked(capabilities); updateTrackedJobsLocked(-1, network); postAdjustCallbacks(); } @@ -1100,7 +1208,10 @@ public final class ConnectivityController extends RestrictingController implemen Slog.v(TAG, "onLost: " + network); } synchronized (mLock) { - mAvailableNetworks.remove(network); + final NetworkCapabilities capabilities = mAvailableNetworks.remove(network); + if (capabilities != null) { + maybeUnregisterSignalStrengthCallbackLocked(capabilities); + } for (int u = 0; u < mCurrentDefaultNetworkCallbacks.size(); ++u) { UidDefaultNetworkCallback callback = mCurrentDefaultNetworkCallbacks.valueAt(u); if (Objects.equals(callback.mDefaultNetwork, network)) { @@ -1111,6 +1222,63 @@ public final class ConnectivityController extends RestrictingController implemen postAdjustCallbacks(); } } + + @GuardedBy("mLock") + private void maybeRegisterSignalStrengthCallbackLocked( + @NonNull NetworkCapabilities capabilities) { + if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + return; + } + TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); + final Set<Integer> subscriptionIds = capabilities.getSubscriptionIds(); + for (int subId : subscriptionIds) { + if (mSignalStrengths.indexOfKey(subId) >= 0) { + continue; + } + TelephonyManager idTm = telephonyManager.createForSubscriptionId(subId); + CellSignalStrengthCallback callback = new CellSignalStrengthCallback(); + idTm.registerTelephonyCallback( + JobSchedulerBackgroundThread.getExecutor(), callback); + mSignalStrengths.put(subId, callback); + + final SignalStrength signalStrength = idTm.getSignalStrength(); + if (signalStrength != null) { + callback.signalStrength = signalStrength.getLevel(); + } + } + } + + @GuardedBy("mLock") + private void maybeUnregisterSignalStrengthCallbackLocked( + @NonNull NetworkCapabilities capabilities) { + if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + return; + } + ArraySet<Integer> activeIds = new ArraySet<>(); + for (int i = 0, size = mAvailableNetworks.size(); i < size; ++i) { + NetworkCapabilities nc = mAvailableNetworks.valueAt(i); + if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + activeIds.addAll(nc.getSubscriptionIds()); + } + } + if (DEBUG) { + Slog.d(TAG, "Active subscription IDs: " + activeIds); + } + TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); + Set<Integer> subscriptionIds = capabilities.getSubscriptionIds(); + for (int subId : subscriptionIds) { + if (activeIds.contains(subId)) { + continue; + } + TelephonyManager idTm = telephonyManager.createForSubscriptionId(subId); + CellSignalStrengthCallback callback = mSignalStrengths.removeReturnOld(subId); + if (callback != null) { + idTm.unregisterTelephonyCallback(callback); + } else { + Slog.wtf(TAG, "Callback for sub " + subId + " didn't exist?!?!"); + } + } + } }; private class CcHandler extends Handler { @@ -1127,6 +1295,13 @@ public final class ConnectivityController extends RestrictingController implemen maybeAdjustRegisteredCallbacksLocked(); } break; + + case MSG_UPDATE_ALL_TRACKED_JOBS: + synchronized (mLock) { + final boolean allowThrottle = msg.arg1 == 1; + updateAllTrackedJobsLocked(allowThrottle); + } + break; } } } @@ -1268,6 +1443,33 @@ public final class ConnectivityController extends RestrictingController implemen } } + private class CellSignalStrengthCallback extends TelephonyCallback + implements TelephonyCallback.SignalStrengthsListener { + @GuardedBy("mLock") + public int signalStrength = CellSignalStrength.SIGNAL_STRENGTH_GREAT; + + @Override + public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength) { + synchronized (mLock) { + final int newSignalStrength = signalStrength.getLevel(); + if (DEBUG) { + Slog.d(TAG, "Signal strength changing from " + + this.signalStrength + " to " + newSignalStrength); + for (CellSignalStrength css : signalStrength.getCellSignalStrengths()) { + Slog.d(TAG, "CSS: " + css.getLevel() + " " + css); + } + } + if (this.signalStrength == newSignalStrength) { + // This happens a lot. + return; + } + this.signalStrength = newSignalStrength; + // Update job bookkeeping out of band to avoid blocking callback progress. + mHandler.obtainMessage(MSG_UPDATE_ALL_TRACKED_JOBS, 1, 0).sendToTarget(); + } + } + } + @GuardedBy("mLock") @Override public void dumpControllerStateLocked(IndentingPrintWriter pw, @@ -1299,6 +1501,20 @@ public final class ConnectivityController extends RestrictingController implemen } pw.println(); + if (mSignalStrengths.size() > 0) { + pw.println("Subscription ID signal strengths:"); + pw.increaseIndent(); + for (int i = 0; i < mSignalStrengths.size(); ++i) { + pw.print(mSignalStrengths.keyAt(i)); + pw.print(": "); + pw.println(mSignalStrengths.valueAt(i).signalStrength); + } + pw.decreaseIndent(); + } else { + pw.println("No cached signal strengths"); + } + pw.println(); + pw.println("Current default network callbacks:"); pw.increaseIndent(); for (int i = 0; i < mCurrentDefaultNetworkCallbacks.size(); i++) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 0456a9bfeb2e..649aa394b951 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -261,11 +261,9 @@ public final class JobStatus { * * Doesn't exempt jobs with a deadline constraint, as they can be started without any content or * network changes, in which case this exemption does not make sense. - * - * TODO(b/149519887): Use a more explicit signal, maybe an API flag, that the scheduling package - * needs to provide at the time of scheduling a job. */ - private final boolean mHasMediaBackupExemption; + private boolean mHasMediaBackupExemption; + private final boolean mHasExemptedMediaUrisOnly; // Set to true if doze constraint was satisfied due to app being whitelisted. boolean appHasDozeExemption; @@ -508,11 +506,9 @@ public final class JobStatus { this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis; this.numFailures = numFailures; - boolean requiresNetwork = false; int requiredConstraints = job.getConstraintFlags(); if (job.getRequiredNetwork() != null) { requiredConstraints |= CONSTRAINT_CONNECTIVITY; - requiresNetwork = true; } if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) { requiredConstraints |= CONSTRAINT_TIMING_DELAY; @@ -531,6 +527,7 @@ public final class JobStatus { } } } + mHasExemptedMediaUrisOnly = exemptedMediaUrisOnly; this.requiredConstraints = requiredConstraints; mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST; addDynamicConstraints(dynamicConstraints); @@ -563,9 +560,7 @@ public final class JobStatus { job = builder.build(false); } - final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class); - mHasMediaBackupExemption = !job.hasLateConstraint() && exemptedMediaUrisOnly - && requiresNetwork && this.sourcePackageName.equals(jsi.getMediaBackupPackage()); + updateMediaBackupExemptionStatus(); } /** Copy constructor: used specifically when cloning JobStatus objects for persistence, @@ -914,6 +909,25 @@ public final class JobStatus { mFirstForceBatchedTimeElapsed = now; } + /** + * Re-evaluates the media backup exemption status. + * + * @return true if the exemption status changed + */ + public boolean updateMediaBackupExemptionStatus() { + final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class); + boolean hasMediaExemption = mHasExemptedMediaUrisOnly + && !job.hasLateConstraint() + && job.getRequiredNetwork() != null + && getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT + && sourcePackageName.equals(jsi.getCloudMediaProviderPackage(sourceUserId)); + if (mHasMediaBackupExemption == hasMediaExemption) { + return false; + } + mHasMediaBackupExemption = hasMediaExemption; + return true; + } + public String getSourceTag() { return sourceTag; } @@ -1139,11 +1153,12 @@ public final class JobStatus { */ public float getFractionRunTime() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - if (earliestRunTimeElapsedMillis == 0 && latestRunTimeElapsedMillis == Long.MAX_VALUE) { + if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME + && latestRunTimeElapsedMillis == NO_LATEST_RUNTIME) { return 1; - } else if (earliestRunTimeElapsedMillis == 0) { + } else if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME) { return now >= latestRunTimeElapsedMillis ? 1 : 0; - } else if (latestRunTimeElapsedMillis == Long.MAX_VALUE) { + } else if (latestRunTimeElapsedMillis == NO_LATEST_RUNTIME) { return now >= earliestRunTimeElapsedMillis ? 1 : 0; } else { if (now <= earliestRunTimeElapsedMillis) { @@ -2026,6 +2041,7 @@ public final class JobStatus { TimeUtils.formatDuration(job.getTriggerContentMaxDelay(), pw); pw.println(); } + pw.print("Has media backup exemption", mHasMediaBackupExemption).println(); } if (job.getExtras() != null && !job.getExtras().isDefinitelyEmpty()) { pw.print("Extras: "); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 19f253719607..b96055f2a452 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -1099,7 +1099,7 @@ public final class QuotaController extends StateController { final long maxExecutionTimeRemainingMs = mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs; - if (maxExecutionTimeRemainingMs <= 0) { + if (maxExecutionTimeRemainingMs < 0) { return 0; } @@ -1110,7 +1110,7 @@ public final class QuotaController extends StateController { sessions, startMaxElapsed, maxExecutionTimeRemainingMs); } - if (allowedTimeRemainingMs <= 0) { + if (allowedTimeRemainingMs < 0) { return 0; } diff --git a/core/api/current.txt b/core/api/current.txt index 5ee3e1d2b6b0..ea3830ff2da0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -112,6 +112,7 @@ package android { field public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS"; field public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS"; field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN"; + field public static final String MANAGE_WIFI_INTERFACES = "android.permission.MANAGE_WIFI_INTERFACES"; field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR"; field public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL"; field public static final String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS"; @@ -194,6 +195,7 @@ package android { field public static final String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS"; field public static final String UPDATE_PACKAGES_WITHOUT_USER_ACTION = "android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION"; field public static final String USE_BIOMETRIC = "android.permission.USE_BIOMETRIC"; + field public static final String USE_EXACT_ALARM = "android.permission.USE_EXACT_ALARM"; field @Deprecated public static final String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT"; field public static final String USE_FULL_SCREEN_INTENT = "android.permission.USE_FULL_SCREEN_INTENT"; field public static final String USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER = "android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER"; @@ -341,6 +343,7 @@ package android { field public static final int allowSingleTap = 16843353; // 0x1010259 field public static final int allowTaskReparenting = 16843268; // 0x1010204 field public static final int allowUndo = 16843999; // 0x10104df + field public static final int allowUntrustedActivityEmbedding; field public static final int alpha = 16843551; // 0x101031f field public static final int alphabeticModifiers = 16844110; // 0x101054e field public static final int alphabeticShortcut = 16843235; // 0x10101e3 @@ -863,6 +866,7 @@ package android { field public static final int installLocation = 16843447; // 0x10102b7 field public static final int interactiveUiTimeout = 16844181; // 0x1010595 field public static final int interpolator = 16843073; // 0x1010141 + field public static final int intro; field public static final int isAccessibilityTool = 16844353; // 0x1010641 field public static final int isAlwaysSyncable = 16843571; // 0x1010333 field public static final int isAsciiCapable = 16843753; // 0x10103e9 @@ -905,6 +909,7 @@ package android { field public static final int keyboardNavigationCluster = 16844096; // 0x1010540 field public static final int keycode = 16842949; // 0x10100c5 field public static final int killAfterRestore = 16843420; // 0x101029c + field public static final int knownActivityEmbeddingCerts; field public static final int knownCerts = 16844330; // 0x101062a field public static final int lStar = 16844359; // 0x1010647 field public static final int label = 16842753; // 0x1010001 @@ -1747,6 +1752,7 @@ package android { field public static final int windowSplashScreenAnimatedIcon = 16844333; // 0x101062d field public static final int windowSplashScreenAnimationDuration = 16844334; // 0x101062e field public static final int windowSplashScreenBackground = 16844332; // 0x101062c + field public static final int windowSplashScreenBehavior; field public static final int windowSplashScreenBrandingImage = 16844335; // 0x101062f field public static final int windowSplashScreenIconBackgroundColor = 16844336; // 0x1010630 field @Deprecated public static final int windowSplashscreenContent = 16844132; // 0x1010564 @@ -3066,6 +3072,7 @@ package android.accessibilityservice { method @Nullable public final android.accessibilityservice.InputMethod getInputMethod(); method @NonNull public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController(); method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow(); + method @Nullable public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow(int); method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo(); method @NonNull public final android.accessibilityservice.AccessibilityService.SoftKeyboardController getSoftKeyboardController(); method @NonNull public final java.util.List<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> getSystemActions(); @@ -3241,6 +3248,7 @@ package android.accessibilityservice { method @Nullable public String getTileServiceClassName(); method public boolean isAccessibilityTool(); method public String loadDescription(android.content.pm.PackageManager); + method @Nullable public CharSequence loadIntro(@NonNull android.content.pm.PackageManager); method public CharSequence loadSummary(android.content.pm.PackageManager); method public void setInteractiveUiTimeoutMillis(@IntRange(from=0) int); method public void setNonInteractiveUiTimeoutMillis(@IntRange(from=0) int); @@ -4533,6 +4541,7 @@ package android.app { method @Nullable public android.graphics.Rect getLaunchBounds(); method public int getLaunchDisplayId(); method public boolean getLockTaskMode(); + method public int getSplashScreenStyle(); method public boolean isPendingIntentBackgroundActivityLaunchAllowed(); method public static android.app.ActivityOptions makeBasic(); method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int); @@ -6627,6 +6636,7 @@ package android.app { method public android.app.PictureInPictureParams.Builder setActions(java.util.List<android.app.RemoteAction>); method public android.app.PictureInPictureParams.Builder setAspectRatio(android.util.Rational); method @NonNull public android.app.PictureInPictureParams.Builder setAutoEnterEnabled(boolean); + method @NonNull public android.app.PictureInPictureParams.Builder setExpandedAspectRatio(@Nullable android.util.Rational); method @NonNull public android.app.PictureInPictureParams.Builder setSeamlessResizeEnabled(boolean); method public android.app.PictureInPictureParams.Builder setSourceRectHint(android.graphics.Rect); } @@ -7039,6 +7049,7 @@ package android.app { public final class VoiceInteractor { method public android.app.VoiceInteractor.Request getActiveRequest(String); method public android.app.VoiceInteractor.Request[] getActiveRequests(); + method @NonNull public String getPackageName(); method public boolean isDestroyed(); method public void notifyDirectActionsChanged(); method public boolean registerOnDestroyedCallback(@NonNull java.util.concurrent.Executor, @NonNull Runnable); @@ -7880,6 +7891,8 @@ package android.app.admin { field public static final int TAG_ADB_SHELL_CMD = 210002; // 0x33452 field public static final int TAG_ADB_SHELL_INTERACTIVE = 210001; // 0x33451 field public static final int TAG_APP_PROCESS_START = 210005; // 0x33455 + field public static final int TAG_BLUETOOTH_CONNECTION = 210039; // 0x33477 + field public static final int TAG_BLUETOOTH_DISCONNECTION = 210040; // 0x33478 field public static final int TAG_CAMERA_POLICY_SET = 210034; // 0x33472 field public static final int TAG_CERT_AUTHORITY_INSTALLED = 210029; // 0x3346d field public static final int TAG_CERT_AUTHORITY_REMOVED = 210030; // 0x3346e @@ -7902,6 +7915,7 @@ package android.app.admin { field public static final int TAG_MEDIA_UNMOUNT = 210014; // 0x3345e field public static final int TAG_OS_SHUTDOWN = 210010; // 0x3345a field public static final int TAG_OS_STARTUP = 210009; // 0x33459 + field public static final int TAG_PASSWORD_CHANGED = 210036; // 0x33474 field public static final int TAG_PASSWORD_COMPLEXITY_REQUIRED = 210035; // 0x33473 field public static final int TAG_PASSWORD_COMPLEXITY_SET = 210017; // 0x33461 field public static final int TAG_PASSWORD_EXPIRATION_SET = 210016; // 0x33460 @@ -7911,6 +7925,8 @@ package android.app.admin { field public static final int TAG_SYNC_SEND_FILE = 210004; // 0x33454 field public static final int TAG_USER_RESTRICTION_ADDED = 210027; // 0x3346b field public static final int TAG_USER_RESTRICTION_REMOVED = 210028; // 0x3346c + field public static final int TAG_WIFI_CONNECTION = 210037; // 0x33475 + field public static final int TAG_WIFI_DISCONNECTION = 210038; // 0x33476 field public static final int TAG_WIPE_FAILURE = 210023; // 0x33467 } @@ -10964,6 +10980,7 @@ package android.content.pm { ctor public ActivityInfo(android.content.pm.ActivityInfo); method public int describeContents(); method public void dump(android.util.Printer, String); + method @NonNull public java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts(); method public final int getThemeResource(); field public static final int COLOR_MODE_DEFAULT = 0; // 0x0 field public static final int COLOR_MODE_HDR = 2; // 0x2 @@ -10991,6 +11008,7 @@ package android.content.pm { field public static final int DOCUMENT_LAUNCH_NEVER = 3; // 0x3 field public static final int DOCUMENT_LAUNCH_NONE = 0; // 0x0 field public static final int FLAG_ALLOW_TASK_REPARENTING = 64; // 0x40 + field public static final int FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING = 268435456; // 0x10000000 field public static final int FLAG_ALWAYS_RETAIN_TASK_STATE = 8; // 0x8 field public static final int FLAG_AUTO_REMOVE_FROM_RECENTS = 8192; // 0x2000 field public static final int FLAG_CLEAR_TASK_ON_LAUNCH = 4; // 0x4 @@ -11080,6 +11098,7 @@ package android.content.pm { method public void dump(android.util.Printer, String); method public static CharSequence getCategoryTitle(android.content.Context, int); method public int getGwpAsanMode(); + method @NonNull public java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts(); method public int getMemtagMode(); method public int getNativeHeapZeroInitialized(); method public int getRequestRawExternalStorageAccess(); @@ -11849,6 +11868,7 @@ package android.content.pm { field public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin"; field public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded"; field public static final String FEATURE_ETHERNET = "android.hardware.ethernet"; + field public static final String FEATURE_EXPANDED_PICTURE_IN_PICTURE = "android.software.expanded_picture_in_picture"; field public static final String FEATURE_FACE = "android.hardware.biometrics.face"; field public static final String FEATURE_FAKETOUCH = "android.hardware.faketouch"; field public static final String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct"; @@ -17360,6 +17380,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size> SCALER_DEFAULT_SECURE_IMAGE_SIZE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_PREVIEW_STABILIZATION_OUTPUT_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_TEN_BIT_OUTPUT_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_USE_CASE_STREAM_COMBINATIONS; @@ -18195,6 +18216,7 @@ package android.hardware.camera2.params { method public void enableSurfaceSharing(); method public int getDynamicRangeProfile(); method public int getMaxSharedSurfaceCount(); + method public int getMirrorMode(); method public int getStreamUseCase(); method @Nullable public android.view.Surface getSurface(); method public int getSurfaceGroupId(); @@ -18203,11 +18225,16 @@ package android.hardware.camera2.params { method public void removeSensorPixelModeUsed(int); method public void removeSurface(@NonNull android.view.Surface); method public void setDynamicRangeProfile(int); + method public void setMirrorMode(int); method public void setPhysicalCameraId(@Nullable String); method public void setStreamUseCase(int); method public void setTimestampBase(int); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR; + field public static final int MIRROR_MODE_AUTO = 0; // 0x0 + field public static final int MIRROR_MODE_H = 2; // 0x2 + field public static final int MIRROR_MODE_NONE = 1; // 0x1 + field public static final int MIRROR_MODE_V = 3; // 0x3 field public static final int SURFACE_GROUP_ID_NONE = -1; // 0xffffffff field public static final int TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED = 4; // 0x4 field public static final int TIMESTAMP_BASE_DEFAULT = 0; // 0x0 @@ -18334,8 +18361,8 @@ package android.hardware.display { } public final class DisplayManager { - method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, @Nullable android.view.Surface, int); - method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, @Nullable android.view.Surface, int, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler); + method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int); + method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler); method public android.view.Display getDisplay(int); method public android.view.Display[] getDisplays(); method public android.view.Display[] getDisplays(String); @@ -24352,6 +24379,17 @@ package android.media.effect { package android.media.metrics { + public final class BundleSession implements java.lang.AutoCloseable { + method public void close(); + method @NonNull public android.media.metrics.LogSessionId getSessionId(); + method public void reportBundleMetrics(@NonNull android.os.PersistableBundle); + } + + public final class EditingSession implements java.lang.AutoCloseable { + method public void close(); + method @NonNull public android.media.metrics.LogSessionId getSessionId(); + } + public abstract class Event { method @NonNull public android.os.Bundle getMetricsBundle(); method @IntRange(from=0xffffffff) public long getTimeSinceCreatedMillis(); @@ -24363,8 +24401,11 @@ package android.media.metrics { } public final class MediaMetricsManager { + method @NonNull public android.media.metrics.BundleSession createBundleSession(); + method @NonNull public android.media.metrics.EditingSession createEditingSession(); method @NonNull public android.media.metrics.PlaybackSession createPlaybackSession(); method @NonNull public android.media.metrics.RecordingSession createRecordingSession(); + method @NonNull public android.media.metrics.TranscodingSession createTranscodingSession(); field public static final long INVALID_TIMESTAMP = -1L; // 0xffffffffffffffffL } @@ -24612,6 +24653,11 @@ package android.media.metrics { method @NonNull public android.media.metrics.TrackChangeEvent.Builder setWidth(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int); } + public final class TranscodingSession implements java.lang.AutoCloseable { + method public void close(); + method @NonNull public android.media.metrics.LogSessionId getSessionId(); + } + } package android.media.midi { @@ -26469,7 +26515,7 @@ package android.net { method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthPsk(@NonNull byte[]); method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthUsernamePassword(@NonNull String, @NonNull String, @Nullable java.security.cert.X509Certificate); method @NonNull public android.net.Ikev2VpnProfile.Builder setBypassable(boolean); - method @NonNull public android.net.Ikev2VpnProfile.Builder setExcludeLocalRoutes(boolean); + method @NonNull public android.net.Ikev2VpnProfile.Builder setLocalRoutesExcluded(boolean); method @NonNull public android.net.Ikev2VpnProfile.Builder setMaxMtu(int); method @NonNull public android.net.Ikev2VpnProfile.Builder setMetered(boolean); method @NonNull public android.net.Ikev2VpnProfile.Builder setProxy(@Nullable android.net.ProxyInfo); @@ -26547,7 +26593,7 @@ package android.net { } public abstract class PlatformVpnProfile { - method public final boolean getExcludeLocalRoutes(); + method public final boolean areLocalRoutesExcluded(); method public final boolean getRequiresInternetValidation(); method public final int getType(); method @NonNull public final String getTypeString(); @@ -26744,6 +26790,23 @@ package android.net { method @Deprecated public void startProvisionedVpnProfile(); method @NonNull public String startProvisionedVpnProfileSession(); method public void stopProvisionedVpnProfile(); + field public static final String ACTION_VPN_MANAGER_EVENT = "android.net.action.VPN_MANAGER_EVENT"; + field public static final String CATEGORY_EVENT_DEACTIVATED_BY_USER = "android.net.category.EVENT_DEACTIVATED_BY_USER"; + field public static final String CATEGORY_EVENT_IKE_ERROR = "android.net.category.EVENT_IKE_ERROR"; + field public static final String CATEGORY_EVENT_NETWORK_ERROR = "android.net.category.EVENT_NETWORK_ERROR"; + field public static final int ERROR_CLASS_NOT_RECOVERABLE = 1; // 0x1 + field public static final int ERROR_CLASS_RECOVERABLE = 2; // 0x2 + field public static final int ERROR_CODE_NETWORK_IO = 3; // 0x3 + field public static final int ERROR_CODE_NETWORK_LOST = 2; // 0x2 + field public static final int ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT = 1; // 0x1 + field public static final int ERROR_CODE_NETWORK_UNKNOWN_HOST = 0; // 0x0 + field public static final String EXTRA_ERROR_CLASS = "android.net.extra.ERROR_CLASS"; + field public static final String EXTRA_ERROR_CODE = "android.net.extra.ERROR_CODE"; + field public static final String EXTRA_SESSION_KEY = "android.net.extra.SESSION_KEY"; + field public static final String EXTRA_TIMESTAMP = "android.net.extra.TIMESTAMP"; + field public static final String EXTRA_UNDERLYING_LINK_PROPERTIES = "android.net.extra.UNDERLYING_LINK_PROPERTIES"; + field public static final String EXTRA_UNDERLYING_NETWORK = "android.net.extra.UNDERLYING_NETWORK"; + field public static final String EXTRA_UNDERLYING_NETWORK_CAPABILITIES = "android.net.extra.UNDERLYING_NETWORK_CAPABILITIES"; } public class VpnService extends android.app.Service { @@ -39467,9 +39530,9 @@ package android.speech { public static final class RecognitionSupport.Builder { ctor public RecognitionSupport.Builder(); - method @NonNull public android.speech.RecognitionSupport.Builder addInstalledLanguages(@NonNull String); - method @NonNull public android.speech.RecognitionSupport.Builder addPendingLanguages(@NonNull String); - method @NonNull public android.speech.RecognitionSupport.Builder addSupportedLanguages(@NonNull String); + method @NonNull public android.speech.RecognitionSupport.Builder addInstalledLanguage(@NonNull String); + method @NonNull public android.speech.RecognitionSupport.Builder addPendingLanguage(@NonNull String); + method @NonNull public android.speech.RecognitionSupport.Builder addSupportedLanguage(@NonNull String); method @NonNull public android.speech.RecognitionSupport build(); method @NonNull public android.speech.RecognitionSupport.Builder setInstalledLanguages(@NonNull java.util.List<java.lang.String>); method @NonNull public android.speech.RecognitionSupport.Builder setPendingLanguages(@NonNull java.util.List<java.lang.String>); @@ -41338,13 +41401,13 @@ package android.telephony { field public static final int IPSEC_ENCRYPTION_ALGORITHM_AES_CBC = 2; // 0x2 field public static final int IPSEC_ENCRYPTION_ALGORITHM_DES_EDE3_CBC = 1; // 0x1 field public static final int IPSEC_ENCRYPTION_ALGORITHM_NULL = 0; // 0x0 - field public static final String KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY = "ims.key_capability_type_call_composer_int_array"; - field public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY = "ims.key_capability_type_options_uce_int_array"; - field public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY = "ims.key_capability_type_presence_uce_int_array"; - field public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY = "ims.key_capability_type_sms_int_array"; - field public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY = "ims.key_capability_type_ut_int_array"; - field public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY = "ims.key_capability_type_video_int_array"; - field public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY = "ims.key_capability_type_voice_int_array"; + field public static final String KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY = "ims.capability_type_call_composer_int_array"; + field public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY = "ims.capability_type_options_uce_int_array"; + field public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY = "ims.capability_type_presence_uce_int_array"; + field public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY = "ims.capability_type_sms_int_array"; + field public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY = "ims.capability_type_ut_int_array"; + field public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY = "ims.capability_type_video_int_array"; + field public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY = "ims.capability_type_voice_int_array"; field public static final String KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL = "ims.enable_presence_capability_exchange_bool"; field public static final String KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL = "ims.enable_presence_group_subscribe_bool"; field public static final String KEY_ENABLE_PRESENCE_PUBLISH_BOOL = "ims.enable_presence_publish_bool"; @@ -51600,6 +51663,7 @@ package android.view.accessibility { method @Deprecated public void getBoundsInParent(android.graphics.Rect); method public void getBoundsInScreen(android.graphics.Rect); method public android.view.accessibility.AccessibilityNodeInfo getChild(int); + method @Nullable public android.view.accessibility.AccessibilityNodeInfo getChild(int, int); method public int getChildCount(); method public CharSequence getClassName(); method public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo getCollectionInfo(); @@ -51619,6 +51683,7 @@ package android.view.accessibility { method public CharSequence getPackageName(); method @Nullable public CharSequence getPaneTitle(); method public android.view.accessibility.AccessibilityNodeInfo getParent(); + method @Nullable public android.view.accessibility.AccessibilityNodeInfo getParent(int); method public android.view.accessibility.AccessibilityNodeInfo.RangeInfo getRangeInfo(); method @Nullable public CharSequence getStateDescription(); method public CharSequence getText(); @@ -51769,8 +51834,15 @@ package android.view.accessibility { field public static final int EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_MAX_LENGTH = 20000; // 0x4e20 field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX"; field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY"; + field public static final int FLAG_PREFETCH_ANCESTORS = 1; // 0x1 + field public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 16; // 0x10 + field public static final int FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST = 8; // 0x8 + field public static final int FLAG_PREFETCH_DESCENDANTS_HYBRID = 4; // 0x4 + field public static final int FLAG_PREFETCH_SIBLINGS = 2; // 0x2 + field public static final int FLAG_PREFETCH_UNINTERRUPTIBLE = 32; // 0x20 field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2 field public static final int FOCUS_INPUT = 1; // 0x1 + field public static final int MAX_NUMBER_OF_PREFETCHED_NODES = 50; // 0x32 field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1 field public static final int MOVEMENT_GRANULARITY_LINE = 4; // 0x4 field public static final int MOVEMENT_GRANULARITY_PAGE = 16; // 0x10 @@ -51935,6 +52007,7 @@ package android.view.accessibility { method public int getScrollX(); method public int getScrollY(); method @Nullable public android.view.accessibility.AccessibilityNodeInfo getSource(); + method @Nullable public android.view.accessibility.AccessibilityNodeInfo getSource(int); method @NonNull public java.util.List<java.lang.CharSequence> getText(); method public int getToIndex(); method public int getWindowId(); @@ -51992,6 +52065,7 @@ package android.view.accessibility { method public android.view.accessibility.AccessibilityWindowInfo getParent(); method public void getRegionInScreen(@NonNull android.graphics.Region); method public android.view.accessibility.AccessibilityNodeInfo getRoot(); + method @Nullable public android.view.accessibility.AccessibilityNodeInfo getRoot(int); method @Nullable public CharSequence getTitle(); method public int getType(); method public boolean isAccessibilityFocused(); @@ -52376,6 +52450,8 @@ package android.view.autofill { method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect); method public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback); method public void setUserData(@Nullable android.service.autofill.UserData); + method public boolean showAutofillDialog(@NonNull android.view.View); + method public boolean showAutofillDialog(@NonNull android.view.View, int); method public void unregisterCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback); field public static final String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE"; field public static final String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT"; diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 5df593d59f83..594f46b6694b 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -250,6 +250,22 @@ package android.media.session { package android.net { + public class EthernetManager { + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void addInterfaceStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.InterfaceStateListener); + method public void removeInterfaceStateListener(@NonNull android.net.EthernetManager.InterfaceStateListener); + method public void setIncludeTestInterfaces(boolean); + field public static final int ROLE_CLIENT = 1; // 0x1 + field public static final int ROLE_NONE = 0; // 0x0 + field public static final int ROLE_SERVER = 2; // 0x2 + field public static final int STATE_ABSENT = 0; // 0x0 + field public static final int STATE_LINK_DOWN = 1; // 0x1 + field public static final int STATE_LINK_UP = 2; // 0x2 + } + + public static interface EthernetManager.InterfaceStateListener { + method public void onInterfaceStateChanged(@NonNull String, int, int, @Nullable android.net.IpConfiguration); + } + public class LocalSocket implements java.io.Closeable { ctor public LocalSocket(@NonNull java.io.FileDescriptor); } @@ -399,6 +415,7 @@ package android.os.storage { method public long computeStorageCacheBytes(@NonNull java.io.File); method public void notifyAppIoBlocked(@NonNull java.util.UUID, int, int, int); method public void notifyAppIoResumed(@NonNull java.util.UUID, int, int, int); + method public void setCloudMediaProvider(@Nullable String); field public static final int APP_IO_BLOCKED_REASON_TRANSCODING = 1; // 0x1 field public static final int APP_IO_BLOCKED_REASON_UNKNOWN = 0; // 0x0 } @@ -466,6 +483,25 @@ package android.util { method public static int logToRadioBuffer(int, @Nullable String, @Nullable String); } + public final class Slog { + method public static int d(@Nullable String, @NonNull String); + method public static int d(@Nullable String, @NonNull String, @Nullable Throwable); + method public static int e(@Nullable String, @NonNull String); + method public static int e(@Nullable String, @NonNull String, @Nullable Throwable); + method public static int i(@Nullable String, @NonNull String); + method public static int i(@Nullable String, @NonNull String, @Nullable Throwable); + method public static int v(@Nullable String, @NonNull String); + method public static int v(@Nullable String, @NonNull String, @Nullable Throwable); + method public static int w(@Nullable String, @NonNull String); + method public static int w(@Nullable String, @NonNull String, @Nullable Throwable); + method public static int w(@Nullable String, @Nullable Throwable); + method public static int wtf(@Nullable String, @NonNull String); + method public static int wtf(@Nullable String, @Nullable Throwable); + method public static int wtf(@Nullable String, @NonNull String, @Nullable Throwable); + method public static void wtfQuiet(@Nullable String, @NonNull String); + method public static int wtfStack(@Nullable String, @NonNull String); + } + public class SystemConfigFileCommitEventLogger { ctor public SystemConfigFileCommitEventLogger(@NonNull String); method public void setStartTime(long); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 82539e80474e..297239a070f4 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -355,6 +355,7 @@ package android { field public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"; field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; field public static final String WRITE_OBB = "android.permission.WRITE_OBB"; + field public static final String WRITE_SECURITY_LOG = "android.permission.WRITE_SECURITY_LOG"; field public static final String WRITE_SMS = "android.permission.WRITE_SMS"; } @@ -851,6 +852,10 @@ package android.app { field public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; // 0xb } + public static class Notification.MediaStyle extends android.app.Notification.Style { + method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.app.Notification.MediaStyle setRemotePlaybackInfo(@NonNull CharSequence, @DrawableRes int, @Nullable android.app.PendingIntent); + } + public static final class Notification.TvExtender implements android.app.Notification.Extender { ctor public Notification.TvExtender(); ctor public Notification.TvExtender(android.app.Notification); @@ -914,7 +919,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public int getNavBarModeOverride(); method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean); method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setNavBarModeOverride(int); - method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferReceiverDisplay(int, @NonNull android.media.MediaRoute2Info); + method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferReceiverDisplay(int, @NonNull android.media.MediaRoute2Info, @Nullable android.graphics.drawable.Icon, @Nullable CharSequence); method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferSenderDisplay(int, @NonNull android.media.MediaRoute2Info, @Nullable java.util.concurrent.Executor, @Nullable Runnable); field public static final int MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER = 0; // 0x0 field public static final int MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER = 1; // 0x1 @@ -1303,6 +1308,10 @@ package android.app.admin { field public static final int ERROR_UNKNOWN = 0; // 0x0 } + public class SecurityLog { + method @RequiresPermission(android.Manifest.permission.WRITE_SECURITY_LOG) public static int writeEvent(int, @NonNull java.lang.Object...); + } + public final class SystemUpdatePolicy implements android.os.Parcelable { method public android.app.admin.SystemUpdatePolicy.InstallationOption getInstallationOptionAt(long); field public static final int TYPE_PAUSE = 4; // 0x4 @@ -1643,6 +1652,7 @@ package android.app.cloudsearch { method public int getResultNumber(); method public int getResultOffset(); method @NonNull public android.os.Bundle getSearchConstraints(); + method @NonNull public String getSource(); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final String CONSTRAINT_IS_PRESUBMIT_SUGGESTION = "IS_PRESUBMIT_SUGGESTION"; field public static final String CONSTRAINT_SEARCH_PROVIDER_FILTER = "SEARCH_PROVIDER_FILTER"; @@ -2750,7 +2760,8 @@ package android.companion.virtual { method public void addActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener); method public void addActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener, @NonNull java.util.concurrent.Executor); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); - method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(int, int, int, @Nullable android.view.Surface, int, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback); + method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback); + method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @NonNull java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); @@ -2782,6 +2793,39 @@ package android.companion.virtual { } +package android.companion.virtual.audio { + + public final class AudioCapture { + ctor public AudioCapture(); + method public int getRecordingState(); + method public int read(@NonNull java.nio.ByteBuffer, int); + method public void startRecording(); + method public void stop(); + } + + public final class AudioInjection { + ctor public AudioInjection(); + method public int getPlayState(); + method public void play(); + method public void stop(); + method public int write(@NonNull java.nio.ByteBuffer, int, int); + } + + public final class VirtualAudioDevice implements java.io.Closeable { + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void close(); + method @Nullable public android.companion.virtual.audio.AudioCapture getAudioCapture(); + method @Nullable public android.companion.virtual.audio.AudioInjection getAudioInjection(); + method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.companion.virtual.audio.AudioCapture startAudioCapture(@NonNull android.media.AudioFormat); + method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.companion.virtual.audio.AudioInjection startAudioInjection(@NonNull android.media.AudioFormat); + } + + public static interface VirtualAudioDevice.AudioConfigurationChangeCallback { + method public void onPlaybackConfigChanged(@NonNull java.util.List<android.media.AudioPlaybackConfiguration>); + method public void onRecordingConfigChanged(@NonNull java.util.List<android.media.AudioRecordingConfiguration>); + } + +} + package android.content { public class ApexEnvironment { @@ -2993,6 +3037,7 @@ package android.content.integrity { } public static final class IntegrityFormula.Application { + method @NonNull public static android.content.integrity.IntegrityFormula certificateLineageContains(@NonNull String); method @NonNull public static android.content.integrity.IntegrityFormula certificatesContain(@NonNull String); method @NonNull public static android.content.integrity.IntegrityFormula isPreInstalled(); method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String); @@ -3287,6 +3332,7 @@ package android.content.pm { method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>); field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS"; field public static final String ACTION_REQUEST_PERMISSIONS_FOR_OTHER = "android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER"; + field public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES"; field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES"; field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS"; field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio"; @@ -5136,6 +5182,15 @@ package android.hardware.soundtrigger { field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.Keyphrase> CREATOR; } + public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable { + method public int describeContents(); + method public int getCoarseConfidenceLevel(); + method public int getKeyphraseId(); + method public int getRecognitionModes(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra> CREATOR; + } + public static final class SoundTrigger.KeyphraseSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable { ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int); ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[]); @@ -5986,6 +6041,7 @@ package android.media { public class AudioManager { method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addAssistantServicesUids(@NonNull java.util.List<java.lang.Integer>); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException; method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForCapturePresetChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener) throws java.lang.SecurityException; method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException; @@ -5993,7 +6049,9 @@ package android.media { method public void clearAudioServerStateCallback(); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy); + method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<java.lang.Integer> getActiveAssistantServicesUids(); method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo); + method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<java.lang.Integer> getAssistantServicesUids(); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies(); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups(); method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat); @@ -6018,6 +6076,7 @@ package android.media { method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void registerMuteAwaitConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.MuteAwaitConnectionCallback); method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeAssistantServicesUids(@NonNull java.util.List<java.lang.Integer>); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForCapturePresetChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener); @@ -6025,6 +6084,7 @@ package android.media { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException; method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int requestAudioFocus(@NonNull android.media.AudioFocusRequest, @Nullable android.media.audiopolicy.AudioPolicy); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setActiveAssistantServiceUids(@NonNull java.util.List<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) long); method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int); @@ -6160,6 +6220,10 @@ package android.media { method @NonNull public android.media.HwAudioSource.Builder setAudioDeviceInfo(@NonNull android.media.AudioDeviceInfo); } + public final class MediaCodec { + method @NonNull @RequiresPermission("android.permission.MEDIA_RESOURCE_OVERRIDE_PID") public static android.media.MediaCodec createByCodecNameForClient(@NonNull String, int, int) throws java.io.IOException; + } + public class MediaPlayer implements android.media.AudioRouting android.media.VolumeAutomation { method @RequiresPermission(android.Manifest.permission.BIND_IMS_SERVICE) public void setOnRtpRxNoticeListener(@NonNull android.content.Context, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaPlayer.OnRtpRxNoticeListener); } @@ -6221,6 +6285,9 @@ package android.media { method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getHeadTrackingMode(); method @IntRange(from=0) @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getOutput(); method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<java.lang.Integer> getSupportedHeadTrackingModes(); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public boolean hasHeadTracker(@NonNull android.media.AudioDeviceAttributes); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public boolean isAvailableForDevice(@NonNull android.media.AudioDeviceAttributes); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public boolean isHeadTrackerEnabled(@NonNull android.media.AudioDeviceAttributes); method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void recenterHeadTracker(); method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes); method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeOnHeadTrackingModeChangedListener(@NonNull android.media.Spatializer.OnHeadTrackingModeChangedListener); @@ -6228,6 +6295,7 @@ package android.media { method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setEffectParameter(int, @NonNull byte[]); method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setEnabled(boolean); method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setGlobalTransform(@NonNull float[]); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setHeadTrackerEnabled(boolean, @NonNull android.media.AudioDeviceAttributes); method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnHeadToSoundstagePoseUpdatedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadToSoundstagePoseUpdatedListener); method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnSpatializerOutputChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerOutputChangedListener); field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_DISABLED = -1; // 0xffffffff @@ -6656,7 +6724,7 @@ package android.media.tv { method @NonNull @RequiresPermission(android.Manifest.permission.TIS_EXTENSION_INTERFACE) public java.util.List<java.lang.String> getAvailableExtensionInterfaceNames(@NonNull String); method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String); method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPid(@NonNull String); - method public int getClientPriority(int, @Nullable String); + method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPriority(int, @Nullable String); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos(); method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList(); method @Nullable @RequiresPermission(android.Manifest.permission.TIS_EXTENSION_INTERFACE) public android.os.IBinder getExtensionInterface(@NonNull String, @NonNull String); @@ -8856,7 +8924,7 @@ package android.net.wifi.nl80211 { method @Nullable public android.net.wifi.nl80211.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String); method @NonNull public java.util.List<android.net.wifi.nl80211.NativeScanResult> getScanResults(@NonNull String, int); method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String); - method public boolean notifyCountryCodeChanged(); + method public void notifyCountryCodeChanged(@Nullable String); method @Nullable public static android.net.wifi.nl80211.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]); method @Deprecated public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback); method public boolean registerCountryCodeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangedListener); @@ -9642,7 +9710,7 @@ package android.os { method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles(); 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(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isAdminUser(); - method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isCloneProfile(); + method public boolean isCloneProfile(); method public boolean isCredentialSharedWithParent(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isGuestUser(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int); @@ -11091,7 +11159,8 @@ package android.service.euicc { method public int encodeSmdxSubjectAndReasonCode(@Nullable String, @Nullable String); method @CallSuper public android.os.IBinder onBind(android.content.Intent); method public abstract int onDeleteSubscription(int, String); - method public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @Nullable android.os.Bundle); + method @Deprecated public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @Nullable android.os.Bundle); + method @NonNull public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @NonNull android.os.Bundle); method @Deprecated public int onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean); method @Deprecated public abstract int onEraseSubscriptions(int); method public int onEraseSubscriptions(int, @android.telephony.euicc.EuiccCardManager.ResetOption int); @@ -11710,8 +11779,13 @@ package android.service.voice { public static class AlwaysOnHotwordDetector.EventPayload { method @Nullable public android.os.ParcelFileDescriptor getAudioStream(); method @Nullable public android.media.AudioFormat getCaptureAudioFormat(); + method @Nullable public byte[] getData(); + method public int getDataFormat(); method @Nullable public android.service.voice.HotwordDetectedResult getHotwordDetectedResult(); - method @Nullable public byte[] getTriggerAudio(); + method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra> getKeyphraseRecognitionExtras(); + method @Deprecated @Nullable public byte[] getTriggerAudio(); + field public static final int DATA_FORMAT_RAW = 0; // 0x0 + field public static final int DATA_FORMAT_TRIGGER_AUDIO = 1; // 0x1 } public static final class AlwaysOnHotwordDetector.ModelParamRange { @@ -11782,6 +11856,7 @@ package android.service.voice { } public interface HotwordDetector { + method public default void destroy(); method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(); method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle); method public boolean stopRecognition(); @@ -12327,7 +12402,7 @@ package android.telephony { method public int getNumRtpPacketsReceived(); method public int getNumRtpPacketsTransmitted(); method public int getNumRtpPacketsTransmittedLost(); - method public int getNumRtpSidPacketsRx(); + method public int getNumRtpSidPacketsReceived(); method public int getNumVoiceFrames(); method public int getUplinkCallQualityLevel(); method public boolean isIncomingSilenceDetectedAtCallSetup(); @@ -12347,8 +12422,8 @@ package android.telephony { ctor public CallQuality.Builder(); method @NonNull public android.telephony.CallQuality build(); method @NonNull public android.telephony.CallQuality.Builder setAverageRelativeJitter(int); - method @NonNull public android.telephony.CallQuality.Builder setAverageRoundTripTime(int); - method @NonNull public android.telephony.CallQuality.Builder setCallDuration(int); + method @NonNull public android.telephony.CallQuality.Builder setAverageRoundTripTimeMillis(int); + method @NonNull public android.telephony.CallQuality.Builder setCallDurationMillis(int); method @NonNull public android.telephony.CallQuality.Builder setCodecType(int); method @NonNull public android.telephony.CallQuality.Builder setDownlinkCallQualityLevel(int); method @NonNull public android.telephony.CallQuality.Builder setIncomingSilenceDetectedAtCallSetup(boolean); @@ -12362,7 +12437,7 @@ package android.telephony { method @NonNull public android.telephony.CallQuality.Builder setNumRtpPacketsReceived(int); method @NonNull public android.telephony.CallQuality.Builder setNumRtpPacketsTransmitted(int); method @NonNull public android.telephony.CallQuality.Builder setNumRtpPacketsTransmittedLost(int); - method @NonNull public android.telephony.CallQuality.Builder setNumRtpSidPacketsRx(int); + method @NonNull public android.telephony.CallQuality.Builder setNumRtpSidPacketsReceived(int); method @NonNull public android.telephony.CallQuality.Builder setNumVoiceFrames(int); method @NonNull public android.telephony.CallQuality.Builder setOutgoingSilenceDetectedAtCallSetup(boolean); method @NonNull public android.telephony.CallQuality.Builder setRtpInactivityDetected(boolean); @@ -13831,7 +13906,7 @@ package android.telephony.euicc { field public static final int RESULT_CALLER_NOT_ALLOWED = -3; // 0xfffffffd field public static final int RESULT_EUICC_NOT_FOUND = -2; // 0xfffffffe field public static final int RESULT_OK = 0; // 0x0 - field public static final int RESULT_PROFILE_NOT_FOUND = -4; // 0xfffffffc + field public static final int RESULT_PROFILE_NOT_FOUND = 1; // 0x1 field public static final int RESULT_UNKNOWN_ERROR = -1; // 0xffffffff } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index bcc7e4a59c49..84b393a06af1 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -300,6 +300,9 @@ package android.app { public class Notification implements android.os.Parcelable { method public boolean shouldShowForegroundImmediately(); + field public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice"; + field public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon"; + field public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent"; } public final class NotificationChannel implements android.os.Parcelable { @@ -349,6 +352,7 @@ package android.app { public final class PictureInPictureParams implements android.os.Parcelable { method public java.util.List<android.app.RemoteAction> getActions(); method public float getAspectRatio(); + method public float getExpandedAspectRatio(); method public android.graphics.Rect getSourceRectHint(); method public boolean isSeamlessResizeEnabled(); } @@ -483,6 +487,7 @@ package android.app { package android.app.admin { public class DevicePolicyManager { + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void acknowledgeNewUserDisclaimer(); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void clearOrganizationId(); method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord(); method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs(); @@ -601,6 +606,14 @@ package android.app.blob { } +package android.app.cloudsearch { + + public static final class SearchRequest.Builder { + method @NonNull public android.app.cloudsearch.SearchRequest.Builder setSource(@NonNull String); + } + +} + package android.app.contentsuggestions { public final class ContentSuggestionsManager { @@ -1276,6 +1289,10 @@ package android.hardware.soundtrigger { field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.KeyphraseMetadata> CREATOR; } + public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable { + ctor public SoundTrigger.KeyphraseRecognitionExtra(int, int, int); + } + public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable { ctor public SoundTrigger.ModelParamRange(int, int); } @@ -1607,10 +1624,6 @@ package android.media.tv.tuner { package android.net { - public class EthernetManager { - method public void setIncludeTestInterfaces(boolean); - } - public class NetworkPolicyManager { method public boolean getRestrictBackground(); method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidNetworkingBlocked(int, boolean); @@ -1753,6 +1766,7 @@ package android.os { public class Process { method public static final int getThreadScheduler(int) throws java.lang.IllegalArgumentException; + method public static final int toSupplementalUid(int); field public static final int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000; // 0x15f90 field public static final int FIRST_ISOLATED_UID = 99000; // 0x182b8 field public static final int LAST_APP_ZYGOTE_ISOLATED_UID = 98999; // 0x182b7 @@ -1954,6 +1968,7 @@ package android.os.storage { method public long computeStorageCacheBytes(@NonNull java.io.File); method @NonNull public static java.util.UUID convert(@NonNull String); method @NonNull public static String convert(@NonNull java.util.UUID); + method @Nullable public String getCloudMediaProvider(); method public boolean isAppIoBlocked(@NonNull java.util.UUID, int, int, int); method public static boolean isUserKeyUnlocked(int); field public static final String CACHE_RESERVE_PERCENT_HIGH_KEY = "cache_reserve_percent_high"; @@ -2064,7 +2079,7 @@ package android.permission { method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(); method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(boolean); method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource); - method public void revokePostNotificationPermissionWithoutKillForTest(@NonNull String, int); + method @RequiresPermission(android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL) public void revokePostNotificationPermissionWithoutKillForTest(@NonNull String, int); } } @@ -2401,6 +2416,19 @@ package android.service.voice { method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public void triggerHardwareRecognitionEventForTest(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[]); } + public static final class AlwaysOnHotwordDetector.EventPayload.Builder { + ctor public AlwaysOnHotwordDetector.EventPayload.Builder(); + method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload build(); + method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setAudioStream(@NonNull android.os.ParcelFileDescriptor); + method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setCaptureAudioFormat(@NonNull android.media.AudioFormat); + method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setCaptureAvailable(boolean); + method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setCaptureSession(int); + method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setData(@NonNull byte[]); + method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setDataFormat(int); + method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setHotwordDetectedResult(@NonNull android.service.voice.HotwordDetectedResult); + method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setKeyphraseRecognitionExtras(@NonNull java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>); + } + public final class VisibleActivityInfo implements android.os.Parcelable { ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder); } diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index cf3ca20315d8..c82f5f6d54fc 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -1008,14 +1008,6 @@ public abstract class AccessibilityService extends Service { * is currently touching or the window with input focus, if the user is not * touching any window. It could be from any logical display. * <p> - * The currently active window is defined as the window that most recently fired one - * of the following events: - * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}, - * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}, - * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}. - * In other words, the last window shown that also has input focus. - * </p> - * <p> * <strong>Note:</strong> In order to access the root node your service has * to declare the capability to retrieve window content by setting the * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent} @@ -1023,10 +1015,29 @@ public abstract class AccessibilityService extends Service { * </p> * * @return The root node if this service can retrieve window content. + * @see AccessibilityWindowInfo#isActive() for more explanation about the active window. */ public AccessibilityNodeInfo getRootInActiveWindow() { + return getRootInActiveWindow(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID); + } + + /** + * Gets the root node in the currently active window if this service + * can retrieve window content. The active window is the one that the user + * is currently touching or the window with input focus, if the user is not + * touching any window. It could be from any logical display. + * + * @param prefetchingStrategy the prefetching strategy. + * @return The root node if this service can retrieve window content. + * + * @see #getRootInActiveWindow() + * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching. + */ + @Nullable + public AccessibilityNodeInfo getRootInActiveWindow( + @AccessibilityNodeInfo.PrefetchingStrategy int prefetchingStrategy) { return AccessibilityInteractionClient.getInstance(this).getRootInActiveWindow( - mConnectionId); + mConnectionId, prefetchingStrategy); } /** diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 85e78540332f..8af68d7c5762 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -569,6 +569,11 @@ public class AccessibilityServiceInfo implements Parcelable { private String mNonLocalizedSummary; /** + * Resource id of the intro of the accessibility service. + */ + private int mIntroResId; + + /** * Resource id of the description of the accessibility service. */ private int mDescriptionResId; @@ -737,6 +742,11 @@ public class AccessibilityServiceInfo implements Parcelable { R.styleable.AccessibilityService_isAccessibilityTool, false); mTileServiceClassName = asAttributes.getString( com.android.internal.R.styleable.AccessibilityService_tileService); + peekedValue = asAttributes.peekValue( + com.android.internal.R.styleable.AccessibilityService_intro); + if (peekedValue != null) { + mIntroResId = peekedValue.resourceId; + } asAttributes.recycle(); } catch (NameNotFoundException e) { throw new XmlPullParserException( "Unable to create context for: " @@ -957,6 +967,29 @@ public class AccessibilityServiceInfo implements Parcelable { } /** + * The localized intro of the accessibility service. + * <p> + * <strong>Statically set from + * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> + * </p> + * @return The localized intro if available, and {@code null} if a intro + * has not been provided. + */ + @Nullable + public CharSequence loadIntro(@NonNull PackageManager packageManager) { + if (mIntroResId == /* invalid */ 0) { + return null; + } + ServiceInfo serviceInfo = mResolveInfo.serviceInfo; + CharSequence intro = packageManager.getText(serviceInfo.packageName, + mIntroResId, serviceInfo.applicationInfo); + if (intro != null) { + return intro.toString().trim(); + } + return null; + } + + /** * Gets the non-localized description of the accessibility service. * <p> * <strong>Statically set from @@ -1114,6 +1147,7 @@ public class AccessibilityServiceInfo implements Parcelable { parcel.writeString(mNonLocalizedDescription); parcel.writeBoolean(mIsAccessibilityTool); parcel.writeString(mTileServiceClassName); + parcel.writeInt(mIntroResId); } private void initFromParcel(Parcel parcel) { @@ -1137,6 +1171,7 @@ public class AccessibilityServiceInfo implements Parcelable { mNonLocalizedDescription = parcel.readString(); mIsAccessibilityTool = parcel.readBoolean(); mTileServiceClassName = parcel.readString(); + mIntroResId = parcel.readInt(); } @Override diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java index 52a1cad7fd88..9a732199ed82 100644 --- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java @@ -70,6 +70,11 @@ public final class AccessibilityShortcutInfo { private final ActivityInfo mActivityInfo; /** + * Resource id of the intro of the accessibility shortcut target. + */ + private final int mIntroResId; + + /** * Resource id of the summary of the accessibility shortcut target. */ private final int mSummaryResId; @@ -160,6 +165,9 @@ public final class AccessibilityShortcutInfo { // Get tile service class name mTileServiceClassName = asAttributes.getString( com.android.internal.R.styleable.AccessibilityShortcutTarget_tileService); + // Gets intro + mIntroResId = asAttributes.getResourceId( + com.android.internal.R.styleable.AccessibilityShortcutTarget_intro, 0); asAttributes.recycle(); if ((mDescriptionResId == 0 && mHtmlDescriptionRes == 0) || mSummaryResId == 0) { @@ -203,6 +211,16 @@ public final class AccessibilityShortcutInfo { } /** + * The localized intro of the accessibility shortcut target. + * + * @return The localized intro. + */ + @Nullable + public String loadIntro(@NonNull PackageManager packageManager) { + return loadResourceString(packageManager, mActivityInfo, mIntroResId); + } + + /** * The localized description of the accessibility shortcut target. * * @return The localized description. diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index a31aa28208dd..983dde3beda0 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -47,7 +47,9 @@ import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.ActivityNotFoundException; +import android.content.ComponentCallbacks; import android.content.ComponentCallbacks2; +import android.content.ComponentCallbacksController; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -982,6 +984,8 @@ public class Activity extends ContextThemeWrapper @Nullable private DumpableContainerImpl mDumpableContainer; + private ComponentCallbacksController mCallbacksController; + private final WindowControllerCallback mWindowControllerCallback = new WindowControllerCallback() { /** @@ -1323,6 +1327,28 @@ public class Activity extends ContextThemeWrapper } } + @Override + public void registerComponentCallbacks(ComponentCallbacks callback) { + if (CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS) + && mCallbacksController == null) { + mCallbacksController = new ComponentCallbacksController(); + } + if (mCallbacksController != null) { + mCallbacksController.registerCallbacks(callback); + } else { + super.registerComponentCallbacks(callback); + } + } + + @Override + public void unregisterComponentCallbacks(ComponentCallbacks callback) { + if (mCallbacksController != null) { + mCallbacksController.unregisterCallbacks(callback); + } else { + super.unregisterComponentCallbacks(callback); + } + } + private void dispatchActivityPreCreated(@Nullable Bundle savedInstanceState) { getApplication().dispatchActivityPreCreated(this, savedInstanceState); Object[] callbacks = collectActivityLifecycleCallbacks(); @@ -2668,9 +2694,12 @@ public class Activity extends ContextThemeWrapper if (mUiTranslationController != null) { mUiTranslationController.onActivityDestroyed(); } - if (mDefaultBackCallback != null) { getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback); + mDefaultBackCallback = null; + } + if (mCallbacksController != null) { + mCallbacksController.clearCallbacks(); } } @@ -2991,6 +3020,9 @@ public class Activity extends ContextThemeWrapper } dispatchActivityConfigurationChanged(); + if (mCallbacksController != null) { + mCallbacksController.dispatchConfigurationChanged(newConfig); + } } /** @@ -3162,12 +3194,18 @@ public class Activity extends ContextThemeWrapper if (DEBUG_LIFECYCLE) Slog.v(TAG, "onLowMemory " + this); mCalled = true; mFragments.dispatchLowMemory(); + if (mCallbacksController != null) { + mCallbacksController.dispatchLowMemory(); + } } public void onTrimMemory(int level) { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onTrimMemory " + this + ": " + level); mCalled = true; mFragments.dispatchTrimMemory(level); + if (mCallbacksController != null) { + mCallbacksController.dispatchTrimMemory(level); + } } /** diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index cce7dd338b3d..294621ee07f8 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -215,6 +215,14 @@ public abstract class ActivityManagerInternal { public abstract boolean isSystemReady(); /** + * Returns package name given pid. + * + * @param pid The pid we are searching package name for. + */ + @Nullable + public abstract String getPackageNameByPid(int pid); + + /** * Sets if the given pid has an overlay UI or not. * * @param pid The pid we are setting overlay UI for. @@ -771,6 +779,16 @@ public abstract class ActivityManagerInternal { * @param started {@code true} if the process transits from non-FGS state to FGS state. */ void onForegroundServiceStateChanged(String packageName, int uid, int pid, boolean started); + + /** + * Call when the notification of the foreground service is updated. + * + * @param packageName The package name of the process. + * @param uid The UID of the process. + * @param foregroundId The current foreground service notification ID, a negative value + * means this notification is being removed. + */ + void onForegroundServiceNotificationUpdated(String packageName, int uid, int foregroundId); } /** diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index e405b60c8daa..50121218cb4e 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -1121,7 +1121,7 @@ public class ActivityOptions extends ComponentOptions { mPackageName = opts.getString(KEY_PACKAGE_NAME); try { - mUsageTimeReport = opts.getParcelable(KEY_USAGE_TIME_REPORT); + mUsageTimeReport = opts.getParcelable(KEY_USAGE_TIME_REPORT, PendingIntent.class); } catch (RuntimeException e) { Slog.w(TAG, e); } @@ -1429,7 +1429,6 @@ public class ActivityOptions extends ComponentOptions { /** * Gets the style can be used for cold-launching an activity. * @see #setSplashScreenStyle(int) - * @hide */ public @SplashScreen.SplashScreenStyle int getSplashScreenStyle() { return mSplashScreenStyle; diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index ec2115c2baed..a3dd705a7e29 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2900,6 +2900,14 @@ class ContextImpl extends Context { } } + /** + * @hide + */ + @Override + public int getAssociatedDisplayId() { + return isAssociatedWithDisplay() ? getDisplayId() : Display.INVALID_DISPLAY; + } + @Override public Display getDisplayNoVerify() { if (mDisplay == null) { diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 208477588885..aa6c1842ddec 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -465,6 +465,7 @@ public class Dialog implements DialogInterface, Window.Callback, } }; getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback); + mDefaultBackCallback = null; } } diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java index 9fea3f75c2c2..cadbf23d1963 100644 --- a/core/java/android/app/DialogFragment.java +++ b/core/java/android/app/DialogFragment.java @@ -140,7 +140,7 @@ import java.io.PrintWriter; * embed} * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> - * {@link android.support.v4.app.DialogFragment} for consistent behavior across all devices + * {@link androidx.fragment.app.DialogFragment} for consistent behavior across all devices * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>. */ @Deprecated diff --git a/core/java/android/app/FragmentContainer.java b/core/java/android/app/FragmentContainer.java index 536c866083d7..ff9dbcb3fd03 100644 --- a/core/java/android/app/FragmentContainer.java +++ b/core/java/android/app/FragmentContainer.java @@ -26,7 +26,7 @@ import android.view.View; * Callbacks to a {@link Fragment}'s container. * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> - * {@link android.support.v4.app.FragmentContainer}. + * {@link androidx.fragment.app.FragmentContainer}. */ @Deprecated public abstract class FragmentContainer { diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java index 150b7a56a36d..3c8d7609eedf 100644 --- a/core/java/android/app/FragmentController.java +++ b/core/java/android/app/FragmentController.java @@ -41,7 +41,7 @@ import java.util.List; * The methods provided by {@link FragmentController} are for that purpose. * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> - * {@link android.support.v4.app.FragmentController} + * {@link androidx.fragment.app.FragmentController} */ @Deprecated public class FragmentController { diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java index 9e887b88c407..6cfdc533d652 100644 --- a/core/java/android/app/FragmentHostCallback.java +++ b/core/java/android/app/FragmentHostCallback.java @@ -40,7 +40,7 @@ import java.io.PrintWriter; * applicable to the host. * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> - * {@link android.support.v4.app.FragmentHostCallback} + * {@link androidx.fragment.app.FragmentHostCallback} */ @Deprecated public abstract class FragmentHostCallback<E> extends FragmentContainer { diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 5435558b3be5..f8f846de1e6c 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -72,12 +72,12 @@ import java.util.concurrent.CopyOnWriteArrayList; * While the FragmentManager API was introduced in * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, a version of the API * at is also available for use on older platforms through - * {@link android.support.v4.app.FragmentActivity}. See the blog post + * {@link androidx.fragment.app.FragmentActivity}. See the blog post * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html"> * Fragments For All</a> for more details. * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> - * {@link android.support.v4.app.FragmentManager} for consistent behavior across all devices + * {@link androidx.fragment.app.FragmentManager} for consistent behavior across all devices * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>. */ @Deprecated @@ -94,7 +94,7 @@ public abstract class FragmentManager { * will be persisted across activity instances. * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html"> - * Support Library</a> {@link android.support.v4.app.FragmentManager.BackStackEntry} + * Support Library</a> {@link androidx.fragment.app.FragmentManager.BackStackEntry} */ @Deprecated public interface BackStackEntry { @@ -142,7 +142,7 @@ public abstract class FragmentManager { * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html"> * Support Library</a> - * {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener} + * {@link androidx.fragment.app.FragmentManager.OnBackStackChangedListener} */ @Deprecated public interface OnBackStackChangedListener { @@ -446,7 +446,7 @@ public abstract class FragmentManager { * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html"> * Support Library</a> - * {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks} + * {@link androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks} */ @Deprecated public abstract static class FragmentLifecycleCallbacks { diff --git a/core/java/android/app/FragmentManagerNonConfig.java b/core/java/android/app/FragmentManagerNonConfig.java index 326438af1bd3..ae7fd640a800 100644 --- a/core/java/android/app/FragmentManagerNonConfig.java +++ b/core/java/android/app/FragmentManagerNonConfig.java @@ -29,7 +29,7 @@ import java.util.List; * {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p> * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> - * {@link android.support.v4.app.FragmentManagerNonConfig} + * {@link androidx.fragment.app.FragmentManagerNonConfig} */ @Deprecated public class FragmentManagerNonConfig { diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java index 713a559afdc8..34c492853c9d 100644 --- a/core/java/android/app/FragmentTransaction.java +++ b/core/java/android/app/FragmentTransaction.java @@ -23,7 +23,7 @@ import java.lang.annotation.RetentionPolicy; * </div> * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> - * {@link android.support.v4.app.FragmentTransaction} + * {@link androidx.fragment.app.FragmentTransaction} */ @Deprecated public abstract class FragmentTransaction { diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index cc8b182f3e41..490afc13ee97 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -128,6 +128,7 @@ interface IActivityTaskManager { int callingUid, in Intent intent, in String resolvedType, in IVoiceInteractionSession session, in IVoiceInteractor interactor, int flags, in ProfilerInfo profilerInfo, in Bundle options, int userId); + String getVoiceInteractorPackageName(in IBinder callingVoiceInteractor); int startAssistantActivity(in String callingPackage, in String callingFeatureId, int callingPid, int callingUid, in Intent intent, in String resolvedType, in Bundle options, int userId); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY)") diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java index 5de0d69248c8..2e833084641c 100644 --- a/core/java/android/app/IntentService.java +++ b/core/java/android/app/IntentService.java @@ -52,7 +52,7 @@ import android.os.Message; * guide.</p> * </div> * - * @see android.support.v4.app.JobIntentService + * @see androidx.core.app.JobIntentService * * @deprecated IntentService is subject to all the * <a href="{@docRoot}about/versions/oreo/background.html">background execution limits</a> diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 5d1f4df4359e..99100003e5b3 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -591,7 +591,7 @@ public class KeyguardManager { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public boolean isDeviceLocked(int userId) { try { - return mTrustManager.isDeviceLocked(userId); + return mTrustManager.isDeviceLocked(userId, mContext.getAssociatedDisplayId()); } catch (RemoteException e) { return false; } @@ -617,7 +617,7 @@ public class KeyguardManager { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isDeviceSecure(int userId) { try { - return mTrustManager.isDeviceSecure(userId); + return mTrustManager.isDeviceSecure(userId, mContext.getAssociatedDisplayId()); } catch (RemoteException e) { return false; } diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java index 7790f70b4f25..b6d80cae34e3 100644 --- a/core/java/android/app/ListFragment.java +++ b/core/java/android/app/ListFragment.java @@ -146,7 +146,7 @@ import android.widget.TextView; * @see android.widget.ListView * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> - * {@link android.support.v4.app.ListFragment} for consistent behavior across all devices + * {@link androidx.fragment.app.ListFragment} for consistent behavior across all devices * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>. */ @Deprecated diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java index 86d0fd6272d2..e2de716d7e74 100644 --- a/core/java/android/app/LoaderManager.java +++ b/core/java/android/app/LoaderManager.java @@ -37,7 +37,7 @@ import java.lang.reflect.Modifier; * While the LoaderManager API was introduced in * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, a version of the API * at is also available for use on older platforms through - * {@link android.support.v4.app.FragmentActivity}. See the blog post + * {@link androidx.fragment.app.FragmentActivity}. See the blog post * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html"> * Fragments For All</a> for more details. * @@ -56,7 +56,7 @@ import java.lang.reflect.Modifier; * </div> * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> - * {@link android.support.v4.app.LoaderManager} + * {@link androidx.loader.app.LoaderManager} */ @Deprecated public abstract class LoaderManager { @@ -64,7 +64,7 @@ public abstract class LoaderManager { * Callback interface for a client to interact with the manager. * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html"> - * Support Library</a> {@link android.support.v4.app.LoaderManager.LoaderCallbacks} + * Support Library</a> {@link androidx.loader.app.LoaderManager.LoaderCallbacks} */ @Deprecated public interface LoaderCallbacks<D> { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 6a147720f8e9..9dd206e21c44 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1330,6 +1330,32 @@ public class Notification implements Parcelable public static final String EXTRA_MEDIA_SESSION = "android.mediaSession"; /** + * {@link #extras} key: A {@code CharSequence} name of a remote device used for a media session + * associated with a {@link Notification.MediaStyle} notification. This will show in the media + * controls output switcher instead of the local device name. + * @hide + */ + @TestApi + public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice"; + + /** + * {@link #extras} key: A {@code int} resource ID for an icon that should show in the output + * switcher of the media controls for a {@link Notification.MediaStyle} notification. + * @hide + */ + @TestApi + public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon"; + + /** + * {@link #extras} key: A {@code PendingIntent} that will replace the default action for the + * media controls output switcher chip, associated with a {@link Notification.MediaStyle} + * notification. This should launch an activity. + * @hide + */ + @TestApi + public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent"; + + /** * {@link #extras} key: the indices of actions to be shown in the compact view, * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}. */ @@ -3719,7 +3745,7 @@ public class Notification implements Parcelable * Provides a convenient way to set the various fields of a {@link Notification} and generate * content views using the platform's notification layout template. If your app supports * versions of Android as old as API level 4, you can instead use - * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}, + * {@link androidx.core.app.NotificationCompat.Builder NotificationCompat.Builder}, * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support * library</a>. * @@ -8943,6 +8969,9 @@ public class Notification implements Parcelable private int[] mActionsToShowInCompact = null; private MediaSession.Token mToken; + private CharSequence mDeviceName; + private int mDeviceIcon; + private PendingIntent mDeviceIntent; public MediaStyle() { } @@ -8976,6 +9005,32 @@ public class Notification implements Parcelable } /** + * For media notifications associated with playback on a remote device, provide device + * information that will replace the default values for the output switcher chip on the + * media control, as well as an intent to use when the output switcher chip is tapped, + * on devices where this is supported. + * + * @param deviceName The name of the remote device to display + * @param iconResource Icon resource representing the device + * @param chipIntent PendingIntent to send when the output switcher is tapped. May be + * {@code null}, in which case the output switcher will be disabled. + * This intent should open an Activity or it will be ignored. + * @return MediaStyle + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) + @NonNull + public MediaStyle setRemotePlaybackInfo(@NonNull CharSequence deviceName, + @DrawableRes int iconResource, @Nullable PendingIntent chipIntent) { + mDeviceName = deviceName; + mDeviceIcon = iconResource; + mDeviceIntent = chipIntent; + return this; + } + + /** * @hide */ @Override @@ -9023,6 +9078,15 @@ public class Notification implements Parcelable if (mActionsToShowInCompact != null) { extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact); } + if (mDeviceName != null) { + extras.putCharSequence(EXTRA_MEDIA_REMOTE_DEVICE, mDeviceName); + } + if (mDeviceIcon > 0) { + extras.putInt(EXTRA_MEDIA_REMOTE_ICON, mDeviceIcon); + } + if (mDeviceIntent != null) { + extras.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, mDeviceIntent); + } } /** @@ -9038,6 +9102,15 @@ public class Notification implements Parcelable if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) { mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS); } + if (extras.containsKey(EXTRA_MEDIA_REMOTE_DEVICE)) { + mDeviceName = extras.getCharSequence(EXTRA_MEDIA_REMOTE_DEVICE); + } + if (extras.containsKey(EXTRA_MEDIA_REMOTE_ICON)) { + mDeviceIcon = extras.getInt(EXTRA_MEDIA_REMOTE_ICON); + } + if (extras.containsKey(EXTRA_MEDIA_REMOTE_INTENT)) { + mDeviceIntent = extras.getParcelable(EXTRA_MEDIA_REMOTE_INTENT); + } } /** diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java index b40fbee08049..eadb7e330c84 100644 --- a/core/java/android/app/NotificationHistory.java +++ b/core/java/android/app/NotificationHistory.java @@ -610,6 +610,7 @@ public final class NotificationHistory implements Parcelable { // Data can be too large for a transact. Write the data as a Blob, which will be written to // ashmem if too large. dest.writeBlob(data.marshall()); + data.recycle(); } public static final @NonNull Creator<NotificationHistory> CREATOR diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java index 358ce6a83a21..18343fddf5d3 100644 --- a/core/java/android/app/PictureInPictureParams.java +++ b/core/java/android/app/PictureInPictureParams.java @@ -18,7 +18,9 @@ package android.app; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresFeature; import android.annotation.TestApi; +import android.content.pm.PackageManager; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; @@ -43,6 +45,9 @@ public final class PictureInPictureParams implements Parcelable { private Rational mAspectRatio; @Nullable + private Rational mExpandedAspectRatio; + + @Nullable private List<RemoteAction> mUserActions; @Nullable @@ -67,6 +72,24 @@ public final class PictureInPictureParams implements Parcelable { } /** + * Sets the aspect ratio for the expanded picture-in-picture mode. The aspect ratio is + * defined as the desired width / height. <br/> + * The aspect ratio cannot be changed from horizontal to vertical or vertical to horizontal + * while the PIP is shown. Any such changes will be ignored. <br/> + * + * Setting the expanded ratio shows the activity's support for expanded mode. + * + * @param expandedAspectRatio must not be between 2.39:1 and 1:2.39 (inclusive). If {@code + * null}, expanded picture-in-picture mode is not supported. + * @return this builder instance. + */ + @RequiresFeature(PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE) + public @NonNull Builder setExpandedAspectRatio(@Nullable Rational expandedAspectRatio) { + mExpandedAspectRatio = expandedAspectRatio; + return this; + } + + /** * Sets the user actions. If there are more than * {@link Activity#getMaxNumPictureInPictureActions()} actions, then the input list * will be truncated to that number. @@ -152,7 +175,8 @@ public final class PictureInPictureParams implements Parcelable { * @see Activity#setPictureInPictureParams(PictureInPictureParams) */ public PictureInPictureParams build() { - PictureInPictureParams params = new PictureInPictureParams(mAspectRatio, mUserActions, + PictureInPictureParams params = new PictureInPictureParams(mAspectRatio, + mExpandedAspectRatio, mUserActions, mSourceRectHint, mAutoEnterEnabled, mSeamlessResizeEnabled); return params; } @@ -165,6 +189,12 @@ public final class PictureInPictureParams implements Parcelable { private Rational mAspectRatio; /** + * The expected aspect ratio of the vertically expanded picture-in-picture window. + */ + @Nullable + private Rational mExpandedAspectRatio; + + /** * The set of actions that are associated with this activity when in picture-in-picture. */ @Nullable @@ -197,9 +227,8 @@ public final class PictureInPictureParams implements Parcelable { /** {@hide} */ PictureInPictureParams(Parcel in) { - if (in.readInt() != 0) { - mAspectRatio = new Rational(in.readInt(), in.readInt()); - } + mAspectRatio = readRationalFromParcel(in); + mExpandedAspectRatio = readRationalFromParcel(in); if (in.readInt() != 0) { mUserActions = new ArrayList<>(); in.readTypedList(mUserActions, RemoteAction.CREATOR); @@ -216,9 +245,11 @@ public final class PictureInPictureParams implements Parcelable { } /** {@hide} */ - PictureInPictureParams(Rational aspectRatio, List<RemoteAction> actions, - Rect sourceRectHint, Boolean autoEnterEnabled, Boolean seamlessResizeEnabled) { + PictureInPictureParams(Rational aspectRatio, Rational expandedAspectRatio, + List<RemoteAction> actions, Rect sourceRectHint, Boolean autoEnterEnabled, + Boolean seamlessResizeEnabled) { mAspectRatio = aspectRatio; + mExpandedAspectRatio = expandedAspectRatio; mUserActions = actions; mSourceRectHint = sourceRectHint; mAutoEnterEnabled = autoEnterEnabled; @@ -230,7 +261,7 @@ public final class PictureInPictureParams implements Parcelable { * @hide */ public PictureInPictureParams(PictureInPictureParams other) { - this(other.mAspectRatio, other.mUserActions, + this(other.mAspectRatio, other.mExpandedAspectRatio, other.mUserActions, other.hasSourceBoundsHint() ? new Rect(other.getSourceRectHint()) : null, other.mAutoEnterEnabled, other.mSeamlessResizeEnabled); } @@ -243,6 +274,10 @@ public final class PictureInPictureParams implements Parcelable { if (otherArgs.hasSetAspectRatio()) { mAspectRatio = otherArgs.mAspectRatio; } + + // Copy either way because null can be used to explicitly unset the value + mExpandedAspectRatio = otherArgs.mExpandedAspectRatio; + if (otherArgs.hasSetActions()) { mUserActions = otherArgs.mUserActions; } @@ -283,6 +318,26 @@ public final class PictureInPictureParams implements Parcelable { } /** + * @return the expanded aspect ratio. If none is set, return 0. + * @hide + */ + @TestApi + public float getExpandedAspectRatio() { + if (mExpandedAspectRatio != null) { + return mExpandedAspectRatio.floatValue(); + } + return 0f; + } + + /** + * @return whether the expanded aspect ratio is set + * @hide + */ + public boolean hasSetExpandedAspectRatio() { + return mExpandedAspectRatio != null; + } + + /** * @return the set of user actions. * @hide */ @@ -349,7 +404,8 @@ public final class PictureInPictureParams implements Parcelable { */ public boolean empty() { return !hasSourceBoundsHint() && !hasSetActions() && !hasSetAspectRatio() - && mAutoEnterEnabled != null && mSeamlessResizeEnabled != null; + && !hasSetExpandedAspectRatio() && mAutoEnterEnabled != null + && mSeamlessResizeEnabled != null; } @Override @@ -360,13 +416,14 @@ public final class PictureInPictureParams implements Parcelable { return Objects.equals(mAutoEnterEnabled, that.mAutoEnterEnabled) && Objects.equals(mSeamlessResizeEnabled, that.mSeamlessResizeEnabled) && Objects.equals(mAspectRatio, that.mAspectRatio) + && Objects.equals(mExpandedAspectRatio, that.mExpandedAspectRatio) && Objects.equals(mUserActions, that.mUserActions) && Objects.equals(mSourceRectHint, that.mSourceRectHint); } @Override public int hashCode() { - return Objects.hash(mAspectRatio, mUserActions, mSourceRectHint, + return Objects.hash(mAspectRatio, mExpandedAspectRatio, mUserActions, mSourceRectHint, mAutoEnterEnabled, mSeamlessResizeEnabled); } @@ -377,13 +434,8 @@ public final class PictureInPictureParams implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - if (mAspectRatio != null) { - out.writeInt(1); - out.writeInt(mAspectRatio.getNumerator()); - out.writeInt(mAspectRatio.getDenominator()); - } else { - out.writeInt(0); - } + writeRationalToParcel(mAspectRatio, out); + writeRationalToParcel(mExpandedAspectRatio, out); if (mUserActions != null) { out.writeInt(1); out.writeTypedList(mUserActions, 0); @@ -410,10 +462,28 @@ public final class PictureInPictureParams implements Parcelable { } } + private void writeRationalToParcel(Rational rational, Parcel out) { + if (rational != null) { + out.writeInt(1); + out.writeInt(rational.getNumerator()); + out.writeInt(rational.getDenominator()); + } else { + out.writeInt(0); + } + } + + private Rational readRationalFromParcel(Parcel in) { + if (in.readInt() != 0) { + return new Rational(in.readInt(), in.readInt()); + } + return null; + } + @Override public String toString() { return "PictureInPictureParams(" + " aspectRatio=" + getAspectRatioRational() + + " expandedAspectRatio=" + mExpandedAspectRatio + " sourceRectHint=" + getSourceRectHint() + " hasSetActions=" + hasSetActions() + " isAutoPipEnabled=" + isAutoEnterEnabled() diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index da1ba5265c4a..5f0034285f0a 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -920,7 +920,7 @@ public class StatusBarManager { public void setNavBarModeOverride(@NavBarModeOverride int navBarModeOverride) { if (navBarModeOverride != NAV_BAR_MODE_OVERRIDE_NONE && navBarModeOverride != NAV_BAR_MODE_OVERRIDE_KIDS) { - throw new UnsupportedOperationException( + throw new IllegalArgumentException( "Supplied navBarModeOverride not supported: " + navBarModeOverride); } @@ -1012,6 +1012,8 @@ public class StatusBarManager { * * @param displayState the new state for media tap-to-transfer. * @param routeInfo the media route information for the media being transferred. + * @param appIcon the icon of the app playing the media. + * @param appName the name of the app playing the media. * * @hide */ @@ -1019,11 +1021,13 @@ public class StatusBarManager { @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferReceiverDisplay( @MediaTransferReceiverState int displayState, - @NonNull MediaRoute2Info routeInfo) { + @NonNull MediaRoute2Info routeInfo, + @Nullable Icon appIcon, + @Nullable CharSequence appName) { Objects.requireNonNull(routeInfo); IStatusBarService svc = getService(); try { - svc.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo); + svc.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo, appIcon, appName); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 2af8905fa3af..5e521b03f6e3 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -733,7 +733,8 @@ public final class UiAutomation { } // Calling out without a lock held. return AccessibilityInteractionClient.getInstance() - .getRootInActiveWindow(connectionId); + .getRootInActiveWindow(connectionId, + AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID); } /** diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java index 73a9e5a221c7..cfaffb11a591 100644 --- a/core/java/android/app/UiModeManager.java +++ b/core/java/android/app/UiModeManager.java @@ -517,7 +517,7 @@ public class UiModeManager { * (and potentially an Activity lifecycle event) being applied to all running apps. * Developers interested in an app-local implementation of night mode should consider using * {@link #setApplicationNightMode(int)} to set and persist the -night qualifier locally or - * {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} for the + * {@link androidx.appcompat.app.AppCompatDelegate#setDefaultNightMode(int)} for the * backward compatible implementation. * * @param mode the night mode to set @@ -595,7 +595,7 @@ public class UiModeManager { * user clears the data for the application, or this application is uninstalled. * <p> * Developers interested in a non-persistent app-local implementation of night mode should - * consider using {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} + * consider using {@link androidx.appcompat.app.AppCompatDelegate#setDefaultNightMode(int)} * to manage the -night qualifier locally. * * @param mode the night mode to set diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java index 433b275d4759..7014d6963538 100644 --- a/core/java/android/app/VoiceInteractor.java +++ b/core/java/android/app/VoiceInteractor.java @@ -1168,6 +1168,23 @@ public final class VoiceInteractor { } } + /** + * @return the package name of the service providing the VoiceInteractionService. + */ + @NonNull + public String getPackageName() { + String packageName = null; + if (mActivity != null && mInteractor != null) { + try { + packageName = ActivityTaskManager.getService() + .getVoiceInteractorPackageName(mInteractor.asBinder()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return packageName == null ? "" : packageName; + } + void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { String innerPrefix = prefix + " "; if (mActiveRequests.size() > 0) { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index e52ae5186180..9a7093ec1806 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3703,8 +3703,9 @@ public class DevicePolicyManager { */ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @UserHandleAware + @TestApi + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public void acknowledgeNewUserDisclaimer() { if (mService != null) { try { @@ -8306,10 +8307,10 @@ public class DevicePolicyManager { * @hide */ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) - public void reportPasswordChanged(@UserIdInt int userId) { + public void reportPasswordChanged(PasswordMetrics metrics, @UserIdInt int userId) { if (mService != null) { try { - mService.reportPasswordChanged(userId); + mService.reportPasswordChanged(metrics, userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -9713,9 +9714,9 @@ public class DevicePolicyManager { * service. When zero or more packages have been added, accessibility services that are not in * the list and not part of the system can not be enabled by the user. * <p> - * Calling with a null value for the list disables the restriction so that all services can be - * used, calling with an empty list only allows the built-in system services. Any non-system - * accessibility service that's currently enabled must be included in the list. + * Calling with a {@code null} value for the list disables the restriction so that all services + * can be used, calling with an empty list only allows the built-in system services. Any + * non-system accessibility service that's currently enabled must be included in the list. * <p> * System accessibility services are always available to the user and this method can't * disable them. @@ -9741,8 +9742,8 @@ public class DevicePolicyManager { /** * Returns the list of permitted accessibility services set by this device or profile owner. * <p> - * An empty list means no accessibility services except system services are allowed. Null means - * all accessibility services are allowed. + * An empty list means no accessibility services except system services are allowed. + * {@code null} means all accessibility services are allowed. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @return List of accessiblity service package names. @@ -9787,7 +9788,7 @@ public class DevicePolicyManager { * Returns the list of accessibility services permitted by the device or profiles * owners of this user. * - * <p>Null means all accessibility services are allowed, if a non-null list is returned + * <p>{@code null} means all accessibility services are allowed, if a non-null list is returned * it will contain the intersection of the permitted lists for any device or profile * owners that apply to this user. It will also include any system accessibility services. * @@ -9933,6 +9934,8 @@ public class DevicePolicyManager { * * @return List of input method package names. * @hide + * + * @see #setPermittedAccessibilityServices(ComponentName, List) */ @SystemApi @RequiresPermission(anyOf = { @@ -9953,29 +9956,30 @@ public class DevicePolicyManager { /** * Returns the list of input methods permitted. * - * <p>When this method returns empty list means all input methods are allowed, if a non-empty - * list is returned it will contain the intersection of the permitted lists for any device or - * profile owners that apply to this user. It will also include any system input methods. + * <p>{@code null} means all input methods are allowed, if a non-null list is returned + * it will contain the intersection of the permitted lists for any device or profile + * owners that apply to this user. It will also include any system input methods. * * @return List of input method package names. * @hide + * + * @see #setPermittedAccessibilityServices(ComponentName, List) */ @UserHandleAware @RequiresPermission(allOf = { android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, android.Manifest.permission.MANAGE_USERS }, conditional = true) - public @NonNull List<String> getPermittedInputMethods() { + public @Nullable List<String> getPermittedInputMethods() { throwIfParentInstance("getPermittedInputMethods"); - List<String> result = null; if (mService != null) { try { - result = mService.getPermittedInputMethodsAsUser(myUserId()); + return mService.getPermittedInputMethodsAsUser(myUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - return result != null ? result : Collections.emptyList(); + return null; } /** @@ -10330,6 +10334,7 @@ public class DevicePolicyManager { throw re.rethrowFromSystemServer(); } } + /** * Gets the user a {@link #logoutUser(ComponentName)} call would switch to, * or {@code null} if the current user is not in a session (i.e., if it was not diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index eedc042607be..0b9d51f0bdda 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -158,7 +158,7 @@ interface IDevicePolicyManager { void forceRemoveActiveAdmin(in ComponentName policyReceiver, int userHandle); boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle); - void reportPasswordChanged(int userId); + void reportPasswordChanged(in PasswordMetrics metrics, int userId); void reportFailedPasswordAttempt(int userHandle); void reportSuccessfulPasswordAttempt(int userHandle); void reportFailedBiometricAttempt(int userHandle); diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java index 8c59982f4058..b170aa2a1325 100644 --- a/core/java/android/app/admin/SecurityLog.java +++ b/core/java/android/app/admin/SecurityLog.java @@ -16,8 +16,12 @@ package android.app.admin; +import android.Manifest; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; @@ -86,7 +90,12 @@ public class SecurityLog { TAG_KEY_INTEGRITY_VIOLATION, TAG_CERT_VALIDATION_FAILURE, TAG_CAMERA_POLICY_SET, - TAG_PASSWORD_COMPLEXITY_REQUIRED + TAG_PASSWORD_COMPLEXITY_REQUIRED, + TAG_PASSWORD_CHANGED, + TAG_WIFI_CONNECTION, + TAG_WIFI_DISCONNECTION, + TAG_BLUETOOTH_CONNECTION, + TAG_BLUETOOTH_DISCONNECTION, }) public @interface SecurityLogTag {} @@ -495,6 +504,65 @@ public class SecurityLog { SecurityLogTags.SECURITY_PASSWORD_COMPLEXITY_REQUIRED; /** + * Indicates that a user has just changed their lockscreen password. + * The log entry contains the following information about the + * event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] complexity for the new password ({@code Integer}) + * <li> [1] target user ID ({@code Integer}) + * + * <p>Password complexity levels are defined as in + * {@link DevicePolicyManager#getPasswordComplexity()} + */ + public static final int TAG_PASSWORD_CHANGED = SecurityLogTags.SECURITY_PASSWORD_CHANGED; + + /** + * Indicates that the device attempts to connect to a WiFi network. + * The log entry contains the following information about the + * event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] The SSID of the network ({@code String}) + * <li> [1] The BSSID of the network ({@code String}) + * <li> [2] Whether the connection is successful ({@code Integer}, 1 if successful, 0 otherwise) + * <li> [3] Optional human-readable failure reason, empty string if none ({@code String}) + */ + public static final int TAG_WIFI_CONNECTION = SecurityLogTags.SECURITY_WIFI_CONNECTION; + + /** + * Indicates that the device disconnects from a connected WiFi network. + * The log entry contains the following information about the + * event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] The SSID of the connected network ({@code String}) + * <li> [1] The BSSID of the connected network ({@code String}) + * <li> [2] Optional human-readable disconnection reason, empty string if none ({@code String}) + */ + public static final int TAG_WIFI_DISCONNECTION = SecurityLogTags.SECURITY_WIFI_DISCONNECTION; + + /** + * Indicates that the device attempts to connect to a Bluetooth device. + * The log entry contains the following information about the + * event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] The MAC address of the Bluetooth device ({@code String}) + * <li> [1] Whether the connection is successful ({@code Integer}, 1 if successful, 0 otherwise) + * <li> [2] Optional human-readable failure reason, empty string if none ({@code String}) + */ + public static final int TAG_BLUETOOTH_CONNECTION = + SecurityLogTags.SECURITY_BLUETOOTH_CONNECTION; + + /** + * Indicates that the device disconnects from a connected Bluetooth device. + * The log entry contains the following information about the + * event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] The MAC address of the connected Bluetooth device ({@code String}) + * <li> [1] Optional human-readable disconnection reason, empty string if none ({@code String}) + */ + public static final int TAG_BLUETOOTH_DISCONNECTION = + SecurityLogTags.SECURITY_BLUETOOTH_DISCONNECTION; + + /** * Event severity level indicating that the event corresponds to normal workflow. */ public static final int LEVEL_INFO = 1; @@ -635,6 +703,7 @@ public class SecurityLog { case TAG_USER_RESTRICTION_REMOVED: case TAG_CAMERA_POLICY_SET: case TAG_PASSWORD_COMPLEXITY_REQUIRED: + case TAG_PASSWORD_CHANGED: return LEVEL_INFO; case TAG_CERT_AUTHORITY_REMOVED: case TAG_CRYPTO_SELF_TEST_COMPLETED: @@ -725,6 +794,13 @@ public class SecurityLog { return null; } break; + case SecurityLog.TAG_PASSWORD_CHANGED: + try { + userId = getIntegerData(1); + } catch (Exception e) { + return null; + } + break; default: userId = UserHandle.USER_NULL; } @@ -840,15 +916,21 @@ public class SecurityLog { throws IOException; /** - * Write a log entry to the underlying storage, with a string payload. - * @hide - */ - public static native int writeEvent(int tag, String str); - - /** * Write a log entry to the underlying storage, with several payloads. * Supported types of payload are: integer, long, float, string plus array of supported types. + * + * <p>Security log is part of Android's device management capability that tracks + * security-sensitive events for auditing purposes. + * + * @param tag the tag ID of the security event + * @param payloads a list of payload values. Each tag dictates the expected payload types + * and their meanings + * @see DevicePolicyManager#setSecurityLoggingEnabled(ComponentName, boolean) + * * @hide */ - public static native int writeEvent(int tag, Object... payloads); + // TODO(b/218658622): enforce WRITE_SECURITY_LOG in logd. + @SystemApi + @RequiresPermission(Manifest.permission.WRITE_SECURITY_LOG) + public static native int writeEvent(@SecurityLogTag int tag, @NonNull Object... payloads); } diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags index db5245c919ab..5f41109600b2 100644 --- a/core/java/android/app/admin/SecurityLogTags.logtags +++ b/core/java/android/app/admin/SecurityLogTags.logtags @@ -40,3 +40,8 @@ option java_package android.app.admin 210033 security_cert_validation_failure (reason|3) 210034 security_camera_policy_set (package|3),(admin_user|1),(target_user|1),(disabled|1) 210035 security_password_complexity_required (package|3),(admin_user|1),(target_user|1),(complexity|1) +210036 security_password_changed (password_complexity|1),(target_user|1) +210037 security_wifi_connection (ssid|3),(bssid|3),(success|1),(reason|3) +210038 security_wifi_disconnection (ssid|3),(bssid|3),(reason|3) +210039 security_bluetooth_connection (addr|3),(success|1),(reason|3) +210040 security_bluetooth_disconnection (addr|3),(reason|3)
\ No newline at end of file diff --git a/core/java/android/app/cloudsearch/SearchRequest.java b/core/java/android/app/cloudsearch/SearchRequest.java index 0c5c30c23d7b..ef66c0cf9a44 100644 --- a/core/java/android/app/cloudsearch/SearchRequest.java +++ b/core/java/android/app/cloudsearch/SearchRequest.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -91,6 +92,14 @@ public final class SearchRequest implements Parcelable { @NonNull private Bundle mSearchConstraints; + /** Auto set by system servier, and the caller cannot set it. + * + * The caller's package name. + * + */ + @NonNull + private String mSource; + private SearchRequest(Parcel in) { this.mQuery = in.readString(); this.mResultOffset = in.readInt(); @@ -98,15 +107,17 @@ public final class SearchRequest implements Parcelable { this.mMaxLatencyMillis = in.readFloat(); this.mSearchConstraints = in.readBundle(); this.mId = in.readString(); + this.mSource = in.readString(); } private SearchRequest(String query, int resultOffset, int resultNumber, float maxLatencyMillis, - Bundle searchConstraints) { + Bundle searchConstraints, String source) { mQuery = query; mResultOffset = resultOffset; mResultNumber = resultNumber; mMaxLatencyMillis = maxLatencyMillis; mSearchConstraints = searchConstraints; + mSource = source; } /** Returns the original query. */ @@ -136,35 +147,37 @@ public final class SearchRequest implements Parcelable { return mSearchConstraints; } + /** Gets the caller's package name. */ + @NonNull + public String getSource() { + return mSource; + } + /** Returns the search request id, which is used to identify the request. */ @NonNull public String getRequestId() { if (mId == null || mId.length() == 0) { - boolean isPresubmit = - mSearchConstraints.containsKey(CONSTRAINT_IS_PRESUBMIT_SUGGESTION) - && mSearchConstraints.getBoolean(CONSTRAINT_IS_PRESUBMIT_SUGGESTION); - - String searchProvider = "EMPTY"; - if (mSearchConstraints.containsKey(CONSTRAINT_SEARCH_PROVIDER_FILTER)) { - searchProvider = mSearchConstraints.getString(CONSTRAINT_SEARCH_PROVIDER_FILTER); - } - - String rawContent = String.format("%s\t%d\t%d\t%f\t%b\t%s", - mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis, - isPresubmit, searchProvider); - - mId = String.valueOf(rawContent.hashCode()); + mId = String.valueOf(toString().hashCode()); } return mId; } + /** Sets the caller, and this will be set by the system server. + * + * @hide + */ + public void setSource(@NonNull String source) { + this.mSource = source; + } + private SearchRequest(Builder b) { mQuery = requireNonNull(b.mQuery); mResultOffset = b.mResultOffset; mResultNumber = b.mResultNumber; mMaxLatencyMillis = b.mMaxLatencyMillis; mSearchConstraints = requireNonNull(b.mSearchConstraints); + mSource = requireNonNull(b.mSource); } /** @@ -192,6 +205,7 @@ public final class SearchRequest implements Parcelable { dest.writeFloat(this.mMaxLatencyMillis); dest.writeBundle(this.mSearchConstraints); dest.writeString(getRequestId()); + dest.writeString(this.mSource); } @Override @@ -214,13 +228,30 @@ public final class SearchRequest implements Parcelable { && mResultOffset == that.mResultOffset && mResultNumber == that.mResultNumber && mMaxLatencyMillis == that.mMaxLatencyMillis - && Objects.equals(mSearchConstraints, that.mSearchConstraints); + && Objects.equals(mSearchConstraints, that.mSearchConstraints) + && Objects.equals(mSource, that.mSource); + } + + @Override + public String toString() { + boolean isPresubmit = + mSearchConstraints.containsKey(CONSTRAINT_IS_PRESUBMIT_SUGGESTION) + && mSearchConstraints.getBoolean(CONSTRAINT_IS_PRESUBMIT_SUGGESTION); + + String searchProvider = "EMPTY"; + if (mSearchConstraints.containsKey(CONSTRAINT_SEARCH_PROVIDER_FILTER)) { + searchProvider = mSearchConstraints.getString(CONSTRAINT_SEARCH_PROVIDER_FILTER); + } + + return String.format("SearchRequest: {query:%s,offset:%d;number:%d;max_latency:%f;" + + "is_presubmit:%b;search_provider:%s;source:%s}", mQuery, mResultOffset, + mResultNumber, mMaxLatencyMillis, isPresubmit, searchProvider, mSource); } @Override public int hashCode() { return Objects.hash(mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis, - mSearchConstraints); + mSearchConstraints, mSource); } /** @@ -235,6 +266,7 @@ public final class SearchRequest implements Parcelable { private int mResultNumber; private float mMaxLatencyMillis; private Bundle mSearchConstraints; + private String mSource; /** * @@ -250,6 +282,7 @@ public final class SearchRequest implements Parcelable { mResultNumber = 10; mMaxLatencyMillis = 200; mSearchConstraints = Bundle.EMPTY; + mSource = "DEFAULT_CALLER"; } /** Sets the input query. */ @@ -288,6 +321,17 @@ public final class SearchRequest implements Parcelable { return this; } + /** Sets the caller, and this will be set by the system server. + * + * @hide + */ + @NonNull + @TestApi + public Builder setSource(@NonNull String source) { + this.mSource = source; + return this; + } + /** Builds a SearchRequest based-on the given params. */ @NonNull public SearchRequest build() { @@ -297,7 +341,7 @@ public final class SearchRequest implements Parcelable { } return new SearchRequest(mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis, - mSearchConstraints); + mSearchConstraints, mSource); } } } diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl index edabccf23c2c..b786444faa8c 100644 --- a/core/java/android/app/trust/ITrustManager.aidl +++ b/core/java/android/app/trust/ITrustManager.aidl @@ -33,8 +33,8 @@ interface ITrustManager { void unregisterTrustListener(in ITrustListener trustListener); void reportKeyguardShowingChanged(); void setDeviceLockedForUser(int userId, boolean locked); - boolean isDeviceLocked(int userId); - boolean isDeviceSecure(int userId); + boolean isDeviceLocked(int userId, int displayId); + boolean isDeviceSecure(int userId, int displayId); boolean isTrustUsuallyManaged(int userId); void unlockedByBiometricForUser(int userId, in BiometricSourceType source); void clearAllBiometricRecognized(in BiometricSourceType target, int unlockedUser); diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 0f5cd4e44bed..3c256ad97f21 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -1037,6 +1037,7 @@ public final class UsageEvents implements Parcelable { // Data can be too large for a transact. Write the data as a Blob, which will be written to // ashmem if too large. dest.writeBlob(data.marshall()); + data.recycle(); } public static final @android.annotation.NonNull Creator<UsageEvents> CREATOR = new Creator<UsageEvents>() { diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index 373a8d957282..f7f0235cd508 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -207,6 +207,18 @@ public final class AssociationInfo implements Parcelable { return macAddress.equals(mDeviceMacAddress); } + /** + * Utility method to be used by CdmService only. + * + * @return whether CdmService should bind the companion application that "owns" this association + * when the device is present. + * + * @hide + */ + public boolean shouldBindWhenPresent() { + return mNotifyOnDeviceNearby || mSelfManaged; + } + /** @hide */ public @NonNull String toShortString() { final StringBuilder sb = new StringBuilder(); diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 36802eabee15..15685000d6ba 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -780,9 +780,9 @@ public final class CompanionDeviceManager { } /** - * Notify the system that the given self-managed association has just 'appeared'. + * Notify the system that the given self-managed association has just appeared. * This causes the system to bind to the companion app to keep it running until the association - * is reported as 'disappeared' + * is reported as disappeared * * <p>This API is only available for the companion apps that manage the connectivity by * themselves.</p> @@ -803,7 +803,7 @@ public final class CompanionDeviceManager { } /** - * Notify the system that the given self-managed association has just 'disappeared'. + * Notify the system that the given self-managed association has just disappeared. * This causes the system to unbind to the companion app. * * <p>This API is only available for the companion apps that manage the connectivity by diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java index cb96ebe8bca9..9e1bf4bb9484 100644 --- a/core/java/android/companion/CompanionDeviceService.java +++ b/core/java/android/companion/CompanionDeviceService.java @@ -77,10 +77,11 @@ import java.util.Objects; * {@link #onDeviceAppeared(AssociationInfo)} and {@link #onDeviceDisappeared(AssociationInfo)} * only to one "primary" services. * Applications that declare multiple {@link CompanionDeviceService}-s should indicate the "primary" - * service using "android.companion.primary" tag. + * service using "android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE" service level + * property. * <pre>{@code - * <meta-data - * android:name="android.companion.primary" + * <property + * android:name="android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE" * android:value="true" /> * }</pre> * diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 8fc24fd2d7f2..e2859998efd4 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -17,6 +17,7 @@ package android.companion.virtual; import android.app.PendingIntent; +import android.companion.virtual.audio.IAudioSessionCallback; import android.graphics.Point; import android.graphics.PointF; import android.hardware.input.VirtualKeyEvent; @@ -45,6 +46,15 @@ interface IVirtualDevice { */ void close(); + /** + * Notifies of an audio session being started. + */ + void onAudioSessionStarting( + int displayId, + IAudioSessionCallback callback); + + void onAudioSessionEnded(); + void createVirtualKeyboard( int displayId, String inputDeviceName, diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 1dbe04cfda8e..f1abb056da56 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -16,19 +16,23 @@ package android.companion.virtual; +import android.annotation.CallbackExecutor; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.Activity; import android.app.PendingIntent; import android.companion.AssociationInfo; +import android.companion.virtual.audio.VirtualAudioDevice; +import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback; import android.content.ComponentName; import android.content.Context; import android.graphics.Point; import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.VirtualDisplayFlag; import android.hardware.display.VirtualDisplay; import android.hardware.display.VirtualDisplayConfig; import android.hardware.input.VirtualKeyboard; @@ -44,6 +48,7 @@ import android.os.ResultReceiver; import android.util.ArrayMap; import android.view.Surface; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -208,25 +213,22 @@ public final class VirtualDeviceManager { * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC VIRTUAL_DISPLAY_FLAG_PUBLIC} and * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY * VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}. + * @param executor The executor on which {@code callback} will be invoked. This is ignored + * if {@code callback} is {@code null}. * @param callback Callback to call when the state of the {@link VirtualDisplay} changes - * @param handler The handler on which the listener should be invoked, or null - * if the listener should be invoked on the calling thread's looper. * @return The newly created virtual display, or {@code null} if the application could * not create the virtual display. * * @see DisplayManager#createVirtualDisplay */ - // Suppress "ExecutorRegistration" because DisplayManager.createVirtualDisplay takes a - // handler - @SuppressLint("ExecutorRegistration") @Nullable public VirtualDisplay createVirtualDisplay( - int width, - int height, - int densityDpi, + @IntRange(from = 1) int width, + @IntRange(from = 1) int height, + @IntRange(from = 1) int densityDpi, @Nullable Surface surface, - int flags, - @Nullable Handler handler, + @VirtualDisplayFlag int flags, + @NonNull @CallbackExecutor Executor executor, @Nullable VirtualDisplay.Callback callback) { // TODO(b/205343547): Handle display groups properly instead of creating a new display // group for every new virtual display created using this API. @@ -242,7 +244,7 @@ public final class VirtualDeviceManager { .setFlags(getVirtualDisplayFlags(flags)) .build(), callback, - handler); + Objects.requireNonNull(executor)); } /** @@ -263,8 +265,8 @@ public final class VirtualDeviceManager { * * @param display the display that the events inputted through this device should target * @param inputDeviceName the name to call this input device - * @param vendorId the vendor id - * @param productId the product id + * @param vendorId the vendor id, as defined by uinput's uinput_setup struct (PCI vendor id) + * @param productId the product id, as defined by uinput's uinput_setup struct */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull @@ -289,8 +291,8 @@ public final class VirtualDeviceManager { * * @param display the display that the events inputted through this device should target * @param inputDeviceName the name to call this input device - * @param vendorId the vendor id - * @param productId the product id + * @param vendorId the vendor id, as defined by uinput's uinput_setup struct (PCI vendor id) + * @param productId the product id, as defined by uinput's uinput_setup struct */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull @@ -315,8 +317,8 @@ public final class VirtualDeviceManager { * * @param display the display that the events inputted through this device should target * @param inputDeviceName the name to call this input device - * @param vendorId the vendor id - * @param productId the product id + * @param vendorId the vendor id, as defined by uinput's uinput_setup struct (PCI vendor id) + * @param productId the product id, as defined by uinput's uinput_setup struct */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull @@ -339,6 +341,30 @@ public final class VirtualDeviceManager { } /** + * Creates a VirtualAudioDevice, capable of recording audio emanating from this device, + * or injecting audio from another device. + * + * <p>Note: This object does not support capturing privileged playback, such as voice call + * audio. + * + * @param display The target virtual display to capture from and inject into. + * @param executor The {@link Executor} object for the thread on which to execute + * the callback. If <code>null</code>, the {@link Executor} associated with + * the main {@link Looper} will be used. + * @param callback Interface to be notified when playback or recording configuration of + * applications running on virtual display is changed. + * @return A {@link VirtualAudioDevice} instance. + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @NonNull + public VirtualAudioDevice createVirtualAudioDevice( + @NonNull VirtualDisplay display, + @Nullable Executor executor, + @Nullable AudioConfigurationChangeCallback callback) { + return new VirtualAudioDevice(mContext, mVirtualDevice, display, executor, callback); + } + + /** * Sets the visibility of the pointer icon for this VirtualDevice's associated displays. * * @param showPointerIcon True if the pointer should be shown; false otherwise. The default diff --git a/core/java/android/companion/virtual/audio/AudioCapture.java b/core/java/android/companion/virtual/audio/AudioCapture.java new file mode 100644 index 000000000000..ebe17dba5775 --- /dev/null +++ b/core/java/android/companion/virtual/audio/AudioCapture.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.audio; + +import static android.media.AudioRecord.RECORDSTATE_RECORDING; +import static android.media.AudioRecord.RECORDSTATE_STOPPED; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.media.AudioRecord; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.nio.ByteBuffer; + +/** + * Wrapper around {@link AudioRecord} that allows for the underlying {@link AudioRecord} to + * be swapped out while recording is ongoing. + * + * @hide + */ +// The stop() actually doesn't release resources, so should not force implementing Closeable. +@SuppressLint("NotCloseable") +@SystemApi +public final class AudioCapture { + private static final String TAG = "AudioCapture"; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + @Nullable + private AudioRecord mAudioRecord; + + @GuardedBy("mLock") + private int mRecordingState = RECORDSTATE_STOPPED; + + /** + * Sets the {@link AudioRecord} to handle audio capturing. + * Callers may call this multiple times with different audio records to change + * the underlying {@link AudioRecord} without stopping and re-starting recording. + * + * @param audioRecord The underlying {@link AudioRecord} to use for capture, + * or null if no audio (i.e. silence) should be captured while still keeping the + * record in a recording state. + */ + void setAudioRecord(@Nullable AudioRecord audioRecord) { + Log.d(TAG, "set AudioRecord with " + audioRecord); + synchronized (mLock) { + // Release old reference. + if (mAudioRecord != null) { + mAudioRecord.release(); + } + // Sync recording state for new reference. + if (audioRecord != null) { + if (mRecordingState == RECORDSTATE_RECORDING + && audioRecord.getRecordingState() != RECORDSTATE_RECORDING) { + audioRecord.startRecording(); + } + if (mRecordingState == RECORDSTATE_STOPPED + && audioRecord.getRecordingState() != RECORDSTATE_STOPPED) { + audioRecord.stop(); + } + } + mAudioRecord = audioRecord; + } + } + + /** See {@link AudioRecord#read(ByteBuffer, int)}. */ + public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes) { + final int sizeRead; + synchronized (mLock) { + if (mAudioRecord != null) { + sizeRead = mAudioRecord.read(audioBuffer, sizeInBytes); + } else { + sizeRead = 0; + } + } + return sizeRead; + } + + /** See {@link AudioRecord#startRecording()}. */ + public void startRecording() { + synchronized (mLock) { + mRecordingState = RECORDSTATE_RECORDING; + if (mAudioRecord != null && mAudioRecord.getRecordingState() != RECORDSTATE_RECORDING) { + mAudioRecord.startRecording(); + } + } + } + + /** See {@link AudioRecord#stop()}. */ + public void stop() { + synchronized (mLock) { + mRecordingState = RECORDSTATE_STOPPED; + if (mAudioRecord != null && mAudioRecord.getRecordingState() != RECORDSTATE_STOPPED) { + mAudioRecord.stop(); + } + } + } + + /** See {@link AudioRecord#getRecordingState()}. */ + public int getRecordingState() { + synchronized (mLock) { + return mRecordingState; + } + } +} diff --git a/core/java/android/companion/virtual/audio/AudioInjection.java b/core/java/android/companion/virtual/audio/AudioInjection.java new file mode 100644 index 000000000000..5e8e0a487a2e --- /dev/null +++ b/core/java/android/companion/virtual/audio/AudioInjection.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.audio; + +import static android.media.AudioTrack.PLAYSTATE_PLAYING; +import static android.media.AudioTrack.PLAYSTATE_STOPPED; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.media.AudioTrack; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.nio.ByteBuffer; + +/** + * Wrapper around {@link AudioTrack} that allows for the underlying {@link AudioTrack} to + * be swapped out while playout is ongoing. + * + * @hide + */ +// The stop() actually doesn't release resources, so should not force implementing Closeable. +@SuppressLint("NotCloseable") +@SystemApi +public final class AudioInjection { + private static final String TAG = "AudioInjection"; + + private final Object mLock = new Object(); + @GuardedBy("mLock") + @Nullable + private AudioTrack mAudioTrack; + @GuardedBy("mLock") + private int mPlayState = PLAYSTATE_STOPPED; + @GuardedBy("mLock") + private boolean mIsSilent; + + /** Sets if the injected microphone sound is silent. */ + void setSilent(boolean isSilent) { + synchronized (mLock) { + mIsSilent = isSilent; + } + } + + /** + * Sets the {@link AudioTrack} to handle audio injection. + * Callers may call this multiple times with different audio tracks to change + * the underlying {@link AudioTrack} without stopping and re-starting injection. + * + * @param audioTrack The underlying {@link AudioTrack} to use for injection, + * or null if no audio (i.e. silence) should be injected while still keeping the + * record in a playing state. + */ + void setAudioTrack(@Nullable AudioTrack audioTrack) { + Log.d(TAG, "set AudioTrack with " + audioTrack); + synchronized (mLock) { + // Release old reference. + if (mAudioTrack != null) { + mAudioTrack.release(); + } + // Sync play state for new reference. + if (audioTrack != null) { + if (mPlayState == PLAYSTATE_PLAYING + && audioTrack.getPlayState() != PLAYSTATE_PLAYING) { + audioTrack.play(); + } + if (mPlayState == PLAYSTATE_STOPPED + && audioTrack.getPlayState() != PLAYSTATE_STOPPED) { + audioTrack.stop(); + } + } + mAudioTrack = audioTrack; + } + } + + /** See {@link AudioTrack#write(ByteBuffer, int, int)}. */ + public int write(@NonNull ByteBuffer audioBuffer, int sizeInBytes, int writeMode) { + final int sizeWrite; + synchronized (mLock) { + if (mAudioTrack != null && !mIsSilent) { + sizeWrite = mAudioTrack.write(audioBuffer, sizeInBytes, writeMode); + } else { + sizeWrite = 0; + } + } + return sizeWrite; + } + + /** See {@link AudioTrack#play()}. */ + public void play() { + synchronized (mLock) { + mPlayState = PLAYSTATE_PLAYING; + if (mAudioTrack != null && mAudioTrack.getPlayState() != PLAYSTATE_PLAYING) { + mAudioTrack.play(); + } + } + } + + /** See {@link AudioTrack#stop()}. */ + public void stop() { + synchronized (mLock) { + mPlayState = PLAYSTATE_STOPPED; + if (mAudioTrack != null && mAudioTrack.getPlayState() != PLAYSTATE_STOPPED) { + mAudioTrack.stop(); + } + } + } + + /** See {@link AudioTrack#getPlayState()}. */ + public int getPlayState() { + synchronized (mLock) { + return mPlayState; + } + } +} diff --git a/core/java/android/companion/virtual/audio/IAudioSessionCallback.aidl b/core/java/android/companion/virtual/audio/IAudioSessionCallback.aidl new file mode 100644 index 000000000000..e22ce148a9be --- /dev/null +++ b/core/java/android/companion/virtual/audio/IAudioSessionCallback.aidl @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.audio; + +import android.media.AudioPlaybackConfiguration; +import android.media.AudioRecordingConfiguration; + +/** + * Callback to control audio rerouting, notify playback and recording state of applications running + * on virtual device. + * + * @hide + */ +oneway interface IAudioSessionCallback { + + /** Updates the set of applications that need to have their audio rerouted. */ + void onAppsNeedingAudioRoutingChanged(in int[] appUids); + + /** + * Called whenever the playback configuration of applications running on virtual device has + * changed. + */ + void onPlaybackConfigChanged(in List<AudioPlaybackConfiguration> configs); + + /** + * Called whenever the recording configuration of applications running on virtual device has + * changed. + */ + void onRecordingConfigChanged(in List<AudioRecordingConfiguration> configs); +} diff --git a/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java b/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java new file mode 100644 index 000000000000..5c246d365751 --- /dev/null +++ b/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.audio; + +import android.annotation.NonNull; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.os.UserManager; + +import com.android.internal.annotations.GuardedBy; + +/** + * Class to detect the user restrictions change for microphone usage. + */ +final class UserRestrictionsDetector extends BroadcastReceiver { + private static final String TAG = "UserRestrictionsDetector"; + + /** Interface for listening user restrictions change. */ + interface UserRestrictionsCallback { + + /** Notifies when value of {@link UserManager#DISALLOW_UNMUTE_MICROPHONE} is changed. */ + void onMicrophoneRestrictionChanged(boolean isUnmuteMicDisallowed); + } + + private final Context mContext; + private final UserManager mUserManager; + private final Object mLock = new Object(); + @GuardedBy("mLock") + private boolean mIsUnmuteMicDisallowed; + private UserRestrictionsCallback mUserRestrictionsCallback; + + UserRestrictionsDetector(Context context) { + mContext = context; + mUserManager = context.getSystemService(UserManager.class); + } + + /** Returns value of {@link UserManager#DISALLOW_UNMUTE_MICROPHONE}. */ + boolean isUnmuteMicrophoneDisallowed() { + Bundle bundle = mUserManager.getUserRestrictions(); + return bundle.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE); + } + + /** Registers user restrictions change. */ + void register(@NonNull UserRestrictionsCallback callback) { + mUserRestrictionsCallback = callback; + + IntentFilter filter = new IntentFilter(); + filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED); + mContext.registerReceiver(/* receiver= */ this, filter); + + synchronized (mLock) { + // Gets initial value. + mIsUnmuteMicDisallowed = isUnmuteMicrophoneDisallowed(); + } + } + + /** Unregisters user restrictions change. */ + void unregister() { + mUserRestrictionsCallback = null; + mContext.unregisterReceiver(/* receiver= */ this); + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (UserManager.ACTION_USER_RESTRICTIONS_CHANGED.equals(action)) { + boolean isUnmuteMicDisallowed = isUnmuteMicrophoneDisallowed(); + synchronized (mLock) { + if (isUnmuteMicDisallowed == mIsUnmuteMicDisallowed) { + return; + } + mIsUnmuteMicDisallowed = isUnmuteMicDisallowed; + } + if (mUserRestrictionsCallback != null) { + mUserRestrictionsCallback.onMicrophoneRestrictionChanged(isUnmuteMicDisallowed); + } + } + } +} diff --git a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java new file mode 100644 index 000000000000..38e37ec0c316 --- /dev/null +++ b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.audio; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.companion.virtual.IVirtualDevice; +import android.content.Context; +import android.hardware.display.VirtualDisplay; +import android.media.AudioFormat; +import android.media.AudioPlaybackConfiguration; +import android.media.AudioRecordingConfiguration; +import android.os.RemoteException; + +import java.io.Closeable; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * The class stores an {@link AudioCapture} for audio capturing and an {@link AudioInjection} for + * audio injection. + * + * @hide + */ +@SystemApi +public final class VirtualAudioDevice implements Closeable { + + /** + * Interface to be notified when playback or recording configuration of applications running on + * virtual display was changed. + * + * @hide + */ + @SystemApi + public interface AudioConfigurationChangeCallback { + /** + * Notifies when playback configuration of applications running on virtual display was + * changed. + */ + void onPlaybackConfigChanged(@NonNull List<AudioPlaybackConfiguration> configs); + + /** + * Notifies when recording configuration of applications running on virtual display was + * changed. + */ + void onRecordingConfigChanged(@NonNull List<AudioRecordingConfiguration> configs); + } + + private final Context mContext; + private final IVirtualDevice mVirtualDevice; + private final VirtualDisplay mVirtualDisplay; + private final AudioConfigurationChangeCallback mCallback; + private final Executor mExecutor; + @Nullable + private VirtualAudioSession mOngoingSession; + + /** + * @hide + */ + public VirtualAudioDevice(Context context, IVirtualDevice virtualDevice, + VirtualDisplay virtualDisplay, Executor executor, + AudioConfigurationChangeCallback callback) { + mContext = context; + mVirtualDevice = virtualDevice; + mVirtualDisplay = virtualDisplay; + mExecutor = executor; + mCallback = callback; + } + + /** + * Begins injecting audio from a remote device into this device. + * + * @return An {@link AudioInjection} containing the injected audio. + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @NonNull + public AudioInjection startAudioInjection(@NonNull AudioFormat injectionFormat) { + Objects.requireNonNull(injectionFormat, "injectionFormat must not be null"); + + if (mOngoingSession != null && mOngoingSession.getAudioInjection() != null) { + throw new IllegalStateException("Cannot start an audio injection while a session is " + + "ongoing. Call close() on this device first to end the previous injection."); + } + if (mOngoingSession == null) { + mOngoingSession = new VirtualAudioSession(mContext, mCallback, mExecutor); + } + + try { + mVirtualDevice.onAudioSessionStarting(mVirtualDisplay.getDisplay().getDisplayId(), + /* callback= */ mOngoingSession); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return mOngoingSession.startAudioInjection(injectionFormat); + } + + /** + * Begins recording audio emanating from this device. + * + * @return An {@link AudioCapture} containing the recorded audio. + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @NonNull + public AudioCapture startAudioCapture(@NonNull AudioFormat captureFormat) { + Objects.requireNonNull(captureFormat, "captureFormat must not be null"); + + if (mOngoingSession != null && mOngoingSession.getAudioCapture() != null) { + throw new IllegalStateException("Cannot start an audio capture while a session is " + + "ongoing. Call close() on this device first to end the previous session."); + } + if (mOngoingSession == null) { + mOngoingSession = new VirtualAudioSession(mContext, mCallback, mExecutor); + } + + try { + mVirtualDevice.onAudioSessionStarting(mVirtualDisplay.getDisplay().getDisplayId(), + /* callback= */ mOngoingSession); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return mOngoingSession.startAudioCapture(captureFormat); + } + + /** Returns the {@link AudioCapture} instance. */ + @Nullable + public AudioCapture getAudioCapture() { + return mOngoingSession != null ? mOngoingSession.getAudioCapture() : null; + } + + /** Returns the {@link AudioInjection} instance. */ + @Nullable + public AudioInjection getAudioInjection() { + return mOngoingSession != null ? mOngoingSession.getAudioInjection() : null; + } + + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @Override + public void close() { + if (mOngoingSession != null) { + mOngoingSession.close(); + mOngoingSession = null; + + try { + mVirtualDevice.onAudioSessionEnded(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } +} diff --git a/core/java/android/companion/virtual/audio/VirtualAudioSession.java b/core/java/android/companion/virtual/audio/VirtualAudioSession.java new file mode 100644 index 000000000000..bc71bd65e944 --- /dev/null +++ b/core/java/android/companion/virtual/audio/VirtualAudioSession.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.audio; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.companion.virtual.audio.UserRestrictionsDetector.UserRestrictionsCallback; +import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback; +import android.content.Context; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioPlaybackConfiguration; +import android.media.AudioRecord; +import android.media.AudioRecordingConfiguration; +import android.media.AudioTrack; +import android.media.audiopolicy.AudioMix; +import android.media.audiopolicy.AudioMixingRule; +import android.media.audiopolicy.AudioPolicy; +import android.util.IntArray; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.Closeable; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * Manages an ongiong audio session in which audio can be captured (recorded) and/or + * injected from a remote device. + * + * @hide + */ +@VisibleForTesting +public final class VirtualAudioSession extends IAudioSessionCallback.Stub implements + UserRestrictionsCallback, Closeable { + private static final String TAG = "VirtualAudioSession"; + + private final Context mContext; + private final UserRestrictionsDetector mUserRestrictionsDetector; + /** The {@link Executor} for sending {@link AudioConfigurationChangeCallback} to the caller */ + private final Executor mExecutor; + @Nullable + private final AudioConfigurationChangeCallback mCallback; + private final Object mLock = new Object(); + @GuardedBy("mLock") + private final IntArray mReroutedAppUids = new IntArray(); + @Nullable + @GuardedBy("mLock") + private AudioPolicy mAudioPolicy; + @Nullable + @GuardedBy("mLock") + private AudioFormat mCaptureFormat; + @Nullable + @GuardedBy("mLock") + private AudioFormat mInjectionFormat; + @Nullable + @GuardedBy("mLock") + private AudioCapture mAudioCapture; + @Nullable + @GuardedBy("mLock") + private AudioInjection mAudioInjection; + + @VisibleForTesting + public VirtualAudioSession(Context context, + @Nullable AudioConfigurationChangeCallback callback, @Nullable Executor executor) { + mContext = context; + mUserRestrictionsDetector = new UserRestrictionsDetector(context); + mCallback = callback; + mExecutor = executor != null ? executor : context.getMainExecutor(); + } + + /** + * Begins recording audio emanating from this device. + * + * @return An {@link AudioCapture} containing the recorded audio. + */ + @VisibleForTesting + @NonNull + public AudioCapture startAudioCapture(@NonNull AudioFormat captureFormat) { + Objects.requireNonNull(captureFormat, "captureFormat must not be null"); + + synchronized (mLock) { + if (mAudioCapture != null) { + throw new IllegalStateException( + "Cannot start capture while another capture is ongoing."); + } + + mCaptureFormat = captureFormat; + mAudioCapture = new AudioCapture(); + mAudioCapture.startRecording(); + return mAudioCapture; + } + } + + /** + * Begins injecting audio from a remote device into this device. + * + * @return An {@link AudioInjection} containing the injected audio. + */ + @VisibleForTesting + @NonNull + public AudioInjection startAudioInjection(@NonNull AudioFormat injectionFormat) { + Objects.requireNonNull(injectionFormat, "injectionFormat must not be null"); + mUserRestrictionsDetector.register(/* callback= */ this); + synchronized (mLock) { + if (mAudioInjection != null) { + throw new IllegalStateException( + "Cannot start injection while injection is already ongoing."); + } + + mInjectionFormat = injectionFormat; + mAudioInjection = new AudioInjection(); + mAudioInjection.play(); + mAudioInjection.setSilent(mUserRestrictionsDetector.isUnmuteMicrophoneDisallowed()); + return mAudioInjection; + } + } + + /** @hide */ + @VisibleForTesting + @Nullable + public AudioCapture getAudioCapture() { + synchronized (mLock) { + return mAudioCapture; + } + } + + /** @hide */ + @VisibleForTesting + @Nullable + public AudioInjection getAudioInjection() { + synchronized (mLock) { + return mAudioInjection; + } + } + + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @Override + public void onAppsNeedingAudioRoutingChanged(int[] appUids) { + synchronized (mLock) { + if (Arrays.equals(mReroutedAppUids.toArray(), appUids)) { + return; + } + } + + releaseAudioStreams(); + + if (appUids.length == 0) { + return; + } + + createAudioStreams(appUids); + } + + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @Override + public void close() { + mUserRestrictionsDetector.unregister(); + releaseAudioStreams(); + synchronized (mLock) { + mAudioCapture = null; + mAudioInjection = null; + mCaptureFormat = null; + mInjectionFormat = null; + } + } + + @Override + public void onMicrophoneRestrictionChanged(boolean isUnmuteMicDisallowed) { + synchronized (mLock) { + if (mAudioInjection != null) { + mAudioInjection.setSilent(isUnmuteMicDisallowed); + } + } + } + + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + private void createAudioStreams(int[] appUids) { + synchronized (mLock) { + if (mCaptureFormat == null && mInjectionFormat == null) { + throw new IllegalStateException( + "At least one of captureFormat and injectionFormat must be specified."); + } + if (mAudioPolicy != null) { + throw new IllegalStateException( + "Cannot create audio streams while the audio policy is registered. Call " + + "releaseAudioStreams() first to unregister the previous audio " + + "policy." + ); + } + + mReroutedAppUids.clear(); + for (int appUid : appUids) { + mReroutedAppUids.add(appUid); + } + + AudioMix audioRecordMix = null; + AudioMix audioTrackMix = null; + AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext); + if (mCaptureFormat != null) { + audioRecordMix = createAudioRecordMix(mCaptureFormat, appUids); + builder.addMix(audioRecordMix); + } + if (mInjectionFormat != null) { + audioTrackMix = createAudioTrackMix(mInjectionFormat, appUids); + builder.addMix(audioTrackMix); + } + mAudioPolicy = builder.build(); + AudioManager audioManager = mContext.getSystemService(AudioManager.class); + if (audioManager.registerAudioPolicy(mAudioPolicy) == AudioManager.ERROR) { + Log.e(TAG, "Failed to register audio policy!"); + } + + AudioRecord audioRecord = + audioRecordMix != null ? mAudioPolicy.createAudioRecordSink(audioRecordMix) + : null; + AudioTrack audioTrack = + audioTrackMix != null ? mAudioPolicy.createAudioTrackSource(audioTrackMix) + : null; + + if (mAudioCapture != null) { + mAudioCapture.setAudioRecord(audioRecord); + } + if (mAudioInjection != null) { + mAudioInjection.setAudioTrack(audioTrack); + } + } + } + + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + private void releaseAudioStreams() { + synchronized (mLock) { + if (mAudioCapture != null) { + mAudioCapture.setAudioRecord(null); + } + if (mAudioInjection != null) { + mAudioInjection.setAudioTrack(null); + } + mReroutedAppUids.clear(); + if (mAudioPolicy != null) { + AudioManager audioManager = mContext.getSystemService(AudioManager.class); + audioManager.unregisterAudioPolicy(mAudioPolicy); + mAudioPolicy = null; + Log.i(TAG, "AudioPolicy unregistered"); + } + } + } + + @Override + public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) { + if (mCallback != null) { + mExecutor.execute(() -> mCallback.onPlaybackConfigChanged(configs)); + } + } + + @Override + public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) { + if (mCallback != null) { + mExecutor.execute(() -> mCallback.onRecordingConfigChanged(configs)); + } + } + + /** @hide */ + @VisibleForTesting + public IntArray getReroutedAppUids() { + synchronized (mLock) { + return mReroutedAppUids; + } + } + + private static AudioMix createAudioRecordMix(@NonNull AudioFormat audioFormat, int[] appUids) { + AudioMixingRule.Builder builder = new AudioMixingRule.Builder(); + builder.setTargetMixRole(AudioMixingRule.MIX_ROLE_PLAYERS); + for (int uid : appUids) { + builder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid); + } + AudioMixingRule audioMixingRule = builder.allowPrivilegedPlaybackCapture(false).build(); + AudioMix audioMix = + new AudioMix.Builder(audioMixingRule) + .setFormat(audioFormat) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK) + .build(); + return audioMix; + } + + private static AudioMix createAudioTrackMix(@NonNull AudioFormat audioFormat, int[] appUids) { + AudioMixingRule.Builder builder = new AudioMixingRule.Builder(); + builder.setTargetMixRole(AudioMixingRule.MIX_ROLE_INJECTOR); + for (int uid : appUids) { + builder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid); + } + AudioMixingRule audioMixingRule = builder.build(); + AudioMix audioMix = + new AudioMix.Builder(audioMixingRule) + .setFormat(audioFormat) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK) + .build(); + return audioMix; + } +} diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java index 14c3387d47dc..3e544b284d25 100644 --- a/core/java/android/content/AsyncTaskLoader.java +++ b/core/java/android/content/AsyncTaskLoader.java @@ -52,7 +52,7 @@ import java.util.concurrent.Executor; * @param <D> the data type to be loaded. * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> - * {@link android.support.v4.content.AsyncTaskLoader} + * {@link androidx.loader.content.AsyncTaskLoader} */ @Deprecated public abstract class AsyncTaskLoader<D> extends Loader<D> { diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index d46a0c67341f..2a19d37c596b 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -376,7 +376,7 @@ public abstract class BroadcastReceiver { * to run, allowing them to execute for 30 seconds or even a bit more. This is something that * receivers should rarely take advantage of (long work should be punted to another system * facility such as {@link android.app.job.JobScheduler}, {@link android.app.Service}, or - * see especially {@link android.support.v4.app.JobIntentService}), but can be useful in + * see especially {@link androidx.core.app.JobIntentService}), but can be useful in * certain rare cases where it is necessary to do some work as soon as the broadcast is * delivered. Keep in mind that the work you do here will block further broadcasts until * it completes, so taking advantage of this at all excessively can be counter-productive diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 207412511198..0fed733e440c 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -36,6 +36,7 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UiContext; import android.annotation.UserIdInt; +import android.app.Activity; import android.app.ActivityManager; import android.app.BroadcastOptions; import android.app.GameManager; @@ -45,6 +46,8 @@ import android.app.VrManager; import android.app.ambientcontext.AmbientContextManager; import android.app.people.PeopleManager; import android.app.time.TimeManager; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -85,6 +88,7 @@ import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient; import android.view.textclassifier.TextClassificationManager; import android.window.WindowContext; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.compat.IPlatformCompat; import com.android.internal.compat.IPlatformCompatNative; @@ -111,6 +115,19 @@ import java.util.function.Consumer; * broadcasting and receiving intents, etc. */ public abstract class Context { + /** + * After {@link Build.VERSION_CODES#TIRAMISU}, + * {@link #registerComponentCallbacks(ComponentCallbacks)} will add a {@link ComponentCallbacks} + * to {@link Activity} or {@link ContextWrapper#getBaseContext()} instead of always adding to + * {@link #getApplicationContext()}. + * + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + @VisibleForTesting + public static final long OVERRIDABLE_COMPONENT_CALLBACKS = 193247900L; + /** @hide */ @IntDef(flag = true, prefix = { "MODE_" }, value = { MODE_PRIVATE, @@ -163,7 +180,7 @@ public abstract class Context { * {@link BroadcastReceiver}, and {@link android.app.Service}. * There are no guarantees that this access mode will remain on * a file, such as when it goes through a backup and restore. - * @see android.support.v4.content.FileProvider + * @see androidx.core.content.FileProvider * @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION */ @Deprecated @@ -183,7 +200,7 @@ public abstract class Context { * {@link BroadcastReceiver}, and {@link android.app.Service}. * There are no guarantees that this access mode will remain on * a file, such as when it goes through a backup and restore. - * @see android.support.v4.content.FileProvider + * @see androidx.core.content.FileProvider * @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION */ @Deprecated @@ -7058,6 +7075,18 @@ public abstract class Context { public abstract int getDisplayId(); /** + * @return Returns the id of the Display object associated with this Context or + * {@link Display#INVALID_DISPLAY} if no Display has been associated. + * @see #getDisplay() + * @see #getDisplayId() + * + * @hide + */ + public int getAssociatedDisplayId() { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** * @hide */ @SuppressWarnings("HiddenAbstractMethod") diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 98ced6d7ed5a..9adf17367039 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -25,8 +25,6 @@ import android.annotation.UiContext; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.compat.CompatChanges; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -74,16 +72,6 @@ public class ContextWrapper extends Context { Context mBase; /** - * After {@link Build.VERSION_CODES#TIRAMISU}, - * {@link #registerComponentCallbacks(ComponentCallbacks)} will delegate to - * {@link #getBaseContext()} instead of {@link #getApplicationContext()}. - */ - @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) - @VisibleForTesting - static final long COMPONENT_CALLBACK_ON_WRAPPER = 193247900L; - - /** * A list to store {@link ComponentCallbacks} which * passes to {@link #registerComponentCallbacks(ComponentCallbacks)} before * {@link #attachBaseContext(Context)}. @@ -1355,7 +1343,7 @@ public class ContextWrapper extends Context { public void registerComponentCallbacks(ComponentCallbacks callback) { if (mBase != null) { mBase.registerComponentCallbacks(callback); - } else if (!CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) { + } else if (!CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS)) { super.registerComponentCallbacks(callback); synchronized (mLock) { // Also register ComponentCallbacks to ContextWrapper, so we can find the correct @@ -1397,7 +1385,7 @@ public class ContextWrapper extends Context { mCallbacksRegisteredToSuper.remove(callback); } else if (mBase != null) { mBase.unregisterComponentCallbacks(callback); - } else if (CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) { + } else if (CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS)) { // Throw exception for Application that is targeting S-v2+ throw new IllegalStateException("ComponentCallbacks must be unregistered after " + "this ContextWrapper is attached to a base Context."); diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java index fda646c7f99c..cfb0f9500d3c 100644 --- a/core/java/android/content/CursorLoader.java +++ b/core/java/android/content/CursorLoader.java @@ -42,7 +42,7 @@ import java.util.Arrays; * and {@link #setProjection(String[])}. * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> - * {@link android.support.v4.content.CursorLoader} + * {@link androidx.loader.content.CursorLoader} */ @Deprecated public class CursorLoader extends AsyncTaskLoader<Cursor> { diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java index b0555d4ce7e0..afd495b19af6 100644 --- a/core/java/android/content/Loader.java +++ b/core/java/android/content/Loader.java @@ -50,7 +50,7 @@ import java.io.PrintWriter; * @param <D> The result returned when the load is complete * * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> - * {@link android.support.v4.content.Loader} + * {@link androidx.loader.content.Loader} */ @Deprecated public class Loader<D> { @@ -71,7 +71,7 @@ public class Loader<D> { * it is used for you by {@link CursorLoader} to take care of executing * an update when the cursor's backing data changes. * - * @deprecated Use {@link android.support.v4.content.Loader.ForceLoadContentObserver} + * @deprecated Use {@link androidx.loader.content.Loader.ForceLoadContentObserver} */ @Deprecated public final class ForceLoadContentObserver extends ContentObserver { @@ -98,7 +98,7 @@ public class Loader<D> { * be reported to its client. This interface should only be used if a * Loader is not being used in conjunction with LoaderManager. * - * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCompleteListener} + * @deprecated Use {@link androidx.loader.content.Loader.OnLoadCompleteListener} */ @Deprecated public interface OnLoadCompleteListener<D> { @@ -119,7 +119,7 @@ public class Loader<D> { * can schedule the next Loader. This interface should only be used if a * Loader is not being used in conjunction with LoaderManager. * - * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCanceledListener} + * @deprecated Use {@link androidx.loader.content.Loader.OnLoadCanceledListener} */ @Deprecated public interface OnLoadCanceledListener<D> { diff --git a/core/java/android/content/integrity/AppInstallMetadata.java b/core/java/android/content/integrity/AppInstallMetadata.java index 4f38fae271f6..9874890ed09e 100644 --- a/core/java/android/content/integrity/AppInstallMetadata.java +++ b/core/java/android/content/integrity/AppInstallMetadata.java @@ -37,6 +37,8 @@ public final class AppInstallMetadata { private final String mPackageName; // Raw string encoding for the SHA-256 hash of the certificate of the app. private final List<String> mAppCertificates; + // Raw string encoding for the SHA-256 hash of the certificate lineage/history of the app. + private final List<String> mAppCertificateLineage; private final String mInstallerName; // Raw string encoding for the SHA-256 hash of the certificate of the installer. private final List<String> mInstallerCertificates; @@ -52,6 +54,7 @@ public final class AppInstallMetadata { private AppInstallMetadata(Builder builder) { this.mPackageName = builder.mPackageName; this.mAppCertificates = builder.mAppCertificates; + this.mAppCertificateLineage = builder.mAppCertificateLineage; this.mInstallerName = builder.mInstallerName; this.mInstallerCertificates = builder.mInstallerCertificates; this.mVersionCode = builder.mVersionCode; @@ -74,6 +77,11 @@ public final class AppInstallMetadata { } @NonNull + public List<String> getAppCertificateLineage() { + return mAppCertificateLineage; + } + + @NonNull public String getInstallerName() { return mInstallerName; } @@ -126,6 +134,7 @@ public final class AppInstallMetadata { + " %b, StampVerified = %b, StampTrusted = %b, StampCert = %s }", mPackageName, mAppCertificates, + mAppCertificateLineage, mInstallerName == null ? "null" : mInstallerName, mInstallerCertificates == null ? "null" : mInstallerCertificates, mVersionCode, @@ -140,6 +149,7 @@ public final class AppInstallMetadata { public static final class Builder { private String mPackageName; private List<String> mAppCertificates; + private List<String> mAppCertificateLineage; private String mInstallerName; private List<String> mInstallerCertificates; private long mVersionCode; @@ -192,6 +202,20 @@ public final class AppInstallMetadata { } /** + * Set the list of (old and new) certificates used for signing the app to be installed. + * + * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate + * lineage/history of the app. + * + * @see AppInstallMetadata#getAppCertificateLineage() + */ + @NonNull + public Builder setAppCertificateLineage(@NonNull List<String> appCertificateLineage) { + this.mAppCertificateLineage = Objects.requireNonNull(appCertificateLineage); + return this; + } + + /** * Set name of the installer installing the app. * * @see AppInstallMetadata#getInstallerName() @@ -294,6 +318,7 @@ public final class AppInstallMetadata { public AppInstallMetadata build() { Objects.requireNonNull(mPackageName); Objects.requireNonNull(mAppCertificates); + Objects.requireNonNull(mAppCertificateLineage); return new AppInstallMetadata(this); } } diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java index e3598004d277..f888813135be 100644 --- a/core/java/android/content/integrity/AtomicFormula.java +++ b/core/java/android/content/integrity/AtomicFormula.java @@ -56,6 +56,7 @@ public abstract class AtomicFormula extends IntegrityFormula { PRE_INSTALLED, STAMP_TRUSTED, STAMP_CERTIFICATE_HASH, + APP_CERTIFICATE_LINEAGE, }) @Retention(RetentionPolicy.SOURCE) public @interface Key {} @@ -122,6 +123,13 @@ public abstract class AtomicFormula extends IntegrityFormula { */ public static final int STAMP_CERTIFICATE_HASH = 7; + /** + * SHA-256 of a certificate in the signing lineage of the app. + * + * <p>Can only be used in {@link StringAtomicFormula}. + */ + public static final int APP_CERTIFICATE_LINEAGE = 8; + public static final int EQ = 0; public static final int GT = 1; public static final int GTE = 2; @@ -225,6 +233,11 @@ public abstract class AtomicFormula extends IntegrityFormula { } @Override + public boolean isAppCertificateLineageFormula() { + return false; + } + + @Override public boolean isInstallerFormula() { return false; } @@ -314,7 +327,8 @@ public abstract class AtomicFormula extends IntegrityFormula { || key == APP_CERTIFICATE || key == INSTALLER_CERTIFICATE || key == INSTALLER_NAME - || key == STAMP_CERTIFICATE_HASH, + || key == STAMP_CERTIFICATE_HASH + || key == APP_CERTIFICATE_LINEAGE, "Key %s cannot be used with StringAtomicFormula", keyToString(key)); mValue = null; mIsHashedValue = null; @@ -335,7 +349,8 @@ public abstract class AtomicFormula extends IntegrityFormula { || key == APP_CERTIFICATE || key == INSTALLER_CERTIFICATE || key == INSTALLER_NAME - || key == STAMP_CERTIFICATE_HASH, + || key == STAMP_CERTIFICATE_HASH + || key == APP_CERTIFICATE_LINEAGE, "Key %s cannot be used with StringAtomicFormula", keyToString(key)); mValue = value; mIsHashedValue = isHashed; @@ -348,8 +363,9 @@ public abstract class AtomicFormula extends IntegrityFormula { * <p>The value will be automatically hashed with SHA256 and the hex digest will be computed * when the key is PACKAGE_NAME or INSTALLER_NAME and the value is more than 32 characters. * - * <p>The APP_CERTIFICATES, INSTALLER_CERTIFICATES, and STAMP_CERTIFICATE_HASH are always - * delivered in hashed form. So the isHashedValue is set to true by default. + * <p>The APP_CERTIFICATES, INSTALLER_CERTIFICATES, STAMP_CERTIFICATE_HASH and + * APP_CERTIFICATE_LINEAGE are always delivered in hashed form. So the isHashedValue is set + * to true by default. * * @throws IllegalArgumentException if {@code key} cannot be used with string value. */ @@ -360,13 +376,15 @@ public abstract class AtomicFormula extends IntegrityFormula { || key == APP_CERTIFICATE || key == INSTALLER_CERTIFICATE || key == INSTALLER_NAME - || key == STAMP_CERTIFICATE_HASH, + || key == STAMP_CERTIFICATE_HASH + || key == APP_CERTIFICATE_LINEAGE, "Key %s cannot be used with StringAtomicFormula", keyToString(key)); mValue = hashValue(key, value); mIsHashedValue = (key == APP_CERTIFICATE || key == INSTALLER_CERTIFICATE - || key == STAMP_CERTIFICATE_HASH) + || key == STAMP_CERTIFICATE_HASH + || key == APP_CERTIFICATE_LINEAGE) || !mValue.equals(value); } @@ -409,6 +427,11 @@ public abstract class AtomicFormula extends IntegrityFormula { } @Override + public boolean isAppCertificateLineageFormula() { + return getKey() == APP_CERTIFICATE_LINEAGE; + } + + @Override public boolean isInstallerFormula() { return getKey() == INSTALLER_NAME || getKey() == INSTALLER_CERTIFICATE; } @@ -474,6 +497,8 @@ public abstract class AtomicFormula extends IntegrityFormula { return Collections.singletonList(appInstallMetadata.getInstallerName()); case AtomicFormula.STAMP_CERTIFICATE_HASH: return Collections.singletonList(appInstallMetadata.getStampCertificateHash()); + case AtomicFormula.APP_CERTIFICATE_LINEAGE: + return appInstallMetadata.getAppCertificateLineage(); default: throw new IllegalStateException( "Unexpected key in StringAtomicFormula: " + key); @@ -577,6 +602,11 @@ public abstract class AtomicFormula extends IntegrityFormula { } @Override + public boolean isAppCertificateLineageFormula() { + return false; + } + + @Override public boolean isInstallerFormula() { return false; } @@ -660,6 +690,8 @@ public abstract class AtomicFormula extends IntegrityFormula { return "STAMP_TRUSTED"; case STAMP_CERTIFICATE_HASH: return "STAMP_CERTIFICATE_HASH"; + case APP_CERTIFICATE_LINEAGE: + return "APP_CERTIFICATE_LINEAGE"; default: throw new IllegalArgumentException("Unknown key " + key); } @@ -686,6 +718,7 @@ public abstract class AtomicFormula extends IntegrityFormula { || key == INSTALLER_CERTIFICATE || key == PRE_INSTALLED || key == STAMP_TRUSTED - || key == STAMP_CERTIFICATE_HASH; + || key == STAMP_CERTIFICATE_HASH + || key == APP_CERTIFICATE_LINEAGE; } } diff --git a/core/java/android/content/integrity/CompoundFormula.java b/core/java/android/content/integrity/CompoundFormula.java index 1ffabd03cc26..23ee1d99bb5e 100644 --- a/core/java/android/content/integrity/CompoundFormula.java +++ b/core/java/android/content/integrity/CompoundFormula.java @@ -137,6 +137,11 @@ public final class CompoundFormula extends IntegrityFormula implements Parcelabl } @Override + public boolean isAppCertificateLineageFormula() { + return getFormulas().stream().anyMatch(formula -> formula.isAppCertificateLineageFormula()); + } + + @Override public boolean isInstallerFormula() { return getFormulas().stream().anyMatch(formula -> formula.isInstallerFormula()); } diff --git a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java index 9d37299e2373..5bcbef69c7af 100644 --- a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java +++ b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java @@ -73,6 +73,11 @@ public class InstallerAllowedByManifestFormula extends IntegrityFormula implemen } @Override + public boolean isAppCertificateLineageFormula() { + return false; + } + + @Override public boolean isInstallerFormula() { return true; } diff --git a/core/java/android/content/integrity/IntegrityFormula.java b/core/java/android/content/integrity/IntegrityFormula.java index d965ef5c71a8..9e2a3321f6e4 100644 --- a/core/java/android/content/integrity/IntegrityFormula.java +++ b/core/java/android/content/integrity/IntegrityFormula.java @@ -49,14 +49,23 @@ public abstract class IntegrityFormula { } /** - * Returns an integrity formula that checks if the app certificates contain {@code - * appCertificate}. + * Returns an integrity formula that checks if the app certificates contain the string + * provided by the appCertificate parameter. */ @NonNull public static IntegrityFormula certificatesContain(@NonNull String appCertificate) { return new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE, appCertificate); } + /** + * Returns an integrity formula that checks if the app certificate lineage contains the + * string provided by the appCertificate parameter. + */ + @NonNull + public static IntegrityFormula certificateLineageContains(@NonNull String appCertificate) { + return new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE_LINEAGE, appCertificate); + } + /** Returns an integrity formula that checks the equality to a version code. */ @NonNull public static IntegrityFormula versionCodeEquals(@NonNull long versionCode) { @@ -187,6 +196,14 @@ public abstract class IntegrityFormula { public abstract boolean isAppCertificateFormula(); /** + * Returns true when the formula (or one of its atomic formulas) has app certificate lineage as + * key. + * + * @hide + */ + public abstract boolean isAppCertificateLineageFormula(); + + /** * Returns true when the formula (or one of its atomic formulas) has installer package name or * installer certificate as key. * diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 8bea006071bf..0673b3ad5b0a 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -18,6 +18,8 @@ package android.content.pm; import android.annotation.FloatRange; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; import android.app.Activity; import android.app.compat.CompatChanges; @@ -35,10 +37,16 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import android.util.ArraySet; import android.util.Printer; +import com.android.internal.util.Parcelling; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.Locale; +import java.util.Set; /** * Information you can retrieve about a particular application @@ -48,6 +56,9 @@ import java.lang.annotation.RetentionPolicy; */ public class ActivityInfo extends ComponentInfo implements Parcelable { + private static final Parcelling.BuiltIn.ForStringSet sForStringSet = + Parcelling.Cache.getOrCreate(Parcelling.BuiltIn.ForStringSet.class); + // NOTE: When adding new data members be sure to update the copy-constructor, Parcel // constructor, and writeToParcel. @@ -525,6 +536,13 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { public static final int FLAG_PREFER_MINIMAL_POST_PROCESSING = 0x2000000; /** + * Bit in {@link #flags}: If set, indicates that the activity can be embedded by untrusted + * hosts. In this case the interactions with and visibility of the embedded activity may be + * limited. Set from the {@link android.R.attr#allowUntrustedActivityEmbedding} attribute. + */ + public static final int FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING = 0x10000000; + + /** * @hide Bit in {@link #flags}: If set, this component will only be seen * by the system user. Only works with broadcast receivers. Set from the * android.R.attr#systemUserOnly attribute. @@ -561,7 +579,8 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * {@link #FLAG_STATE_NOT_NEEDED}, {@link #FLAG_EXCLUDE_FROM_RECENTS}, * {@link #FLAG_ALLOW_TASK_REPARENTING}, {@link #FLAG_NO_HISTORY}, * {@link #FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS}, - * {@link #FLAG_HARDWARE_ACCELERATED}, {@link #FLAG_SINGLE_USER}. + * {@link #FLAG_HARDWARE_ACCELERATED}, {@link #FLAG_SINGLE_USER}, + * {@link #FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING}. */ public int flags; @@ -1080,6 +1099,13 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { private static final long CHECK_MIN_WIDTH_HEIGHT_FOR_MULTI_WINDOW = 197654537L; /** + * Optional set of a certificates identifying apps that are allowed to embed this activity. From + * the "knownActivityEmbeddingCerts" attribute. + */ + @Nullable + private Set<String> mKnownActivityEmbeddingCerts; + + /** * Convert Java change bits to native. * * @hide @@ -1227,6 +1253,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { launchMode = orig.launchMode; documentLaunchMode = orig.documentLaunchMode; permission = orig.permission; + mKnownActivityEmbeddingCerts = orig.mKnownActivityEmbeddingCerts; taskAffinity = orig.taskAffinity; targetActivity = orig.targetActivity; flags = orig.flags; @@ -1442,6 +1469,31 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { return mMinAspectRatio; } + /** + * Gets the trusted host certificate digests of apps that are allowed to embed this activity. + * The digests are computed using the SHA-256 digest algorithm. + * @see android.R.attr#knownActivityEmbeddingCerts + */ + @NonNull + public Set<String> getKnownActivityEmbeddingCerts() { + return mKnownActivityEmbeddingCerts == null ? Collections.emptySet() + : mKnownActivityEmbeddingCerts; + } + + /** + * Sets the trusted host certificates of apps that are allowed to embed this activity. + * @see #getKnownActivityEmbeddingCerts() + * @hide + */ + public void setKnownActivityEmbeddingCerts(@NonNull Set<String> knownActivityEmbeddingCerts) { + // Convert the provided digest to upper case for consistent Set membership + // checks when verifying the signing certificate digests of requesting apps. + mKnownActivityEmbeddingCerts = new ArraySet<>(); + for (String knownCert : knownActivityEmbeddingCerts) { + mKnownActivityEmbeddingCerts.add(knownCert.toUpperCase(Locale.US)); + } + } + private boolean isChangeEnabled(long changeId) { return CompatChanges.isChangeEnabled(changeId, applicationInfo.packageName, UserHandle.getUserHandleForUid(applicationInfo.uid)); @@ -1573,6 +1625,9 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { if (supportsSizeChanges) { pw.println(prefix + "supportsSizeChanges=true"); } + if (mKnownActivityEmbeddingCerts != null) { + pw.println(prefix + "knownActivityEmbeddingCerts=" + mKnownActivityEmbeddingCerts); + } super.dumpBack(pw, prefix, dumpFlags); } @@ -1618,6 +1673,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { dest.writeFloat(mMaxAspectRatio); dest.writeFloat(mMinAspectRatio); dest.writeBoolean(supportsSizeChanges); + sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags); } /** @@ -1739,6 +1795,10 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { mMaxAspectRatio = source.readFloat(); mMinAspectRatio = source.readFloat(); supportsSizeChanges = source.readBoolean(); + mKnownActivityEmbeddingCerts = sForStringSet.unparcel(source); + if (mKnownActivityEmbeddingCerts.isEmpty()) { + mKnownActivityEmbeddingCerts = null; + } } /** diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 567f649ea762..2528e16ce7b7 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -35,6 +35,7 @@ import android.os.Parcelable; import android.os.UserHandle; import android.os.storage.StorageManager; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Printer; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; @@ -52,7 +53,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Locale; import java.util.Objects; +import java.util.Set; import java.util.UUID; /** @@ -62,6 +65,8 @@ import java.util.UUID; */ public class ApplicationInfo extends PackageItemInfo implements Parcelable { private static ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class); + private static final Parcelling.BuiltIn.ForStringSet sForStringSet = + Parcelling.Cache.getOrCreate(Parcelling.BuiltIn.ForStringSet.class); /** * Default task affinity of all activities in this application. See @@ -1550,6 +1555,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ private int localeConfigRes; + /** + * Optional set of a certificates identifying apps that are allowed to embed activities of this + * application. From the "knownActivityEmbeddingCerts" attribute. + */ + @Nullable + private Set<String> mKnownActivityEmbeddingCerts; + public void dump(Printer pw, String prefix) { dump(pw, prefix, DUMP_FLAG_ALL); } @@ -1673,6 +1685,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } } pw.println(prefix + "createTimestamp=" + createTimestamp); + if (mKnownActivityEmbeddingCerts != null) { + pw.println(prefix + "knownActivityEmbeddingCerts=" + mKnownActivityEmbeddingCerts); + } super.dumpBack(pw, prefix); } @@ -1787,6 +1802,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } proto.end(detailToken); } + if (!ArrayUtils.isEmpty(mKnownActivityEmbeddingCerts)) { + for (String knownCert : mKnownActivityEmbeddingCerts) { + proto.write(ApplicationInfoProto.KNOWN_ACTIVITY_EMBEDDING_CERTS, knownCert); + } + } proto.end(token); } @@ -1837,6 +1857,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { super(orig); taskAffinity = orig.taskAffinity; permission = orig.permission; + mKnownActivityEmbeddingCerts = orig.mKnownActivityEmbeddingCerts; processName = orig.processName; className = orig.className; theme = orig.theme; @@ -2006,6 +2027,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } } dest.writeInt(localeConfigRes); + sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags); } public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR @@ -2102,6 +2124,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } } localeConfigRes = source.readInt(); + mKnownActivityEmbeddingCerts = sForStringSet.unparcel(source); + if (mKnownActivityEmbeddingCerts.isEmpty()) { + mKnownActivityEmbeddingCerts = null; + } } /** @@ -2658,7 +2684,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return localeConfigRes; } - /** * List of all shared libraries this application is linked against. This * list will only be set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES @@ -2675,4 +2700,29 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return sharedLibraryInfos; } + /** + * Gets the trusted host certificate digests of apps that are allowed to embed activities of + * this application. The digests are computed using the SHA-256 digest algorithm. + * @see android.R.attr#knownActivityEmbeddingCerts + */ + @NonNull + public Set<String> getKnownActivityEmbeddingCerts() { + return mKnownActivityEmbeddingCerts == null ? Collections.emptySet() + : mKnownActivityEmbeddingCerts; + } + + /** + * Sets the trusted host certificates of apps that are allowed to embed activities of this + * application. + * @see #getKnownActivityEmbeddingCerts() + * @hide + */ + public void setKnownActivityEmbeddingCerts(@NonNull Set<String> knownActivityEmbeddingCerts) { + // Convert the provided digest to upper case for consistent Set membership + // checks when verifying the signing certificate digests of requesting apps. + mKnownActivityEmbeddingCerts = new ArraySet<>(); + for (String knownCert : knownActivityEmbeddingCerts) { + mKnownActivityEmbeddingCerts.add(knownCert.toUpperCase(Locale.US)); + } + } } diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index cb8988eb5b92..08cfbf76a040 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -58,7 +58,7 @@ interface ILauncherApps { void startActivityAsUser(in IApplicationThread caller, String callingPackage, String callingFeatureId, in ComponentName component, in Rect sourceBounds, in Bundle opts, in UserHandle user); - PendingIntent getActivityLaunchIntent(in ComponentName component, in Bundle opts, + PendingIntent getActivityLaunchIntent(String callingPackage, in ComponentName component, in UserHandle user); void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage, String callingFeatureId, in ComponentName component, in Rect sourceBounds, diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index e35c2f48038d..301d1bbc8e9d 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -749,24 +749,29 @@ public class LauncherApps { } /** - * Returns a PendingIntent that would start the same activity started from - * {@link #startMainActivity(ComponentName, UserHandle, Rect, Bundle)}. + * Returns a mutable PendingIntent that would start the same activity started from + * {@link #startMainActivity(ComponentName, UserHandle, Rect, Bundle)}. The caller needs to + * take care in ensuring that the mutable intent returned is not passed to untrusted parties. * * @param component The ComponentName of the activity to launch * @param startActivityOptions This parameter is no longer supported * @param user The UserHandle of the profile * @hide */ + @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) @Nullable public PendingIntent getMainActivityLaunchIntent(@NonNull ComponentName component, @Nullable Bundle startActivityOptions, @NonNull UserHandle user) { + if (mContext.checkSelfPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) + != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "Only allowed for recents."); + } logErrorForInvalidProfileAccess(user); if (DEBUG) { Log.i(TAG, "GetMainActivityLaunchIntent " + component + " " + user); } try { - // due to b/209607104, startActivityOptions will be ignored - return mService.getActivityLaunchIntent(component, null /* opts */, user); + return mService.getActivityLaunchIntent(mContext.getPackageName(), component, user); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index ce549c380af8..dc79ee6b09e2 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3797,6 +3797,16 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device supports expanded picture-in-picture multi-window mode. + * + * @see android.app.PictureInPictureParams.Builder#setExpandedAspectRatio + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_EXPANDED_PICTURE_IN_PICTURE + = "android.software.expanded_picture_in_picture"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: * The device supports running activities on secondary displays. */ @SdkConstant(SdkConstantType.FEATURE) @@ -4342,6 +4352,21 @@ public abstract class PackageManager { = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS"; /** + * Indicates that the package requesting permissions has legacy access for some permissions, + * or had it, but it was recently revoked. These request dialogs may show different text, + * indicating that the app is requesting continued access to a permission. Will be cleared + * from any permission request intent, if set by a non-system server app. + * <p> + * <strong>Type:</strong> String[] + * </p> + * + * @hide + */ + @SystemApi + public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES + = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES"; + + /** * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of * {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}. This extra names the package which provides * the existing definition for the permission. diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index 7dbfd08310be..70b90e6fdb1a 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -532,7 +532,7 @@ public class ShortcutManager { * app. * * <p><b>Note:</b> See also the support library counterpart - * {@link android.support.v4.content.pm.ShortcutManagerCompat#isRequestPinShortcutSupported( + * {@link androidx.core.content.pm.ShortcutManagerCompat#isRequestPinShortcutSupported( * Context)}, which supports Android versions lower than {@link VERSION_CODES#O} using the * legacy private intent {@code com.android.launcher.action.INSTALL_SHORTCUT}. * @@ -561,7 +561,7 @@ public class ShortcutManager { * previous requests. * * <p><b>Note:</b> See also the support library counterpart - * {@link android.support.v4.content.pm.ShortcutManagerCompat#requestPinShortcut( + * {@link androidx.core.content.pm.ShortcutManagerCompat#requestPinShortcut( * Context, ShortcutInfoCompat, IntentSender)}, * which supports Android versions lower than {@link VERSION_CODES#O} using the * legacy private intent {@code com.android.launcher.action.INSTALL_SHORTCUT}. @@ -767,7 +767,7 @@ public class ShortcutManager { * order to make sure shortcuts exist and are up-to-date, without the need to explicitly handle * the shortcut count limit. * @see android.app.NotificationManager#notify(int, Notification) - * @see Notification.Builder#setShortcutId(String) + * @see android.app.Notification.Builder#setShortcutId(String) * * <p>If {@link #getMaxShortcutCountPerActivity()} is already reached, an existing shortcut with * the lowest rank will be removed to add space for the new shortcut. diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java index 515a0098b6c6..ac1bcf3186be 100644 --- a/core/java/android/hardware/HardwareBuffer.java +++ b/core/java/android/hardware/HardwareBuffer.java @@ -131,7 +131,8 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { /** Usage: The buffer will be written to by the GPU */ public static final long USAGE_GPU_COLOR_OUTPUT = 1 << 9; /** - * The buffer will be used as a composer HAL overlay layer. + * The buffer will be used as a hardware composer overlay layer. That is, it will be displayed + * using the system compositor via {@link SurfaceControl} * * This flag is currently only needed when using * {@link android.view.SurfaceControl.Transaction#setBuffer(SurfaceControl, HardwareBuffer)} diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index a06566c24019..524fe795cb75 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -3455,6 +3455,30 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<android.hardware.camera2.params.MandatoryStreamCombination[]>("android.scaler.mandatoryTenBitOutputStreamCombinations", android.hardware.camera2.params.MandatoryStreamCombination[].class); /** + * <p>An array of mandatory stream combinations which are applicable when device lists + * {@code PREVIEW_STABILIZATION} in {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES android.control.availableVideoStabilizationModes}. + * This is an app-readable conversion of the maximum resolution mandatory stream combination + * {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p> + * <p>The array of + * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is + * generated according to the documented + * {@link android.hardware.camera2.CameraDevice#createCaptureSession guideline} for each + * device which supports {@code PREVIEW_STABILIZATION} + * Clients can use the array as a quick reference to find an appropriate camera stream + * combination. + * The mandatory stream combination array will be {@code null} in case the device does not + * list {@code PREVIEW_STABILIZATION} in {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES android.control.availableVideoStabilizationModes}.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES + */ + @PublicKey + @NonNull + @SyntheticKey + public static final Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_PREVIEW_STABILIZATION_OUTPUT_STREAM_COMBINATIONS = + new Key<android.hardware.camera2.params.MandatoryStreamCombination[]>("android.scaler.mandatoryPreviewStabilizationOutputStreamCombinations", android.hardware.camera2.params.MandatoryStreamCombination[].class); + + /** * <p>Whether the camera device supports multi-resolution input or output streams</p> * <p>A logical multi-camera or an ultra high resolution camera may support multi-resolution * input or output streams. With multi-resolution output streams, the camera device is able diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 1a42eaf541ca..8f42b1f6f355 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -883,6 +883,27 @@ public abstract class CameraDevice implements AutoCloseable { * </table><br> * </p> * + *<p> For devices where {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES} + * includes {@link CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION}, + * the following stream combinations are guaranteed, + * for CaptureRequests where {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE} is set to + * {@link CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION} <p> + * <table> + * <tr><th colspan="7">Preview stabilization guaranteed stream configurations</th></tr> + * <tr><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th rowspan="2">Sample use case(s)</th> </tr> + * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th></tr> + * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code RECORD}</td><td colspan="4" id="rb"></td> <td>Stabilized preview, GPU video processing, or no-preview stabilized video recording.</td> </tr> + * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG / YUV}</td><td id="rb">{@code MAXIMUM }</td><td>Standard still imaging with stabilized preview.</td> </tr> + * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV / YUV}</td><td id="rb">{@code RECORD }</td><td>High-resolution recording with stabilized preview and recording stream.</td> </tr> + * </table><br> + * <p> + * For the maximum size column, PREVIEW refers to the best size match to the device's screen + * resolution, or to 1080p (1920x1080), whichever is smaller. RECORD refers to the camera + * device's maximum supported recording resolution, as determined by + * {@link android.media.CamcorderProfile}. MAXIMUM refers to the camera device's maximum output + * resolution for that format or target from {@link StreamConfigurationMap#getOutputSizes(int)}. + * </p> + * * <p>Since the capabilities of camera devices vary greatly, a given camera device may support * target combinations with sizes outside of these guarantees, but this can only be tested for * by calling {@link #isSessionConfigurationSupported} or attempting to create a session with diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 9b67633fe72b..4fb496d66fbd 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -334,6 +334,7 @@ public class CameraMetadataNative implements Parcelable { private static final int MANDATORY_STREAM_CONFIGURATIONS_CONCURRENT = 2; private static final int MANDATORY_STREAM_CONFIGURATIONS_10BIT = 3; private static final int MANDATORY_STREAM_CONFIGURATIONS_USE_CASE = 4; + private static final int MANDATORY_STREAM_CONFIGURATIONS_PREVIEW_STABILIZATION = 5; private static String translateLocationProviderToProcess(final String provider) { if (provider == null) { @@ -709,6 +710,15 @@ public class CameraMetadataNative implements Parcelable { return (T) metadata.getMandatoryUseCaseStreamCombinations(); } }); + sGetCommandMap.put( + CameraCharacteristics.SCALER_MANDATORY_PREVIEW_STABILIZATION_OUTPUT_STREAM_COMBINATIONS.getNativeKey(), + new GetCommand() { + @Override + @SuppressWarnings("unchecked") + public <T> T getValue(CameraMetadataNative metadata, Key<T> key) { + return (T) metadata.getMandatoryPreviewStabilizationStreamCombinations(); + } + }); sGetCommandMap.put( CameraCharacteristics.CONTROL_MAX_REGIONS_AE.getNativeKey(), new GetCommand() { @@ -1400,6 +1410,24 @@ public class CameraMetadataNative implements Parcelable { CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE); } + private boolean isPreviewStabilizationSupported() { + boolean ret = false; + + int[] videoStabilizationModes = + getBase(CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES); + if (videoStabilizationModes == null) { + return false; + } + for (int mode : videoStabilizationModes) { + if (mode == CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION) { + ret = true; + break; + } + } + + return ret; + } + private MandatoryStreamCombination[] getMandatoryStreamCombinationsHelper( int mandatoryStreamsType) { int[] capabilities = getBase(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); @@ -1411,7 +1439,7 @@ public class CameraMetadataNative implements Parcelable { int hwLevel = getBase(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); MandatoryStreamCombination.Builder build = new MandatoryStreamCombination.Builder( mCameraId, hwLevel, mDisplaySize, caps, getStreamConfigurationMap(), - getStreamConfigurationMapMaximumResolution()); + getStreamConfigurationMapMaximumResolution(), isPreviewStabilizationSupported()); List<MandatoryStreamCombination> combs = null; switch (mandatoryStreamsType) { @@ -1427,6 +1455,9 @@ public class CameraMetadataNative implements Parcelable { case MANDATORY_STREAM_CONFIGURATIONS_USE_CASE: combs = build.getAvailableMandatoryStreamUseCaseCombinations(); break; + case MANDATORY_STREAM_CONFIGURATIONS_PREVIEW_STABILIZATION: + combs = build.getAvailableMandatoryPreviewStabilizedStreamCombinations(); + break; default: combs = build.getAvailableMandatoryStreamCombinations(); } @@ -1464,6 +1495,11 @@ public class CameraMetadataNative implements Parcelable { return getMandatoryStreamCombinationsHelper(MANDATORY_STREAM_CONFIGURATIONS_USE_CASE); } + private MandatoryStreamCombination[] getMandatoryPreviewStabilizationStreamCombinations() { + return getMandatoryStreamCombinationsHelper( + MANDATORY_STREAM_CONFIGURATIONS_PREVIEW_STABILIZATION); + } + private StreamConfigurationMap getStreamConfigurationMap() { StreamConfiguration[] configurations = getBase( CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS); diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java index 0d93c985b793..8c0dcfcf0693 100644 --- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java +++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java @@ -1262,6 +1262,50 @@ public final class MandatoryStreamCombination { "Preview, in-application image processing, and YUV still image capture"), }; + private static StreamCombinationTemplate sPreviewStabilizedStreamCombinations[] = { + // 1 stream combinations + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD)}, + "Stabilized preview, GPU video processing, or no-preview stabilized recording"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD)}, + "Stabilized preview, GPU video processing, or no-preview stabilized recording"), + //2 stream combinations + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)}, + "Standard JPEG still imaging with stabilized preview"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)}, + "Standard YUV still imaging with stabilized preview"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW)}, + "Standard YUV still imaging with stabilized in-app image processing stream"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW)}, + "Standard JPEG still imaging with stabilized in-app image processing stream"), + + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)}, + "High-resolution video recording with preview both streams stabilized"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW)}, + "High-resolution video recording with preview both streams stabilized"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW)}, + "High-resolution video recording with preview both streams stabilized"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD), + new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)}, + "High-resolution video recording with preview both streams stabilized"), + }; + /** * Helper builder class to generate a list of available mandatory stream combinations. * @hide @@ -1273,6 +1317,7 @@ public final class MandatoryStreamCombination { private StreamConfigurationMap mStreamConfigMap; private StreamConfigurationMap mStreamConfigMapMaximumResolution; private boolean mIsHiddenPhysicalCamera; + private boolean mIsPreviewStabilizationSupported; private final Size kPreviewSizeBound = new Size(1920, 1088); @@ -1289,7 +1334,7 @@ public final class MandatoryStreamCombination { */ public Builder(int cameraId, int hwLevel, @NonNull Size displaySize, @NonNull List<Integer> capabilities, @NonNull StreamConfigurationMap sm, - StreamConfigurationMap smMaxResolution) { + StreamConfigurationMap smMaxResolution, boolean previewStabilization) { mCameraId = cameraId; mDisplaySize = displaySize; mCapabilities = capabilities; @@ -1298,24 +1343,12 @@ public final class MandatoryStreamCombination { mHwLevel = hwLevel; mIsHiddenPhysicalCamera = CameraManager.isHiddenPhysicalCamera(Integer.toString(mCameraId)); + mIsPreviewStabilizationSupported = previewStabilization; } - /** - * Retrieve a list of all available mandatory 10-bit output capable stream combinations. - * - * @return a non-modifiable list of supported mandatory 10-bit capable stream combinations, - * null in case device is not 10-bit output capable. - */ - public @NonNull List<MandatoryStreamCombination> - getAvailableMandatory10BitStreamCombinations() { - // Since 10-bit streaming support is optional, we mandate these stream - // combinations regardless of camera device capabilities. - - StreamCombinationTemplate []chosenStreamCombinations = s10BitOutputStreamCombinations; - if (!is10BitOutputSupported()) { - Log.v(TAG, "Device is not able to output 10-bit!"); - return null; - } + private @Nullable List<MandatoryStreamCombination> + getAvailableMandatoryStreamCombinationsInternal( + StreamCombinationTemplate []chosenStreamCombinations, boolean s10Bit) { HashMap<Pair<SizeThreshold, Integer>, List<Size>> availableSizes = enumerateAvailableSizes(); @@ -1334,7 +1367,7 @@ public final class MandatoryStreamCombination { Pair<SizeThreshold, Integer> pair; pair = new Pair<>(template.mSizeThreshold, new Integer(template.mFormat)); sizes = availableSizes.get(pair); - if (template.mFormat == ImageFormat.YCBCR_P010) { + if (s10Bit && template.mFormat == ImageFormat.YCBCR_P010) { // Make sure that exactly the same 10 and 8-bit YUV streams sizes are // supported pair = new Pair<>(template.mSizeThreshold, @@ -1354,7 +1387,8 @@ public final class MandatoryStreamCombination { streamInfo = new MandatoryStreamInformation(sizes, template.mFormat, isMaximumSize, /*isInput*/ false, /*isUltraHighResolution*/ false, - /*is10BitCapable*/ template.mFormat != ImageFormat.JPEG); + /*is10BitCapable*/ s10Bit ? template.mFormat != ImageFormat.JPEG : + false); } catch (IllegalArgumentException e) { Log.e(TAG, "No available sizes found for format: " + template.mFormat + " size threshold: " + template.mSizeThreshold + " combination: " + @@ -1381,6 +1415,52 @@ public final class MandatoryStreamCombination { } /** + * Retrieve a list of all available mandatory stream combinations for devices supporting + * preview stabilization. + * + * @return a non-modifiable list of supported mandatory stream combinations on which + * preview stabilization is supported., + * null in case device is not 10-bit output capable. + */ + public @Nullable List<MandatoryStreamCombination> + getAvailableMandatoryPreviewStabilizedStreamCombinations() { + // Since preview stabilization support is optional, we mandate these stream + // combinations regardless of camera device capabilities. + + StreamCombinationTemplate []chosenStreamCombinations = + sPreviewStabilizedStreamCombinations; + + if (mIsPreviewStabilizationSupported) { + Log.v(TAG, "Device does not support preview stabilization"); + return null; + } + + return getAvailableMandatoryStreamCombinationsInternal(chosenStreamCombinations, + /*10bit*/false); + } + + + /** + * Retrieve a list of all available mandatory 10-bit output capable stream combinations. + * + * @return a non-modifiable list of supported mandatory 10-bit capable stream combinations, + * null in case device is not 10-bit output capable. + */ + public @Nullable List<MandatoryStreamCombination> + getAvailableMandatory10BitStreamCombinations() { + // Since 10-bit streaming support is optional, we mandate these stream + // combinations regardless of camera device capabilities. + + StreamCombinationTemplate []chosenStreamCombinations = s10BitOutputStreamCombinations; + if (!is10BitOutputSupported()) { + Log.v(TAG, "Device is not able to output 10-bit!"); + return null; + } + return getAvailableMandatoryStreamCombinationsInternal(chosenStreamCombinations, + /*10bit*/true); + } + + /** * Retrieve a list of all available mandatory stream combinations with stream use cases. * when the camera device has {@link * CameraMetdata.REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE} capability. diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index f67a5b44a8e9..80937640e094 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -264,6 +264,44 @@ public final class OutputConfiguration implements Parcelable { public @interface StreamUseCase {}; /** + * Automatic mirroring based on camera facing + * + * <p>This is the default mirroring mode for the camera device. With this mode, + * the camera output is mirrored horizontally for front-facing cameras. There is + * no mirroring for rear-facing and external cameras.</p> + */ + public static final int MIRROR_MODE_AUTO = 0; + + /** + * No mirror transform is applied + * + * <p>No mirroring is applied to the camera output regardless of the camera facing.</p> + */ + public static final int MIRROR_MODE_NONE = 1; + + /** + * Camera output is mirrored horizontally + * + * <p>The camera output is mirrored horizontally, the same behavior as in AUTO mode for + * front facing camera.</p> + */ + public static final int MIRROR_MODE_H = 2; + + /** + * Camera output is mirrored vertically + */ + public static final int MIRROR_MODE_V = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"MIRROR_MODE_"}, value = + {MIRROR_MODE_AUTO, + MIRROR_MODE_NONE, + MIRROR_MODE_H, + MIRROR_MODE_V}) + public @interface MirrorMode {}; + + /** * Create a new {@link OutputConfiguration} instance with a {@link Surface}. * * @param surface @@ -461,6 +499,7 @@ public final class OutputConfiguration implements Parcelable { mDynamicRangeProfile = DynamicRangeProfiles.STANDARD; mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT; mTimestampBase = TIMESTAMP_BASE_DEFAULT; + mMirrorMode = MIRROR_MODE_AUTO; } /** @@ -945,6 +984,42 @@ public final class OutputConfiguration implements Parcelable { } /** + * Set the mirroring mode for this output target + * + * <p>If this function is not called, the mirroring mode for this output is + * {@link #MIRROR_MODE_AUTO}, with which the camera API will mirror the output images + * horizontally for front facing camera.</p> + * + * <p>For efficiency, the mirror effect is applied as a transform flag, so it is only effective + * in some outputs. It works automatically for SurfaceView and TextureView outputs. For manual + * use of SurfaceTexture, it is reflected in the value of + * {@link android.graphics.SurfaceTexture#getTransformMatrix}. For other end points, such as + * ImageReader, MediaRecorder, or MediaCodec, the mirror mode has no effect. If mirroring is + * needed for such outputs, the application needs to mirror the image buffers itself before + * passing them onward.</p> + */ + public void setMirrorMode(@MirrorMode int mirrorMode) { + // Verify that the value is in range + if (mirrorMode < MIRROR_MODE_AUTO || + mirrorMode > MIRROR_MODE_V) { + throw new IllegalArgumentException("Not a valid mirror mode " + mirrorMode); + } + mMirrorMode = mirrorMode; + } + + /** + * Get the current mirroring mode + * + * <p>If no {@link #setMirrorMode} is called first, this function returns + * {@link #MIRROR_MODE_AUTO}.</p> + * + * @return The currently set mirroring mode + */ + public @MirrorMode int getMirrorMode() { + return mMirrorMode; + } + + /** * Create a new {@link OutputConfiguration} instance with another {@link OutputConfiguration} * instance. * @@ -973,6 +1048,7 @@ public final class OutputConfiguration implements Parcelable { this.mDynamicRangeProfile = other.mDynamicRangeProfile; this.mStreamUseCase = other.mStreamUseCase; this.mTimestampBase = other.mTimestampBase; + this.mMirrorMode = other.mMirrorMode; } /** @@ -998,6 +1074,8 @@ public final class OutputConfiguration implements Parcelable { DynamicRangeProfiles.checkProfileValue(dynamicRangeProfile); int timestampBase = source.readInt(); + int mirrorMode = source.readInt(); + mSurfaceGroupId = surfaceSetId; mRotation = rotation; mSurfaces = surfaces; @@ -1023,6 +1101,7 @@ public final class OutputConfiguration implements Parcelable { mDynamicRangeProfile = dynamicRangeProfile; mStreamUseCase = streamUseCase; mTimestampBase = timestampBase; + mMirrorMode = mirrorMode; } /** @@ -1141,6 +1220,7 @@ public final class OutputConfiguration implements Parcelable { dest.writeInt(mDynamicRangeProfile); dest.writeInt(mStreamUseCase); dest.writeInt(mTimestampBase); + dest.writeInt(mMirrorMode); } /** @@ -1173,7 +1253,8 @@ public final class OutputConfiguration implements Parcelable { !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) || mIsMultiResolution != other.mIsMultiResolution || mStreamUseCase != other.mStreamUseCase || - mTimestampBase != other.mTimestampBase) + mTimestampBase != other.mTimestampBase || + mMirrorMode != other.mMirrorMode) return false; if (mSensorPixelModesUsed.size() != other.mSensorPixelModesUsed.size()) { return false; @@ -1211,7 +1292,7 @@ public final class OutputConfiguration implements Parcelable { mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0, mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(), - mDynamicRangeProfile, mStreamUseCase, mTimestampBase); + mDynamicRangeProfile, mStreamUseCase, mTimestampBase, mMirrorMode); } return HashCodeHelpers.hashCode( @@ -1220,7 +1301,8 @@ public final class OutputConfiguration implements Parcelable { mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0, mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(), - mDynamicRangeProfile, mStreamUseCase, mTimestampBase); + mDynamicRangeProfile, mStreamUseCase, mTimestampBase, + mMirrorMode); } private static final String TAG = "OutputConfiguration"; @@ -1258,4 +1340,6 @@ public final class OutputConfiguration implements Parcelable { private int mStreamUseCase; // Timestamp base private int mTimestampBase; + // Mirroring mode + private int mMirrorMode; } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index eefa1d3279e3..971b61bacd97 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -22,6 +22,7 @@ import static android.view.Display.HdrCapabilities.HdrType; import android.Manifest; import android.annotation.FloatRange; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -38,6 +39,8 @@ import android.graphics.Point; import android.media.projection.MediaProjection; import android.os.Build; import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.Looper; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -48,6 +51,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; /** @@ -104,6 +108,25 @@ public final class DisplayManager { public static final String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION"; + /** @hide **/ + @IntDef(prefix = "VIRTUAL_DISPLAY_FLAG_", flag = true, value = { + VIRTUAL_DISPLAY_FLAG_PUBLIC, + VIRTUAL_DISPLAY_FLAG_PRESENTATION, + VIRTUAL_DISPLAY_FLAG_SECURE, + VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY, + VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, + VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD, + VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH, + VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT, + VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL, + VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS, + VIRTUAL_DISPLAY_FLAG_TRUSTED, + VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP, + VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface VirtualDisplayFlag {} + /** * Virtual display flag: Create a public display. * @@ -820,7 +843,11 @@ public final class DisplayManager { * VirtualDisplay.Callback, Handler) */ public VirtualDisplay createVirtualDisplay(@NonNull String name, - int width, int height, int densityDpi, @Nullable Surface surface, int flags) { + @IntRange(from = 1) int width, + @IntRange(from = 1) int height, + @IntRange(from = 1) int densityDpi, + @Nullable Surface surface, + @VirtualDisplayFlag int flags) { return createVirtualDisplay(name, width, height, densityDpi, surface, flags, null, null); } @@ -868,8 +895,13 @@ public final class DisplayManager { * a virtual display with the specified flags. */ public VirtualDisplay createVirtualDisplay(@NonNull String name, - int width, int height, int densityDpi, @Nullable Surface surface, int flags, - @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { + @IntRange(from = 1) int width, + @IntRange(from = 1) int height, + @IntRange(from = 1) int densityDpi, + @Nullable Surface surface, + @VirtualDisplayFlag int flags, + @Nullable VirtualDisplay.Callback callback, + @Nullable Handler handler) { final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width, height, densityDpi); builder.setFlags(flags); @@ -882,9 +914,16 @@ public final class DisplayManager { // TODO : Remove this hidden API after remove all callers. (Refer to MultiDisplayService) /** @hide */ - public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection, - @NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface, - int flags, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler, + public VirtualDisplay createVirtualDisplay( + @Nullable MediaProjection projection, + @NonNull String name, + @IntRange(from = 1) int width, + @IntRange(from = 1) int height, + @IntRange(from = 1) int densityDpi, + @Nullable Surface surface, + @VirtualDisplayFlag int flags, + @Nullable VirtualDisplay.Callback callback, + @Nullable Handler handler, @Nullable String uniqueId) { final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width, height, densityDpi); @@ -904,16 +943,24 @@ public final class DisplayManager { @NonNull VirtualDisplayConfig virtualDisplayConfig, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler, @Nullable Context windowContext) { + Executor executor = null; + // If callback is null, the executor will not be used. Avoid creating the handler and the + // handler executor. + if (callback != null) { + executor = new HandlerExecutor( + Handler.createAsync(handler != null ? handler.getLooper() : Looper.myLooper())); + } return mGlobal.createVirtualDisplay(mContext, projection, null /* virtualDevice */, - virtualDisplayConfig, callback, handler, windowContext); + virtualDisplayConfig, callback, executor, windowContext); } /** @hide */ public VirtualDisplay createVirtualDisplay(@Nullable IVirtualDevice virtualDevice, @NonNull VirtualDisplayConfig virtualDisplayConfig, - @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { + @Nullable VirtualDisplay.Callback callback, + @NonNull Executor executor) { return mGlobal.createVirtualDisplay(mContext, null /* projection */, virtualDevice, - virtualDisplayConfig, callback, handler, null); + virtualDisplayConfig, callback, executor, null); } /** @@ -1214,17 +1261,6 @@ public final class DisplayManager { } /** - * Returns whether the specified display supports DISPLAY_DECORATION. - * - * @param displayId The display to query support. - * - * @hide - */ - public boolean getDisplayDecorationSupport(int displayId) { - return mGlobal.getDisplayDecorationSupport(displayId); - } - - /** * Returns the user preference for "Match content frame rate". * <p> * Never: Even if the app requests it, the device will never try to match its output to the diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index af8ec279ac30..889100d0f242 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -32,6 +32,7 @@ import android.content.res.Resources; import android.graphics.ColorSpace; import android.graphics.Point; import android.hardware.display.DisplayManager.DisplayListener; +import android.hardware.graphics.common.DisplayDecorationSupport; import android.media.projection.IMediaProjection; import android.media.projection.MediaProjection; import android.os.Handler; @@ -55,6 +56,8 @@ 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; /** * Manager communication with the display manager service on behalf of @@ -584,8 +587,9 @@ public final class DisplayManagerGlobal { public VirtualDisplay createVirtualDisplay(@NonNull Context context, MediaProjection projection, IVirtualDevice virtualDevice, @NonNull VirtualDisplayConfig virtualDisplayConfig, - VirtualDisplay.Callback callback, Handler handler, @Nullable Context windowContext) { - VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, handler); + VirtualDisplay.Callback callback, @Nullable Executor executor, + @Nullable Context windowContext) { + VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, executor); IMediaProjection projectionToken = projection != null ? projection.getProjection() : null; int displayId; try { @@ -812,13 +816,13 @@ public final class DisplayManagerGlobal { } /** - * Report whether the display supports DISPLAY_DECORATION. + * Report whether/how the display supports DISPLAY_DECORATION. * * @param displayId The display whose support is being queried. * * @hide */ - public boolean getDisplayDecorationSupport(int displayId) { + public DisplayDecorationSupport getDisplayDecorationSupport(int displayId) { try { return mDm.getDisplayDecorationSupport(displayId); } catch (RemoteException ex) { @@ -1047,61 +1051,36 @@ public final class DisplayManagerGlobal { } private final static class VirtualDisplayCallback extends IVirtualDisplayCallback.Stub { - private VirtualDisplayCallbackDelegate mDelegate; + @Nullable private final VirtualDisplay.Callback mCallback; + @Nullable private final Executor mExecutor; - public VirtualDisplayCallback(VirtualDisplay.Callback callback, Handler handler) { - if (callback != null) { - mDelegate = new VirtualDisplayCallbackDelegate(callback, handler); - } + VirtualDisplayCallback(VirtualDisplay.Callback callback, Executor executor) { + mCallback = callback; + mExecutor = mCallback != null ? Objects.requireNonNull(executor) : null; } + // These methods are called from the binder thread, but the AIDL is oneway, so it should be + // safe to call the callback on arbitrary executors directly without risking blocking + // the system. + @Override // Binder call public void onPaused() { - if (mDelegate != null) { - mDelegate.sendEmptyMessage(VirtualDisplayCallbackDelegate.MSG_DISPLAY_PAUSED); + if (mCallback != null) { + mExecutor.execute(mCallback::onPaused); } } @Override // Binder call public void onResumed() { - if (mDelegate != null) { - mDelegate.sendEmptyMessage(VirtualDisplayCallbackDelegate.MSG_DISPLAY_RESUMED); + if (mCallback != null) { + mExecutor.execute(mCallback::onResumed); } } @Override // Binder call public void onStopped() { - if (mDelegate != null) { - mDelegate.sendEmptyMessage(VirtualDisplayCallbackDelegate.MSG_DISPLAY_STOPPED); - } - } - } - - private final static class VirtualDisplayCallbackDelegate extends Handler { - public static final int MSG_DISPLAY_PAUSED = 0; - public static final int MSG_DISPLAY_RESUMED = 1; - public static final int MSG_DISPLAY_STOPPED = 2; - - private final VirtualDisplay.Callback mCallback; - - public VirtualDisplayCallbackDelegate(VirtualDisplay.Callback callback, - Handler handler) { - super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/); - mCallback = callback; - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_DISPLAY_PAUSED: - mCallback.onPaused(); - break; - case MSG_DISPLAY_RESUMED: - mCallback.onResumed(); - break; - case MSG_DISPLAY_STOPPED: - mCallback.onStopped(); - break; + if (mCallback != null) { + mExecutor.execute(mCallback::onStopped); } } } diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index b3af52b19063..ddd18f4502e7 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -22,6 +22,7 @@ import android.graphics.Point; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.BrightnessInfo; import android.hardware.display.Curve; +import android.hardware.graphics.common.DisplayDecorationSupport; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; @@ -183,5 +184,5 @@ interface IDisplayManager { int getRefreshRateSwitchingType(); // Query for DISPLAY_DECORATION support. - boolean getDisplayDecorationSupport(int displayId); + DisplayDecorationSupport getDisplayDecorationSupport(int displayId); } diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java index 0e86f43207aa..e292394c36ff 100644 --- a/core/java/android/hardware/display/VirtualDisplayConfig.java +++ b/core/java/android/hardware/display/VirtualDisplayConfig.java @@ -21,6 +21,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.hardware.display.DisplayManager.VirtualDisplayFlag; import android.media.projection.MediaProjection; import android.os.Handler; import android.os.IBinder; @@ -70,6 +71,7 @@ public final class VirtualDisplayConfig implements Parcelable { * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}, * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}. */ + @VirtualDisplayFlag private int mFlags = 0; /** @@ -120,7 +122,7 @@ public final class VirtualDisplayConfig implements Parcelable { @IntRange(from = 1) int width, @IntRange(from = 1) int height, @IntRange(from = 1) int densityDpi, - int flags, + @VirtualDisplayFlag int flags, @Nullable Surface surface, @Nullable String uniqueId, int displayIdToMirror, @@ -141,6 +143,8 @@ public final class VirtualDisplayConfig implements Parcelable { IntRange.class, null, mDensityDpi, "from", 1); this.mFlags = flags; + com.android.internal.util.AnnotationValidations.validate( + VirtualDisplayFlag.class, null, mFlags); this.mSurface = surface; this.mUniqueId = uniqueId; this.mDisplayIdToMirror = displayIdToMirror; @@ -190,7 +194,7 @@ public final class VirtualDisplayConfig implements Parcelable { * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}. */ @DataClass.Generated.Member - public int getFlags() { + public @VirtualDisplayFlag int getFlags() { return mFlags; } @@ -291,6 +295,8 @@ public final class VirtualDisplayConfig implements Parcelable { IntRange.class, null, mDensityDpi, "from", 1); this.mFlags = flags; + com.android.internal.util.AnnotationValidations.validate( + VirtualDisplayFlag.class, null, mFlags); this.mSurface = surface; this.mUniqueId = uniqueId; this.mDisplayIdToMirror = displayIdToMirror; @@ -324,7 +330,7 @@ public final class VirtualDisplayConfig implements Parcelable { private @IntRange(from = 1) int mWidth; private @IntRange(from = 1) int mHeight; private @IntRange(from = 1) int mDensityDpi; - private int mFlags; + private @VirtualDisplayFlag int mFlags; private @Nullable Surface mSurface; private @Nullable String mUniqueId; private int mDisplayIdToMirror; @@ -419,7 +425,7 @@ public final class VirtualDisplayConfig implements Parcelable { * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}. */ @DataClass.Generated.Member - public @NonNull Builder setFlags(int value) { + public @NonNull Builder setFlags(@VirtualDisplayFlag int value) { checkNotUsed(); mBuilderFieldsSet |= 0x10; mFlags = value; @@ -517,10 +523,10 @@ public final class VirtualDisplayConfig implements Parcelable { } @DataClass.Generated( - time = 1620657851981L, + time = 1643938791506L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java", - inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nprivate @android.annotation.Nullable android.os.IBinder mWindowTokenClientToMirror\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)") + inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nprivate @android.annotation.Nullable android.os.IBinder mWindowTokenClientToMirror\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/hardware/input/VirtualKeyEvent.java b/core/java/android/hardware/input/VirtualKeyEvent.java index d875156f5dc7..26d74df6f2ef 100644 --- a/core/java/android/hardware/input/VirtualKeyEvent.java +++ b/core/java/android/hardware/input/VirtualKeyEvent.java @@ -114,9 +114,56 @@ public final class VirtualKeyEvent implements Parcelable { } /** - * Sets the Android key code of the event. The set of allowed characters include digits 0-9, - * characters A-Z, and standard punctuation, as well as numpad keys, function keys F1-F12, - * and meta keys (caps lock, shift, etc.). + * Sets the Android key code of the event. The set of allowed keys include digits + * {@link android.view.KeyEvent#KEYCODE_0} through + * {@link android.view.KeyEvent#KEYCODE_9}, characters + * {@link android.view.KeyEvent#KEYCODE_A} through + * {@link android.view.KeyEvent#KEYCODE_Z}, function keys + * {@link android.view.KeyEvent#KEYCODE_F1} through + * {@link android.view.KeyEvent#KEYCODE_F12}, numpad keys + * {@link android.view.KeyEvent#KEYCODE_NUMPAD_0} through + * {@link android.view.KeyEvent#KEYCODE_NUMPAD_RIGHT_PAREN}, + * and these additional keys: + * {@link android.view.KeyEvent#KEYCODE_GRAVE} + * {@link android.view.KeyEvent#KEYCODE_MINUS} + * {@link android.view.KeyEvent#KEYCODE_EQUALS} + * {@link android.view.KeyEvent#KEYCODE_LEFT_BRACKET} + * {@link android.view.KeyEvent#KEYCODE_RIGHT_BRACKET} + * {@link android.view.KeyEvent#KEYCODE_BACKSLASH} + * {@link android.view.KeyEvent#KEYCODE_SEMICOLON} + * {@link android.view.KeyEvent#KEYCODE_APOSTROPHE} + * {@link android.view.KeyEvent#KEYCODE_COMMA} + * {@link android.view.KeyEvent#KEYCODE_PERIOD} + * {@link android.view.KeyEvent#KEYCODE_SLASH} + * {@link android.view.KeyEvent#KEYCODE_ALT_LEFT} + * {@link android.view.KeyEvent#KEYCODE_ALT_RIGHT} + * {@link android.view.KeyEvent#KEYCODE_CTRL_LEFT} + * {@link android.view.KeyEvent#KEYCODE_CTRL_RIGHT} + * {@link android.view.KeyEvent#KEYCODE_SHIFT_LEFT} + * {@link android.view.KeyEvent#KEYCODE_SHIFT_RIGHT} + * {@link android.view.KeyEvent#KEYCODE_META_LEFT} + * {@link android.view.KeyEvent#KEYCODE_META_RIGHT} + * {@link android.view.KeyEvent#KEYCODE_CAPS_LOCK} + * {@link android.view.KeyEvent#KEYCODE_SCROLL_LOCK} + * {@link android.view.KeyEvent#KEYCODE_NUM_LOCK} + * {@link android.view.KeyEvent#KEYCODE_ENTER} + * {@link android.view.KeyEvent#KEYCODE_TAB} + * {@link android.view.KeyEvent#KEYCODE_SPACE} + * {@link android.view.KeyEvent#KEYCODE_DPAD_DOWN} + * {@link android.view.KeyEvent#KEYCODE_DPAD_UP} + * {@link android.view.KeyEvent#KEYCODE_DPAD_LEFT} + * {@link android.view.KeyEvent#KEYCODE_DPAD_RIGHT} + * {@link android.view.KeyEvent#KEYCODE_MOVE_END} + * {@link android.view.KeyEvent#KEYCODE_MOVE_HOME} + * {@link android.view.KeyEvent#KEYCODE_PAGE_DOWN} + * {@link android.view.KeyEvent#KEYCODE_PAGE_UP} + * {@link android.view.KeyEvent#KEYCODE_DEL} + * {@link android.view.KeyEvent#KEYCODE_FORWARD_DEL} + * {@link android.view.KeyEvent#KEYCODE_INSERT} + * {@link android.view.KeyEvent#KEYCODE_ESCAPE} + * {@link android.view.KeyEvent#KEYCODE_BREAK} + * {@link android.view.KeyEvent#KEYCODE_BACK} + * {@link android.view.KeyEvent#KEYCODE_FORWARD} * * @return this builder, to allow for chaining of calls */ diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index b0439d0e5253..c36390917cf1 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -61,6 +61,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Locale; import java.util.UUID; @@ -1576,31 +1577,58 @@ public class SoundTrigger { } /** - * Additional data conveyed by a {@link KeyphraseRecognitionEvent} - * for a key phrase detection. - * - * @hide + * Additional data conveyed by a {@link KeyphraseRecognitionEvent} + * for a key phrase detection. */ - public static class KeyphraseRecognitionExtra implements Parcelable { - /** The keyphrase ID */ + public static final class KeyphraseRecognitionExtra implements Parcelable { + /** + * The keyphrase ID + * + * @hide + */ @UnsupportedAppUsage public final int id; - /** Recognition modes matched for this event */ + /** + * Recognition modes matched for this event + * + * @hide + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public final int recognitionModes; - /** Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification - * is not performed */ + /** + * Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification + * is not performed + * + * @hide + */ @UnsupportedAppUsage public final int coarseConfidenceLevel; - /** Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to - * be recognized (RecognitionConfig) */ + /** + * Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to + * be recognized (RecognitionConfig) + * + * @hide + */ @UnsupportedAppUsage @NonNull public final ConfidenceLevel[] confidenceLevels; + + /** + * @hide + */ + @TestApi + public KeyphraseRecognitionExtra(int id, @RecognitionModes int recognitionModes, + int coarseConfidenceLevel) { + this(id, recognitionModes, coarseConfidenceLevel, new ConfidenceLevel[0]); + } + + /** + * @hide + */ @UnsupportedAppUsage public KeyphraseRecognitionExtra(int id, int recognitionModes, int coarseConfidenceLevel, @Nullable ConfidenceLevel[] confidenceLevels) { @@ -1611,7 +1639,47 @@ public class SoundTrigger { confidenceLevels != null ? confidenceLevels : new ConfidenceLevel[0]; } - public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR + /** + * The keyphrase ID associated with this class' additional data + */ + public int getKeyphraseId() { + return id; + } + + /** + * Recognition modes matched for this event + */ + @RecognitionModes + public int getRecognitionModes() { + return recognitionModes; + } + + /** + * Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification + * is not performed + * + * <p>The confidence level is expressed in percent (0% -100%). + */ + public int getCoarseConfidenceLevel() { + return coarseConfidenceLevel; + } + + /** + * Detected confidence level for users defined in a keyphrase. + * + * <p>The confidence level is expressed in percent (0% -100%). + * + * <p>The user ID is derived from the system ID + * {@link android.os.UserHandle#getIdentifier()}. + * + * @hide + */ + @NonNull + public Collection<ConfidenceLevel> getConfidenceLevels() { + return Arrays.asList(confidenceLevels); + } + + public static final @NonNull Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR = new Parcelable.Creator<KeyphraseRecognitionExtra>() { public KeyphraseRecognitionExtra createFromParcel(Parcel in) { return KeyphraseRecognitionExtra.fromParcel(in); @@ -1632,7 +1700,7 @@ public class SoundTrigger { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(id); dest.writeInt(recognitionModes); dest.writeInt(coarseConfidenceLevel); @@ -1657,21 +1725,28 @@ public class SoundTrigger { @Override public boolean equals(@Nullable Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } KeyphraseRecognitionExtra other = (KeyphraseRecognitionExtra) obj; - if (!Arrays.equals(confidenceLevels, other.confidenceLevels)) + if (!Arrays.equals(confidenceLevels, other.confidenceLevels)) { return false; - if (id != other.id) + } + if (id != other.id) { return false; - if (recognitionModes != other.recognitionModes) + } + if (recognitionModes != other.recognitionModes) { return false; - if (coarseConfidenceLevel != other.coarseConfidenceLevel) + } + if (coarseConfidenceLevel != other.coarseConfidenceLevel) { return false; + } return true; } @@ -1715,7 +1790,7 @@ public class SoundTrigger { keyphraseExtras != null ? keyphraseExtras : new KeyphraseRecognitionExtra[0]; } - public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionEvent> CREATOR + public static final @NonNull Parcelable.Creator<KeyphraseRecognitionEvent> CREATOR = new Parcelable.Creator<KeyphraseRecognitionEvent>() { public KeyphraseRecognitionEvent createFromParcel(Parcel in) { return KeyphraseRecognitionEvent.fromParcelForKeyphrase(in); diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java index 83b053aaa034..499634ae5bec 100644 --- a/core/java/android/inputmethodservice/InkWindow.java +++ b/core/java/android/inputmethodservice/InkWindow.java @@ -102,11 +102,4 @@ final class InkWindow extends PhoneWindow { lp.token = token; setAttributes(lp); } - - /** - * Returns {@code true} if Window was created and added to WM. - */ - boolean isInitialized() { - return mIsViewAdded; - } } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index b6e1b1f1fa3f..adf2759a8cd8 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -578,6 +578,7 @@ public class InputMethodService extends AbstractInputMethodService { private boolean mImeSurfaceScheduledForRemoval; private ImsConfigurationTracker mConfigTracker = new ImsConfigurationTracker(); private boolean mDestroyed; + private boolean mOnPreparedStylusHwCalled; /** Stylus handwriting Ink window. */ private InkWindow mInkWindow; @@ -919,9 +920,10 @@ public class InputMethodService extends AbstractInputMethodService { Log.d(TAG, "Input should have started before starting Stylus handwriting."); return; } - if (!mInkWindow.isInitialized()) { + if (!mOnPreparedStylusHwCalled) { // prepare hasn't been called by Stylus HOVER. onPrepareStylusHandwriting(); + mOnPreparedStylusHwCalled = true; } if (onStartStylusHandwriting()) { mPrivOps.onStylusHandwritingReady(requestId); @@ -976,6 +978,7 @@ public class InputMethodService extends AbstractInputMethodService { public void initInkWindow() { mInkWindow.initOnly(); onPrepareStylusHandwriting(); + mOnPreparedStylusHwCalled = true; } /** @@ -2354,7 +2357,7 @@ public class InputMethodService extends AbstractInputMethodService { /** * Called to prepare stylus handwriting. - * The system calls this before the first {@link #onStartStylusHandwriting} request. + * The system calls this before the {@link #onStartStylusHandwriting} request. * * <p>Note: The system tries to call this as early as possible, when it detects that * handwriting stylus input is imminent. However, that a subsequent call to @@ -2438,6 +2441,7 @@ public class InputMethodService extends AbstractInputMethodService { mInkWindow.hide(false /* remove */); mPrivOps.finishStylusHandwriting(requestId); + mOnPreparedStylusHwCalled = false; onFinishStylusHandwriting(); } diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java index c85fcd990564..59c9c5064b1e 100644 --- a/core/java/android/inputmethodservice/Keyboard.java +++ b/core/java/android/inputmethodservice/Keyboard.java @@ -217,6 +217,7 @@ public class Keyboard { rowEdgeFlags = a.getInt(com.android.internal.R.styleable.Keyboard_Row_rowEdgeFlags, 0); mode = a.getResourceId(com.android.internal.R.styleable.Keyboard_Row_keyboardMode, 0); + a.recycle(); } } diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index b780b21bf3d0..af63dd3a6fed 100644 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -326,9 +326,11 @@ public class KeyboardView extends View implements View.OnClickListener { } } + a.recycle(); a = mContext.obtainStyledAttributes( com.android.internal.R.styleable.Theme); mBackgroundDimAmount = a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f); + a.recycle(); mPreviewPopup = new PopupWindow(context); if (previewLayout != 0) { diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java index ec752fdbf45f..0fd3e034291b 100644 --- a/core/java/android/net/Ikev2VpnProfile.java +++ b/core/java/android/net/Ikev2VpnProfile.java @@ -547,7 +547,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { if (profile.excludeLocalRoutes && !profile.isBypassable) { Log.w(TAG, "ExcludeLocalRoutes should only be set in the bypassable VPN"); } - builder.setExcludeLocalRoutes(profile.excludeLocalRoutes && profile.isBypassable); + + builder.setLocalRoutesExcluded(profile.excludeLocalRoutes && profile.isBypassable); builder.setRequiresInternetValidation(profile.requiresInternetValidation); return builder.build(); @@ -1104,7 +1105,7 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { */ @NonNull @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) - public Builder setExcludeLocalRoutes(boolean excludeLocalRoutes) { + public Builder setLocalRoutesExcluded(boolean excludeLocalRoutes) { mExcludeLocalRoutes = excludeLocalRoutes; return this; } diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index d8f098eb880b..18ec8f57ad3a 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -167,6 +167,8 @@ public class NetworkPolicyManager { public static final String FIREWALL_CHAIN_NAME_POWERSAVE = "powersave"; /** @hide */ public static final String FIREWALL_CHAIN_NAME_RESTRICTED = "restricted"; + /** @hide */ + public static final String FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY = "low_power_standby"; private static final boolean ALLOW_PLATFORM_APP_POLICY = true; @@ -174,6 +176,9 @@ public class NetworkPolicyManager { public static final int FOREGROUND_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + /** @hide */ + public static final int TOP_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_BOUND_TOP; + /** * {@link Intent} extra that indicates which {@link NetworkTemplate} rule it * applies to. @@ -245,6 +250,20 @@ public class NetworkPolicyManager { */ public static final int ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS = 1 << 4; /** + * Flag to indicate that app is exempt from certain network restrictions because of it being + * in the bound top or top procstate. + * + * @hide + */ + public static final int ALLOWED_REASON_TOP = 1 << 5; + /** + * Flag to indicate that app is exempt from low power standby restrictions because of it being + * allowlisted. + * + * @hide + */ + public static final int ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST = 1 << 6; + /** * Flag to indicate that app is exempt from certain metered network restrictions because user * explicitly exempted it. * @@ -750,6 +769,14 @@ public class NetworkPolicyManager { || (capability & ActivityManager.PROCESS_CAPABILITY_NETWORK) != 0; } + /** @hide */ + public static boolean isProcStateAllowedWhileInLowPowerStandby(@Nullable UidState uidState) { + if (uidState == null) { + return false; + } + return uidState.procState <= TOP_THRESHOLD_STATE; + } + /** * Returns true if {@param procState} is considered foreground and as such will be allowed * to access network when the device is in data saver mode. Otherwise, false. diff --git a/core/java/android/net/PlatformVpnProfile.java b/core/java/android/net/PlatformVpnProfile.java index 8bd1c8d07017..c0fb4cf4f3dd 100644 --- a/core/java/android/net/PlatformVpnProfile.java +++ b/core/java/android/net/PlatformVpnProfile.java @@ -83,7 +83,7 @@ public abstract class PlatformVpnProfile { /** * Returns whether the local traffic is exempted from the VPN. */ - public final boolean getExcludeLocalRoutes() { + public final boolean areLocalRoutesExcluded() { return mExcludeLocalRoutes; } diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java index 5aad997af8c1..779d931245c8 100644 --- a/core/java/android/net/VpnManager.java +++ b/core/java/android/net/VpnManager.java @@ -24,6 +24,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.annotation.UserIdInt; import android.app.Activity; @@ -52,7 +53,7 @@ import java.util.List; * app (unlike VpnService). * * <p>VPN apps using supported protocols should preferentially use this API over the {@link - * VpnService} API for ease-of-development and reduced maintainance burden. This also give the user + * VpnService} API for ease-of-development and reduced maintenance burden. This also give the user * the guarantee that VPN network traffic is not subjected to on-device packet interception. * * @see Ikev2VpnProfile @@ -97,130 +98,173 @@ public class VpnManager { public static final String NOTIFICATION_CHANNEL_VPN = "VPN"; /** - * Action sent in the intent when an error occurred. + * Action sent in {@link android.content.Intent}s to VpnManager clients when an event occurred. * - * @hide + * This action will have a category of either {@link #CATEGORY_EVENT_IKE_ERROR}, + * {@link #CATEGORY_EVENT_NETWORK_ERROR}, or {@link #CATEGORY_EVENT_DEACTIVATED_BY_USER}, + * that the app can use to filter events it's interested in reacting to. + * + * It will also contain the following extras : + * <ul> + * <li>{@link #EXTRA_SESSION_KEY}, a {@code String} for the session key, as returned by + * {@link #startProvisionedVpnProfileSession}. + * <li>{@link #EXTRA_TIMESTAMP}, a long for the timestamp at which the error occurred, + * in milliseconds since the epoch, as returned by + * {@link java.lang.System#currentTimeMillis}. + * <li>{@link #EXTRA_UNDERLYING_NETWORK}, a {@link Network} containing the underlying + * network at the time the error occurred, or null if none. Note that this network + * may have disconnected already. + * <li>{@link #EXTRA_UNDERLYING_NETWORK_CAPABILITIES}, a {@link NetworkCapabilities} for + * the underlying network at the time the error occurred. + * <li>{@link #EXTRA_UNDERLYING_LINK_PROPERTIES}, a {@link LinkProperties} for the underlying + * network at the time the error occurred. + * </ul> + * When this event is an error, either {@link #CATEGORY_EVENT_IKE_ERROR} or + * {@link #CATEGORY_EVENT_NETWORK_ERROR}, the following extras will be populated : + * <ul> + * <li>{@link #EXTRA_ERROR_CLASS}, an {@code int} for the class of error, either + * {@link #ERROR_CLASS_RECOVERABLE} or {@link #ERROR_CLASS_NOT_RECOVERABLE}. + * <li>{@link #EXTRA_ERROR_CODE}, an {@code int} error code specific to the error. See + * {@link #EXTRA_ERROR_CODE} for details. + * </ul> */ - public static final String ACTION_VPN_MANAGER_ERROR = "android.net.action.VPN_MANAGER_ERROR"; + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String ACTION_VPN_MANAGER_EVENT = "android.net.action.VPN_MANAGER_EVENT"; /** - * An IKE protocol error. Codes are the codes from IkeProtocolException, RFC 7296. + * An IKE protocol error occurred. * - * @hide + * Codes (in {@link #EXTRA_ERROR_CODE}) are the codes from + * {@link android.net.ipsec.ike.exceptions.IkeProtocolException}, as defined by IANA in + * "IKEv2 Notify Message Types - Error Types". */ - public static final String CATEGORY_ERROR_IKE = "android.net.category.ERROR_IKE"; + @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_EVENT_IKE_ERROR = "android.net.category.EVENT_IKE_ERROR"; /** - * User deactivated the VPN, either by turning it off or selecting a different VPN provider. - * The error code is always 0. + * A network error occurred. * - * @hide + * Error codes (in {@link #EXTRA_ERROR_CODE}) are ERROR_CODE_NETWORK_*. */ - public static final String CATEGORY_ERROR_USER_DEACTIVATED = - "android.net.category.ERROR_USER_DEACTIVATED"; + @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_EVENT_NETWORK_ERROR = + "android.net.category.EVENT_NETWORK_ERROR"; /** - * Network error. Error codes are ERROR_CODE_NETWORK_*. + * The user deactivated the VPN. * - * @hide + * This can happen either when the user turns the VPN off explicitly, or when they select + * a different VPN provider. */ - public static final String CATEGORY_ERROR_NETWORK = "android.net.category.ERROR_NETWORK"; + @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_EVENT_DEACTIVATED_BY_USER = + "android.net.category.EVENT_DEACTIVATED_BY_USER"; /** - * The key of the session that experienced this error, as returned by - * startProvisionedVpnProfileSession. + * The key of the session that experienced this event, as a {@code String}. * - * @hide + * This is the same key that was returned by {@link #startProvisionedVpnProfileSession}. */ public static final String EXTRA_SESSION_KEY = "android.net.extra.SESSION_KEY"; /** - * Extra for the Network object that was the underlying network at the time of the failure, or - * null if none. + * The network that was underlying the VPN when the event occurred, as a {@link Network}. * - * @hide + * This extra will be null if there was no underlying network at the time of the event. */ public static final String EXTRA_UNDERLYING_NETWORK = "android.net.extra.UNDERLYING_NETWORK"; /** - * The NetworkCapabilities of the underlying network. + * The {@link NetworkCapabilities} of the underlying network when the event occurred. * - * @hide + * This extra will be null if there was no underlying network at the time of the event. */ public static final String EXTRA_UNDERLYING_NETWORK_CAPABILITIES = "android.net.extra.UNDERLYING_NETWORK_CAPABILITIES"; /** - * The LinkProperties of the underlying network. + * The {@link LinkProperties} of the underlying network when the event occurred. * - * @hide + * This extra will be null if there was no underlying network at the time of the event. */ public static final String EXTRA_UNDERLYING_LINK_PROPERTIES = "android.net.extra.UNDERLYING_LINK_PROPERTIES"; /** - * A long timestamp with SystemClock.elapsedRealtime base for when the event happened. + * A {@code long} timestamp containing the time at which the event occurred. * - * @hide + * This is a number of milliseconds since the epoch, suitable to be compared with + * {@link java.lang.System#currentTimeMillis}. */ public static final String EXTRA_TIMESTAMP = "android.net.extra.TIMESTAMP"; /** - * Extra for the error type. This is ERROR_NOT_RECOVERABLE or ERROR_RECOVERABLE. + * Extra for the error class, as an {@code int}. * - * @hide + * This is always either {@link #ERROR_CLASS_NOT_RECOVERABLE} or + * {@link #ERROR_CLASS_RECOVERABLE}. This extra is only populated for error categories. */ - public static final String EXTRA_ERROR_TYPE = "android.net.extra.ERROR_TYPE"; + public static final String EXTRA_ERROR_CLASS = "android.net.extra.ERROR_CLASS"; /** - * Extra for the error code. The value will be 0 for CATEGORY_ERROR_USER_DEACTIVATED, one of - * ERROR_CODE_NETWORK_* for ERROR_CATEGORY_NETWORK or one of values defined in - * IkeProtocolException#ErrorType for CATEGORY_ERROR_IKE. + * Extra for an error code, as an {@code int}. * - * @hide + * <ul> + * <li>For {@link #CATEGORY_EVENT_NETWORK_ERROR}, this is one of the + * {@code ERROR_CODE_NETWORK_*} constants. + * <li>For {@link #CATEGORY_EVENT_IKE_ERROR}, this is one of values defined in + * {@link android.net.ipsec.ike.exceptions.IkeProtocolException}.ERROR_TYPE_*. + * </ul> + * For non-error categories, this extra is not populated. */ public static final String EXTRA_ERROR_CODE = "android.net.extra.ERROR_CODE"; /** - * This error is fatal, e.g. the VPN was disabled or configuration error. The stack will not - * retry connection. + * {@link #EXTRA_ERROR_CLASS} coding for a non-recoverable error. * - * @hide + * This error is fatal, e.g. configuration error. The stack will not retry connection. */ - public static final int ERROR_NOT_RECOVERABLE = 1; + public static final int ERROR_CLASS_NOT_RECOVERABLE = 1; /** - * The stack experienced an error but will retry with exponential backoff, e.g. network timeout. + * {@link #EXTRA_ERROR_CLASS} coding for a recoverable error. * - * @hide + * The stack experienced an error but will retry with exponential backoff, e.g. network timeout. */ - public static final int ERROR_RECOVERABLE = 2; + public static final int ERROR_CLASS_RECOVERABLE = 2; /** - * An error code to indicate that there was an UnknownHostException. + * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} to indicate that the + * network host isn't known. * - * @hide + * This happens when domain name resolution could not resolve an IP address for the + * specified host. {@see java.net.UnknownHostException} */ public static final int ERROR_CODE_NETWORK_UNKNOWN_HOST = 0; /** - * An error code to indicate that there is a SocketTimeoutException. + * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} indicating a timeout. * - * @hide + * For Ikev2 VPNs, this happens typically after a retransmission failure. + * {@see android.net.ipsec.ike.exceptions.IkeTimeoutException} */ - public static final int ERROR_CODE_NETWORK_TIMEOUT = 1; + public static final int ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT = 1; /** - * An error code to indicate the connection was reset. (e.g. SocketException) + * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} indicating that + * network connectivity was lost. * - * @hide + * The most common reason for this error is that the underlying network was disconnected, + * {@see android.net.ipsec.ike.exceptions.IkeNetworkLostException}. */ - public static final int ERROR_CODE_NETWORK_RESET = 2; + public static final int ERROR_CODE_NETWORK_LOST = 2; /** - * An error code to indicate that there is an IOException. + * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} indicating an + * input/output error. * - * @hide + * This code happens when reading or writing to sockets on the underlying networks was + * terminated by an I/O error. {@see IOException}. */ public static final int ERROR_CODE_NETWORK_IO = 3; diff --git a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java index 9772bde94ac9..2dd3aaa1f55a 100644 --- a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java +++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java @@ -24,6 +24,7 @@ import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL; import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.TAG_NONE; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import android.annotation.NonNull; import android.annotation.SystemApi; @@ -108,6 +109,7 @@ public class NetworkStatsDataMigrationUtils { static final int VERSION_ADD_METERED = 4; static final int VERSION_ADD_DEFAULT_NETWORK = 5; static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6; + static final int VERSION_ADD_SUB_ID = 7; } /** @@ -448,6 +450,13 @@ public class NetworkStatsDataMigrationUtils { oemNetCapabilities = NetworkTemplate.OEM_MANAGED_NO; } + final int subId; + if (version >= IdentitySetVersion.VERSION_ADD_SUB_ID) { + subId = in.readInt(); + } else { + subId = INVALID_SUBSCRIPTION_ID; + } + // Legacy files might contain TYPE_MOBILE_* types which were deprecated in later // releases. For backward compatibility, record them as TYPE_MOBILE instead. final int collapsedLegacyType = getCollapsedLegacyType(type); @@ -457,7 +466,8 @@ public class NetworkStatsDataMigrationUtils { .setWifiNetworkKey(networkId) .setRoaming(roaming).setMetered(metered) .setDefaultNetwork(defaultNetwork) - .setOemManaged(oemNetCapabilities); + .setOemManaged(oemNetCapabilities) + .setSubId(subId); if (type == TYPE_MOBILE && ratType != NetworkTemplate.NETWORK_TYPE_ALL) { builder.setRatType(ratType); } @@ -501,10 +511,10 @@ public class NetworkStatsDataMigrationUtils { * This is copied from {@code NetworkStatsCollection#readLegacyUid}. * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}. * - * @param taggedData whether to read tagged data. For legacy uid files, the tagged - * data was stored in the same binary file with non-tagged data. - * But in later releases, these data should be kept in different - * recorders. + * @param taggedData whether to read only tagged data (true) or only non-tagged data + * (false). For legacy uid files, the tagged data was stored in + * the same binary file with non-tagged data. But in later releases, + * these data should be kept in different recorders. * @hide */ @VisibleForTesting diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index ad3de25fecc2..244335d3aa06 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -31,7 +31,7 @@ import com.android.internal.util.IndentingPrintWriter; import java.io.Serializable; import java.util.ArrayList; import java.util.Set; -import java.util.function.Supplier; +import java.util.function.Function; /** * A mapping from String keys to values of various types. In most cases, you @@ -252,11 +252,10 @@ public class BaseBundle { if (size == 0) { return null; } - Object o = getValueAt(0); try { - return (String) o; - } catch (ClassCastException e) { - typeWarning("getPairValue()", o, "String", e); + return getValueAt(0, String.class); + } catch (ClassCastException | BadParcelableException e) { + typeWarning("getPairValue()", /* value */ null, "String", e); return null; } } @@ -309,7 +308,7 @@ public class BaseBundle { } for (int i = 0, n = mMap.size(); i < n; i++) { // Triggers deserialization of i-th item, if needed - getValueAt(i); + getValueAt(i, /* clazz */ null); } } } @@ -324,8 +323,21 @@ public class BaseBundle { * @hide */ final Object getValue(String key) { + return getValue(key, /* clazz */ null); + } + + /** + * Returns the value for key {@code key} for expected return type {@param clazz} (or {@code + * null} for no type check). + * + * This call should always be made after {@link #unparcel()} or inside a lock after making sure + * {@code mMap} is not null. + * + * @hide + */ + final <T> T getValue(String key, @Nullable Class<T> clazz) { int i = mMap.indexOfKey(key); - return (i >= 0) ? getValueAt(i) : null; + return (i >= 0) ? getValueAt(i, clazz) : null; } /** @@ -336,11 +348,12 @@ public class BaseBundle { * * @hide */ - final Object getValueAt(int i) { + @SuppressWarnings("unchecked") + final <T> T getValueAt(int i, @Nullable Class<T> clazz) { Object object = mMap.valueAt(i); - if (object instanceof Supplier<?>) { + if (object instanceof Function<?, ?>) { try { - object = ((Supplier<?>) object).get(); + object = ((Function<Class<?>, ?>) object).apply(clazz); } catch (BadParcelableException e) { if (sShouldDefuse) { Log.w(TAG, "Failed to parse item " + mMap.keyAt(i) + ", returning null.", e); @@ -351,7 +364,7 @@ public class BaseBundle { } mMap.setValueAt(i, object); } - return object; + return (clazz != null) ? clazz.cast(object) : (T) object; } private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel, @@ -528,7 +541,7 @@ public class BaseBundle { } else { // Following semantic above of failing in case we get a serialized value vs a // deserialized one, we'll compare the map. If a certain element hasn't been - // deserialized yet, it's a Supplier (or more specifically a LazyValue, but let's + // deserialized yet, it's a function object (or more specifically a LazyValue, but let's // pretend we don't know that here :P), we'll use that element's equality comparison as // map naturally does. That will takes care of comparing the payload if needed (see // Parcel.readLazyValue() for details). @@ -982,15 +995,19 @@ public class BaseBundle { } // Log a message if the value was non-null but not of the expected type - void typeWarning(String key, Object value, String className, - Object defaultValue, ClassCastException e) { + void typeWarning(String key, @Nullable Object value, String className, + Object defaultValue, RuntimeException e) { StringBuilder sb = new StringBuilder(); sb.append("Key "); sb.append(key); sb.append(" expected "); sb.append(className); - sb.append(" but value was a "); - sb.append(value.getClass().getName()); + if (value != null) { + sb.append(" but value was a "); + sb.append(value.getClass().getName()); + } else { + sb.append(" but value was of a different type "); + } sb.append(". The default value "); sb.append(defaultValue); sb.append(" was returned."); @@ -998,8 +1015,7 @@ public class BaseBundle { Log.w(TAG, "Attempt to cast generated internal exception:", e); } - void typeWarning(String key, Object value, String className, - ClassCastException e) { + void typeWarning(String key, @Nullable Object value, String className, RuntimeException e) { typeWarning(key, value, className, "<null>", e); } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 9970641dd324..1d3908958af7 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -401,7 +401,12 @@ public class Build { /** * All known codenames starting from {@link VERSION_CODES.Q}. * - * <p>This includes in development codenames as well. + * <p>This includes in development codenames as well, i.e. if {@link #CODENAME} is not "REL" + * then the value of that is present in this set. + * + * <p>If a particular string is not present in this set, then it is either not a codename + * or a codename for a future release. For example, during Android R development, "Tiramisu" + * was not a known codename. * * @hide */ diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index b2bbfd6c163d..2b13f20ffe6d 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -16,6 +16,9 @@ package android.os; +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.util.ArrayMap; @@ -914,6 +917,33 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { /** * Returns the value associated with the given key, or {@code null} if + * no mapping of the desired type exists for the given key or a {@code null} + * value is explicitly associated with the key. + * + * <p><b>Note: </b> if the expected value is not a class provided by the Android platform, + * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first. + * Otherwise, this method might throw an exception or return {@code null}. + * + * @param key a String, or {@code null} + * @param clazz The type of the object expected or {@code null} for performing no checks. + * @return a Parcelable value, or {@code null} + * + * @hide + */ + @SuppressWarnings("unchecked") + @Nullable + public <T> T getParcelable(@Nullable String key, @NonNull Class<T> clazz) { + unparcel(); + try { + return getValue(key, requireNonNull(clazz)); + } catch (ClassCastException | BadParcelableException e) { + typeWarning(key, /* value */ null, "Parcelable", e); + return null; + } + } + + /** + * Returns the value associated with the given key, or {@code null} if * no mapping of the desired type exists for the given key or a null * value is explicitly associated with the key. * diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 0257408b3e42..3d129417e53b 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -1333,7 +1333,7 @@ public class Environment { final Context context = AppGlobals.getInitialApplication(); final int uid = context.getApplicationInfo().uid; // Isolated processes and Instant apps are never allowed to be in scoped storage - if (Process.isIsolated(uid)) { + if (Process.isIsolated(uid) || Process.isSupplemental(uid)) { return false; } diff --git a/core/java/android/os/FileUriExposedException.java b/core/java/android/os/FileUriExposedException.java index e47abe288d22..a3af24dae1c8 100644 --- a/core/java/android/os/FileUriExposedException.java +++ b/core/java/android/os/FileUriExposedException.java @@ -35,7 +35,7 @@ import android.content.Intent; * or higher. Applications targeting earlier SDK versions are allowed to share * {@code file://} {@link android.net.Uri}, but it's strongly discouraged. * - * @see android.support.v4.content.FileProvider + * @see androidx.core.content.FileProvider * @see Intent#FLAG_GRANT_READ_URI_PERMISSION */ public class FileUriExposedException extends RuntimeException { diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index fcce266d6a2d..39ca596f9181 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -107,9 +107,7 @@ interface IUserManager { void clearSeedAccountData(int userId); boolean someUserHasSeedAccount(in String accountName, in String accountType); boolean someUserHasAccount(in String accountName, in String accountType); - boolean isProfile(int userId); - boolean isManagedProfile(int userId); - boolean isCloneProfile(int userId); + String getProfileType(int userId); boolean isMediaSharedWithParent(int userId); boolean isCredentialSharedWithParent(int userId); boolean isDemoUser(int userId); diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 9998e1206602..ae923530b651 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -4319,18 +4319,19 @@ public final class Parcel { } /** - * This will return a {@link Supplier} for length-prefixed types that deserializes the object - * when {@link Supplier#get()} is called, for other types it will return the object itself. + * This will return a {@link Function} for length-prefixed types that deserializes the object + * when {@link Function#apply} is called with the expected class of the return object (or {@code + * null} for no type check), for other types it will return the object itself. * - * <p>After calling {@link Supplier#get()} the parcel cursor will not change. Note that you - * shouldn't recycle the parcel, not at least until all objects have been retrieved. No + * <p>After calling {@link Function#apply(Object)} the parcel cursor will not change. Note that + * you shouldn't recycle the parcel, not at least until all objects have been retrieved. No * synchronization attempts are made. * - * </p>The supplier returned implements {@link #equals(Object)} and {@link #hashCode()}. Two - * suppliers are equal if either of the following is true: + * </p>The function returned implements {@link #equals(Object)} and {@link #hashCode()}. Two + * function objects are equal if either of the following is true: * <ul> - * <li>{@link Supplier#get()} has been called on both and both objects returned are equal. - * <li>{@link Supplier#get()} hasn't been called on either one and everything below is true: + * <li>{@link Function#apply} has been called on both and both objects returned are equal. + * <li>{@link Function#apply} hasn't been called on either one and everything below is true: * <ul> * <li>The {@code loader} parameters used to retrieve each are equal. * <li>They both have the same type. @@ -4357,7 +4358,7 @@ public final class Parcel { } - private static final class LazyValue implements Supplier<Object> { + private static final class LazyValue implements Function<Class<?>, Object> { /** * | 4B | 4B | * mSource = Parcel{... | type | length | object | ...} @@ -4389,7 +4390,7 @@ public final class Parcel { } @Override - public Object get() { + public Object apply(@Nullable Class<?> clazz) { Parcel source = mSource; if (source != null) { synchronized (source) { @@ -4398,7 +4399,7 @@ public final class Parcel { int restore = source.dataPosition(); try { source.setDataPosition(mPosition); - mObject = source.readValue(mLoader); + mObject = source.readValue(mLoader, clazz); } finally { source.setDataPosition(restore); } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 2fe062268112..17b5ec5ca01b 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -929,6 +929,7 @@ public class Process { * @hide */ @SystemApi(client = MODULE_LIBRARIES) + @TestApi public static final int toSupplementalUid(int uid) { return uid + (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID); } diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 70aaa5e52c44..412a33a1c124 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -942,7 +942,7 @@ public final class StrictMode { * <p>Instead, apps should use {@code content://} Uris so the platform can extend * temporary permission for the receiving app to access the resource. * - * @see android.support.v4.content.FileProvider + * @see androidx.core.content.FileProvider * @see Intent#FLAG_GRANT_READ_URI_PERMISSION */ public @NonNull Builder detectFileUriExposure() { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index c597a1a6e7dc..e56f214e30d9 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -98,8 +98,8 @@ public class UserManager { /** The userId of the constructor param context. To be used instead of mContext.getUserId(). */ private final @UserIdInt int mUserId; - private Boolean mIsManagedProfileCached; - private Boolean mIsProfileCached; + /** The userType of UserHandle.myUserId(); empty string if not a profile; null until cached. */ + private String mProfileTypeOfProcessUser = null; /** * User type representing a {@link UserHandle#USER_SYSTEM system} user that is a human user. @@ -2276,7 +2276,7 @@ public class UserManager { * {@link UserManager#USER_TYPE_PROFILE_MANAGED managed profile}. * @hide */ - public static boolean isUserTypeManagedProfile(String userType) { + public static boolean isUserTypeManagedProfile(@Nullable String userType) { return USER_TYPE_PROFILE_MANAGED.equals(userType); } @@ -2284,7 +2284,7 @@ public class UserManager { * Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_GUEST guest user}. * @hide */ - public static boolean isUserTypeGuest(String userType) { + public static boolean isUserTypeGuest(@Nullable String userType) { return USER_TYPE_FULL_GUEST.equals(userType); } @@ -2293,7 +2293,7 @@ public class UserManager { * {@link UserManager#USER_TYPE_FULL_RESTRICTED restricted user}. * @hide */ - public static boolean isUserTypeRestricted(String userType) { + public static boolean isUserTypeRestricted(@Nullable String userType) { return USER_TYPE_FULL_RESTRICTED.equals(userType); } @@ -2301,7 +2301,7 @@ public class UserManager { * Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_DEMO demo user}. * @hide */ - public static boolean isUserTypeDemo(String userType) { + public static boolean isUserTypeDemo(@Nullable String userType) { return USER_TYPE_FULL_DEMO.equals(userType); } @@ -2309,7 +2309,7 @@ public class UserManager { * Returns whether the user type is a {@link UserManager#USER_TYPE_PROFILE_CLONE clone user}. * @hide */ - public static boolean isUserTypeCloneProfile(String userType) { + public static boolean isUserTypeCloneProfile(@Nullable String userType) { return USER_TYPE_PROFILE_CLONE.equals(userType); } @@ -2525,25 +2525,50 @@ public class UserManager { } private boolean isProfile(@UserIdInt int userId) { - if (userId == mUserId) { + final String profileType = getProfileType(userId); + return profileType != null && !profileType.equals(""); + } + + /** + * Returns the user type of the context user if it is a profile. + * + * This is a more specific form of {@link #getUserType()} with relaxed permission requirements. + * + * @return the user type of the context user if it is a {@link #isProfile() profile}, + * an empty string if it is not a profile, + * or null if the user doesn't exist. + */ + @UserHandleAware( + requiresAnyOfPermissionsIfNotCallerProfileGroup = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.QUERY_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS}) + private @Nullable String getProfileType() { + return getProfileType(mUserId); + } + + /** @see #getProfileType() */ + private @Nullable String getProfileType(@UserIdInt int userId) { + // First, the typical case (i.e. the *process* user, not necessarily the context user). + // This cache cannot be become invalidated since it's about the calling process itself. + if (userId == UserHandle.myUserId()) { // 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. - if (mIsProfileCached != null) { - return mIsProfileCached; - } - try { - mIsProfileCached = mService.isProfile(mUserId); - return mIsProfileCached; - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); + if (mProfileTypeOfProcessUser != null) { + return mProfileTypeOfProcessUser; } - } else { try { - return mService.isProfile(userId); + final String profileType = mService.getProfileType(userId); + if (profileType != null) { + return mProfileTypeOfProcessUser = profileType.intern(); + } } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } + + // The userId is not for the process's user. Use a slower cache that handles invalidation. + return mProfileTypeCache.query(userId); } /** @@ -2577,50 +2602,26 @@ public class UserManager { android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) public boolean isManagedProfile(@UserIdInt int userId) { - if (userId == mUserId) { - // 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. - if (mIsManagedProfileCached != null) { - return mIsManagedProfileCached; - } - try { - mIsManagedProfileCached = mService.isManagedProfile(mUserId); - return mIsManagedProfileCached; - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } else { - try { - return mService.isManagedProfile(userId); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } + return isUserTypeManagedProfile(getProfileType(userId)); } /** * Checks if the context user is a clone profile. * - * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or - * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller - * must be in the same profile group of the user. - * * @return whether the context user is a clone profile. * * @see android.os.UserManager#USER_TYPE_PROFILE_CLONE * @hide */ @SystemApi - @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, - Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) - @UserHandleAware + @UserHandleAware( + requiresAnyOfPermissionsIfNotCallerProfileGroup = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.QUERY_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS}) @SuppressAutoDoc public boolean isCloneProfile() { - try { - return mService.isCloneProfile(mUserId); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } + return isUserTypeCloneProfile(getProfileType()); } /** @@ -4840,15 +4841,16 @@ public class UserManager { * @param overrideDevicePolicy when {@code true}, user is removed even if the caller has * the {@link #DISALLOW_REMOVE_USER} or {@link #DISALLOW_REMOVE_MANAGED_PROFILE} restriction * - * @return the result code {@link #REMOVE_RESULT_REMOVED}, {@link #REMOVE_RESULT_DEFERRED}, - * {@link #REMOVE_RESULT_ALREADY_BEING_REMOVED}, or {@link #REMOVE_RESULT_ERROR}. + * @return the {@link RemoveResult} code: {@link #REMOVE_RESULT_REMOVED}, + * {@link #REMOVE_RESULT_DEFERRED}, {@link #REMOVE_RESULT_ALREADY_BEING_REMOVED}, or + * {@link #REMOVE_RESULT_ERROR}. * * @hide */ @SystemApi @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) - public int removeUserWhenPossible(@NonNull UserHandle user, + public @RemoveResult int removeUserWhenPossible(@NonNull UserHandle user, boolean overrideDevicePolicy) { try { return mService.removeUserWhenPossible(user.getIdentifier(), overrideDevicePolicy); @@ -5247,6 +5249,33 @@ public class UserManager { } } + /* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */ + private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props"; + + private final PropertyInvalidatedCache<Integer, String> mProfileTypeCache = + new PropertyInvalidatedCache<Integer, String>(32, CACHE_KEY_STATIC_USER_PROPERTIES) { + @Override + public String recompute(Integer query) { + try { + // Will be null (and not cached) if invalid user; otherwise cache the type. + String profileType = mService.getProfileType(query); + if (profileType != null) profileType = profileType.intern(); + return profileType; + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + @Override + public boolean bypass(Integer query) { + return query < 0; + } + }; + + /** {@hide} */ + public static final void invalidateStaticUserProperties() { + PropertyInvalidatedCache.invalidateCache(CACHE_KEY_STATIC_USER_PROPERTIES); + } + /** * @hide * User that enforces a restriction. diff --git a/core/java/android/os/logcat/ILogcatManagerService.aidl b/core/java/android/os/logcat/ILogcatManagerService.aidl index 68b5679919d6..02db2749bbe8 100644 --- a/core/java/android/os/logcat/ILogcatManagerService.aidl +++ b/core/java/android/os/logcat/ILogcatManagerService.aidl @@ -22,5 +22,7 @@ package android.os.logcat; interface ILogcatManagerService { void startThread(in int uid, in int gid, in int pid, in int fd); void finishThread(in int uid, in int gid, in int pid, in int fd); + void approve(in int uid, in int gid, in int pid, in int fd); + void decline(in int uid, in int gid, in int pid, in int fd); } diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index 0eb21e169123..722cdbc620a7 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -203,4 +203,6 @@ interface IStorageManager { void notifyAppIoResumed(in String volumeUuid, int uid, int tid, int reason) = 93; int getExternalStorageMountMode(int uid, in String packageName) = 94; boolean isAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 95; + void setCloudMediaProvider(in String authority) = 96; + String getCloudMediaProvider() = 97; }
\ No newline at end of file diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 052bc6a0f3c3..4e1337f56606 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1331,8 +1331,10 @@ public class StorageManager { /** * Return the list of shared/external storage volumes currently available to - * the calling user and the user it shares media with - * CDD link : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support + * the calling user and the user it shares media with. Please refer to + * <a href="https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support"> + * multi-user support</a> for more details. + * * <p> * This is similar to {@link StorageManager#getStorageVolumes()} except that the result also * includes the volumes belonging to any user it shares media with @@ -3000,6 +3002,35 @@ public class StorageManager { } } + /** + * Notify the system of the current cloud media provider. + * + * This can only be called by the {@link android.service.storage.ExternalStorageService} + * holding the {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission. + * + * @param authority the authority of the content provider + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public void setCloudMediaProvider(@Nullable String authority) { + try { + mStorageManager.setCloudMediaProvider(authority); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + @TestApi + @Nullable + public String getCloudMediaProvider() { + try { + return mStorageManager.getCloudMediaProvider(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private final Object mFuseAppLoopLock = new Object(); @GuardedBy("mFuseAppLoopLock") diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java index 8928a423c6cb..059bd846327c 100644 --- a/core/java/android/os/storage/StorageManagerInternal.java +++ b/core/java/android/os/storage/StorageManagerInternal.java @@ -114,7 +114,6 @@ public abstract class StorageManagerInternal { */ public abstract void prepareAppDataAfterInstall(@NonNull String packageName, int uid); - /** * Return true if uid is external storage service. */ @@ -151,4 +150,23 @@ public abstract class StorageManagerInternal { * it's ok to access and modify CE directories on volumes for this user. */ public abstract boolean isCeStoragePrepared(@UserIdInt int userId); + + /** + * A listener for changes to the cloud provider. + */ + public interface CloudProviderChangeListener { + /** + * Triggered when the cloud provider changes. A {@code null} value means there's currently + * no cloud provider. + */ + void onCloudProviderChanged(int userId, @Nullable String authority); + } + + /** + * Register a {@link CloudProviderChangeListener} to be notified when a cloud media provider + * changes. The listener will be called after registration with any currently set cloud media + * providers. + */ + public abstract void registerCloudProviderChangeListener( + @NonNull CloudProviderChangeListener listener); } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 12fa0ddfc648..fc7ac116a470 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -16,6 +16,12 @@ package android.permission; +import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; +import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE; +import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED; +import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; +import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED; +import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; import static android.os.Build.VERSION_CODES.S; import android.Manifest; @@ -107,6 +113,16 @@ public final class PermissionManager { public static final int PERMISSION_HARD_DENIED = 2; /** + * The set of flags that indicate that a permission state has been explicitly set + * + * @hide + */ + public static final int EXPLICIT_SET_FLAGS = FLAG_PERMISSION_USER_SET + | FLAG_PERMISSION_USER_FIXED | FLAG_PERMISSION_POLICY_FIXED + | FLAG_PERMISSION_SYSTEM_FIXED | FLAG_PERMISSION_GRANTED_BY_DEFAULT + | FLAG_PERMISSION_GRANTED_BY_ROLE; + + /** * Activity action: Launch UI to review permission decisions. * <p> * <strong>Important:</strong>You must protect the activity that handles this action with the @@ -1447,6 +1463,7 @@ public final class PermissionManager { * @hide */ @TestApi + @RequiresPermission(Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL) public void revokePostNotificationPermissionWithoutKillForTest(@NonNull String packageName, int userId) { try { diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java index 22399f517908..bbee48b30d2f 100644 --- a/core/java/android/preference/PreferenceFragment.java +++ b/core/java/android/preference/PreferenceFragment.java @@ -142,7 +142,7 @@ public abstract class PreferenceFragment extends Fragment implements * switch to a new fragment. * * @deprecated Use {@link - * android.support.v7.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback} + * androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback} */ @Deprecated public interface OnPreferenceStartFragmentCallback { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 464567b85270..ea20ed42ee14 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10709,6 +10709,19 @@ public final class Settings { "hdmi_cec_set_menu_language_denylist"; /** + * Whether the Taskbar Education is about to be shown or is currently showing. + * + * <p>1 if true, 0 or unset otherwise. + * + * <p>This setting is used to inform other components that the Taskbar Education is + * currently showing, which can prevent them from showing something else to the user. + * + * @hide + */ + public static final String LAUNCHER_TASKBAR_EDUCATION_SHOWING = + "launcher_taskbar_education_showing"; + + /** * These entries are considered common between the personal and the managed profile, * since the managed profile doesn't get to change them. */ diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index bb1f393b99bc..345917220b6b 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -31,6 +31,11 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Handler; import android.os.IBinder; @@ -39,9 +44,11 @@ import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; +import android.util.AttributeSet; import android.util.Log; import android.util.MathUtils; import android.util.Slog; +import android.util.Xml; import android.view.ActionMode; import android.view.Display; import android.view.KeyEvent; @@ -57,9 +64,14 @@ import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.accessibility.AccessibilityEvent; +import com.android.internal.R; import com.android.internal.util.DumpUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.FileDescriptor; +import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.function.Consumer; @@ -159,8 +171,9 @@ import java.util.function.Consumer; * </pre> */ public class DreamService extends Service implements Window.Callback { - private final String mTag = - DreamService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]"; + private static final String TAG = DreamService.class.getSimpleName(); + private final String mTag = TAG + "[" + getClass().getSimpleName() + "]"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); /** * The name of the dream manager service. @@ -191,6 +204,11 @@ public class DreamService extends Service implements Window.Callback { public static final String DREAM_META_DATA = "android.service.dream"; /** + * Name of the root tag under which a Dream defines its metadata in an XML file. + */ + private static final String DREAM_META_DATA_ROOT_TAG = "dream"; + + /** * Extra containing a boolean for whether to show complications on the overlay. * @hide */ @@ -239,13 +257,16 @@ public class DreamService extends Service implements Window.Callback { mRequests = new ArrayDeque<>(); } - public void bind(Context context, @Nullable ComponentName overlayService) { + public void bind(Context context, @Nullable ComponentName overlayService, + ComponentName dreamService) { if (overlayService == null) { return; } final Intent overlayIntent = new Intent(); overlayIntent.setComponent(overlayService); + overlayIntent.putExtra(EXTRA_SHOW_COMPLICATIONS, + fetchShouldShowComplications(context, dreamService)); context.bindService(overlayIntent, this, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE); @@ -967,7 +988,8 @@ public class DreamService extends Service implements Window.Callback { // Connect to the overlay service if present. if (!mWindowless) { - mOverlayConnection.bind(this, intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT)); + mOverlayConnection.bind(this, intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT), + new ComponentName(this, getClass())); } return mDreamServiceWrapper; @@ -1081,6 +1103,86 @@ public class DreamService extends Service implements Window.Callback { // end public api /** + * Parses and returns metadata of the dream service indicated by the service info. Returns null + * if metadata cannot be found. + * + * Note that {@link ServiceInfo} must be fetched with {@link PackageManager#GET_META_DATA} flag. + * + * @hide + */ + @Nullable + public static DreamMetadata getDreamMetadata(Context context, ServiceInfo serviceInfo) { + final PackageManager pm = context.getPackageManager(); + + final TypedArray rawMetadata = readMetadata(pm, serviceInfo); + if (rawMetadata == null) return null; + + final DreamMetadata metadata = new DreamMetadata( + convertToComponentName(rawMetadata.getString( + com.android.internal.R.styleable.Dream_settingsActivity), serviceInfo), + rawMetadata.getDrawable( + com.android.internal.R.styleable.Dream_previewImage), + rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications, + DEFAULT_SHOW_COMPLICATIONS)); + rawMetadata.recycle(); + return metadata; + } + + /** + * Returns the raw XML metadata fetched from the {@link ServiceInfo}. + * + * Returns <code>null</code> if the {@link ServiceInfo} doesn't contain valid dream metadata. + */ + @Nullable + private static TypedArray readMetadata(PackageManager pm, ServiceInfo serviceInfo) { + if (serviceInfo == null || serviceInfo.metaData == null) { + return null; + } + + try (XmlResourceParser parser = + serviceInfo.loadXmlMetaData(pm, DreamService.DREAM_META_DATA)) { + if (parser == null) { + if (DEBUG) Log.w(TAG, "No " + DreamService.DREAM_META_DATA + " metadata"); + return null; + } + + final AttributeSet attrs = Xml.asAttributeSet(parser); + while (true) { + final int type = parser.next(); + if (type == XmlPullParser.END_DOCUMENT || type == XmlPullParser.START_TAG) { + break; + } + } + + if (!parser.getName().equals(DREAM_META_DATA_ROOT_TAG)) { + if (DEBUG) { + Log.w(TAG, "Metadata does not start with " + DREAM_META_DATA_ROOT_TAG + " tag"); + } + return null; + } + + return pm.getResourcesForApplication(serviceInfo.applicationInfo).obtainAttributes( + attrs, com.android.internal.R.styleable.Dream); + } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) { + if (DEBUG) Log.e(TAG, "Error parsing: " + serviceInfo.packageName, e); + return null; + } + } + + private static ComponentName convertToComponentName(String flattenedString, + ServiceInfo serviceInfo) { + if (flattenedString == null) { + return null; + } + + if (!flattenedString.contains("/")) { + return new ComponentName(serviceInfo.packageName, flattenedString); + } + + return ComponentName.unflattenFromString(flattenedString); + } + + /** * Called by DreamController.stopDream() when the Dream is about to be unbound and destroyed. * * Must run on mHandler. @@ -1242,6 +1344,30 @@ public class DreamService extends Service implements Window.Callback { return (oldFlags&~mask) | (flags&mask); } + /** + * Fetches metadata of the dream indicated by the {@link ComponentName}, and returns whether + * the dream should show complications on the overlay. If not defined, returns + * {@link DreamService#DEFAULT_SHOW_COMPLICATIONS}. + */ + private static boolean fetchShouldShowComplications(Context context, + ComponentName componentName) { + final PackageManager pm = context.getPackageManager(); + + try { + final ServiceInfo si = pm.getServiceInfo(componentName, + PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA)); + final DreamMetadata metadata = getDreamMetadata(context, si); + + if (metadata != null) { + return metadata.showComplications; + } + } catch (PackageManager.NameNotFoundException e) { + if (DEBUG) Log.w(TAG, "cannot find component " + componentName.flattenToShortString()); + } + + return DEFAULT_SHOW_COMPLICATIONS; + } + @Override protected void dump(final FileDescriptor fd, PrintWriter pw, final String[] args) { DumpUtils.dumpAsync(mHandler, (pw1, prefix) -> dumpOnHandler(fd, pw1, args), pw, "", 1000); @@ -1302,4 +1428,27 @@ public class DreamService extends Service implements Window.Callback { onWindowCreated(a.getWindow()); } } + + /** + * Represents metadata defined in {@link android.R.styleable#Dream <dream>}. + * + * @hide + */ + public static final class DreamMetadata { + @Nullable + public final ComponentName settingsActivity; + + @Nullable + public final Drawable previewImage; + + @NonNull + public final boolean showComplications; + + DreamMetadata(ComponentName settingsActivity, Drawable previewImage, + boolean showComplications) { + this.settingsActivity = settingsActivity; + this.previewImage = previewImage; + this.showComplications = showComplications; + } + } } diff --git a/core/java/android/service/voice/AbstractHotwordDetector.java b/core/java/android/service/voice/AbstractHotwordDetector.java index 192260791a8b..01d5638461af 100644 --- a/core/java/android/service/voice/AbstractHotwordDetector.java +++ b/core/java/android/service/voice/AbstractHotwordDetector.java @@ -18,6 +18,7 @@ package android.service.voice; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; +import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; @@ -34,6 +35,9 @@ import android.util.Slog; import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.app.IVoiceInteractionManagerService; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + /** Base implementation of {@link HotwordDetector}. */ abstract class AbstractHotwordDetector implements HotwordDetector { private static final String TAG = AbstractHotwordDetector.class.getSimpleName(); @@ -45,6 +49,8 @@ abstract class AbstractHotwordDetector implements HotwordDetector { private final Handler mHandler; private final HotwordDetector.Callback mCallback; private final int mDetectorType; + private Consumer<AbstractHotwordDetector> mOnDestroyListener; + private final AtomicBoolean mIsDetectorActive; AbstractHotwordDetector( IVoiceInteractionManagerService managerService, @@ -55,6 +61,7 @@ abstract class AbstractHotwordDetector implements HotwordDetector { mHandler = new Handler(Looper.getMainLooper()); mCallback = callback; mDetectorType = detectorType; + mIsDetectorActive = new AtomicBoolean(true); } /** @@ -70,6 +77,7 @@ abstract class AbstractHotwordDetector implements HotwordDetector { if (DEBUG) { Slog.i(TAG, "#recognizeHotword"); } + throwIfDetectorIsNoLongerActive(); // TODO: consider closing existing session. @@ -106,6 +114,7 @@ abstract class AbstractHotwordDetector implements HotwordDetector { if (DEBUG) { Slog.d(TAG, "updateState()"); } + throwIfDetectorIsNoLongerActive(); synchronized (mLock) { updateStateLocked(options, sharedMemory, null /* callback */, mDetectorType); } @@ -126,6 +135,35 @@ abstract class AbstractHotwordDetector implements HotwordDetector { } } + void registerOnDestroyListener(Consumer<AbstractHotwordDetector> onDestroyListener) { + synchronized (mLock) { + if (mOnDestroyListener != null) { + throw new IllegalStateException("only one destroy listener can be registered"); + } + mOnDestroyListener = onDestroyListener; + } + } + + @CallSuper + @Override + public void destroy() { + if (!mIsDetectorActive.get()) { + return; + } + mIsDetectorActive.set(false); + synchronized (mLock) { + mOnDestroyListener.accept(this); + } + } + + protected void throwIfDetectorIsNoLongerActive() { + if (!mIsDetectorActive.get()) { + Slog.e(TAG, "attempting to use a destroyed detector which is no longer active"); + throw new IllegalStateException( + "attempting to use a destroyed detector which is no longer active"); + } + } + private static class BinderCallback extends IMicrophoneHotwordDetectionVoiceInteractionCallback.Stub { private final Handler mHandler; @@ -146,7 +184,10 @@ abstract class AbstractHotwordDetector implements HotwordDetector { mHandler.sendMessage(obtainMessage( HotwordDetector.Callback::onDetected, mCallback, - new AlwaysOnHotwordDetector.EventPayload(audioFormat, hotwordDetectedResult))); + new AlwaysOnHotwordDetector.EventPayload.Builder() + .setCaptureAudioFormat(audioFormat) + .setHotwordDetectedResult(hotwordDetectedResult) + .build())); } } } diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index c9daf52b5685..bec5d1be57fd 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -23,6 +23,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.ActivityThread; @@ -59,6 +60,9 @@ import com.android.internal.app.IVoiceInteractionSoundTriggerSession; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Locale; /** @@ -336,7 +340,39 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector { * Additional payload for {@link Callback#onDetected}. */ public static class EventPayload { - private final boolean mTriggerAvailable; + + /** + * Flags for describing the data format provided in the event payload. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"DATA_FORMAT_"}, value = { + DATA_FORMAT_RAW, + DATA_FORMAT_TRIGGER_AUDIO, + }) + public @interface DataFormat { + } + + /** + * Data format is not strictly defined by the framework, and the + * {@link android.hardware.soundtrigger.SoundTriggerModule} voice engine may populate this + * field in any format. + */ + public static final int DATA_FORMAT_RAW = 0; + + /** + * Data format is defined as trigger audio. + * + * <p>When this format is used, {@link #getCaptureAudioFormat()} can be used to understand + * further the audio format for reading the data. + * + * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO + */ + public static final int DATA_FORMAT_TRIGGER_AUDIO = 1; + + @DataFormat + private final int mDataFormat; // Indicates if {@code captureSession} can be used to continue capturing more audio // from the DSP hardware. private final boolean mCaptureAvailable; @@ -348,40 +384,24 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector { private final byte[] mData; private final HotwordDetectedResult mHotwordDetectedResult; private final ParcelFileDescriptor mAudioStream; - - EventPayload(boolean triggerAvailable, boolean captureAvailable, - AudioFormat audioFormat, int captureSession, byte[] data) { - this(triggerAvailable, captureAvailable, audioFormat, captureSession, data, null, - null); - } - - EventPayload(boolean triggerAvailable, boolean captureAvailable, - AudioFormat audioFormat, int captureSession, byte[] data, - HotwordDetectedResult hotwordDetectedResult) { - this(triggerAvailable, captureAvailable, audioFormat, captureSession, data, - hotwordDetectedResult, null); - } - - EventPayload(AudioFormat audioFormat, HotwordDetectedResult hotwordDetectedResult) { - this(false, false, audioFormat, -1, null, hotwordDetectedResult, null); - } - - EventPayload(AudioFormat audioFormat, - HotwordDetectedResult hotwordDetectedResult, - ParcelFileDescriptor audioStream) { - this(false, false, audioFormat, -1, null, hotwordDetectedResult, audioStream); - } - - private EventPayload(boolean triggerAvailable, boolean captureAvailable, - AudioFormat audioFormat, int captureSession, byte[] data, - HotwordDetectedResult hotwordDetectedResult, ParcelFileDescriptor audioStream) { - mTriggerAvailable = triggerAvailable; + private final List<KeyphraseRecognitionExtra> mKephraseExtras; + + private EventPayload(boolean captureAvailable, + @Nullable AudioFormat audioFormat, + int captureSession, + @DataFormat int dataFormat, + @Nullable byte[] data, + @Nullable HotwordDetectedResult hotwordDetectedResult, + @Nullable ParcelFileDescriptor audioStream, + @NonNull List<KeyphraseRecognitionExtra> keyphraseExtras) { mCaptureAvailable = captureAvailable; mCaptureSession = captureSession; mAudioFormat = audioFormat; + mDataFormat = dataFormat; mData = data; mHotwordDetectedResult = hotwordDetectedResult; mAudioStream = audioStream; + mKephraseExtras = keyphraseExtras; } /** @@ -400,10 +420,12 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector { * {@link #getCaptureAudioFormat()}. * * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO + * @deprecated Use {@link #getData()} instead. */ + @Deprecated @Nullable public byte[] getTriggerAudio() { - if (mTriggerAvailable) { + if (mDataFormat == DATA_FORMAT_TRIGGER_AUDIO) { return mData; } else { return null; @@ -411,6 +433,36 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector { } /** + * Conveys the format of the additional data that is triggered with the keyphrase event. + * + * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO + * @see DataFormat + */ + @DataFormat + public int getDataFormat() { + return mDataFormat; + } + + /** + * Gets additional raw data that is triggered with the keyphrase event. + * + * <p>A {@link android.hardware.soundtrigger.SoundTriggerModule} may populate this + * field with opaque data for use by system applications who know about voice + * engine internals. Data may be null if the field is not populated by the + * {@link android.hardware.soundtrigger.SoundTriggerModule}. + * + * <p>If {@link #getDataFormat()} is {@link #DATA_FORMAT_TRIGGER_AUDIO}, then the + * entirety of this buffer is expected to be of the format from + * {@link #getCaptureAudioFormat()}. + * + * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO + */ + @Nullable + public byte[] getData() { + return mData; + } + + /** * Gets the session ID to start a capture from the DSP. * This may be null if streaming capture isn't possible. * If non-null, the format of the audio that can be captured can be @@ -464,6 +516,166 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector { public ParcelFileDescriptor getAudioStream() { return mAudioStream; } + + /** + * Returns the keyphrases recognized by the voice engine with additional confidence + * information + * + * @return List of keyphrase extras describing additional data for each keyphrase the voice + * engine triggered on for this event. The ordering of the list is preserved based on what + * the ordering provided by {@link android.hardware.soundtrigger.SoundTriggerModule}. + */ + @NonNull + public List<KeyphraseRecognitionExtra> getKeyphraseRecognitionExtras() { + return mKephraseExtras; + } + + /** + * Builder class for {@link EventPayload} objects + * + * @hide + */ + @TestApi + public static final class Builder { + private boolean mCaptureAvailable = false; + private int mCaptureSession = -1; + private AudioFormat mAudioFormat = null; + @DataFormat + private int mDataFormat = DATA_FORMAT_RAW; + private byte[] mData = null; + private HotwordDetectedResult mHotwordDetectedResult = null; + private ParcelFileDescriptor mAudioStream = null; + private List<KeyphraseRecognitionExtra> mKeyphraseExtras = Collections.emptyList(); + + public Builder() {} + + Builder(SoundTrigger.KeyphraseRecognitionEvent keyphraseRecognitionEvent) { + setCaptureAvailable(keyphraseRecognitionEvent.isCaptureAvailable()); + setCaptureSession(keyphraseRecognitionEvent.getCaptureSession()); + if (keyphraseRecognitionEvent.getCaptureFormat() != null) { + setCaptureAudioFormat(keyphraseRecognitionEvent.getCaptureFormat()); + } + setDataFormat((keyphraseRecognitionEvent.triggerInData) ? DATA_FORMAT_TRIGGER_AUDIO + : DATA_FORMAT_RAW); + if (keyphraseRecognitionEvent.getData() != null) { + setData(keyphraseRecognitionEvent.getData()); + } + if (keyphraseRecognitionEvent.keyphraseExtras != null) { + setKeyphraseRecognitionExtras( + Arrays.asList(keyphraseRecognitionEvent.keyphraseExtras)); + } + } + + /** + * Indicates if {@code captureSession} can be used to continue capturing more audio from + * the DSP hardware. + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public Builder setCaptureAvailable(boolean captureAvailable) { + mCaptureAvailable = captureAvailable; + return this; + } + + /** + * Sets the session ID to start a capture from the DSP. + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public Builder setCaptureSession(int captureSession) { + mCaptureSession = captureSession; + return this; + } + + /** + * Sets the format of the audio obtained using {@link #getTriggerAudio()}. + */ + @NonNull + public Builder setCaptureAudioFormat(@NonNull AudioFormat audioFormat) { + mAudioFormat = audioFormat; + return this; + } + + /** + * Conveys the format of the additional data that is triggered with the keyphrase event. + * + * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO + * @see DataFormat + */ + @NonNull + public Builder setDataFormat(@DataFormat int dataFormat) { + mDataFormat = dataFormat; + return this; + } + + /** + * Sets additional raw data that is triggered with the keyphrase event. + * + * <p>A {@link android.hardware.soundtrigger.SoundTriggerModule} may populate this + * field with opaque data for use by system applications who know about voice + * engine internals. Data may be null if the field is not populated by the + * {@link android.hardware.soundtrigger.SoundTriggerModule}. + * + * <p>If {@link #getDataFormat()} is {@link #DATA_FORMAT_TRIGGER_AUDIO}, then the + * entirety of this + * buffer is expected to be of the format from {@link #getCaptureAudioFormat()}. + * + * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO + */ + @NonNull + public Builder setData(@NonNull byte[] data) { + mData = data; + return this; + } + + /** + * Sets {@link HotwordDetectedResult} associated with the hotword event, passed from + * {@link HotwordDetectionService}. + */ + @NonNull + public Builder setHotwordDetectedResult( + @NonNull HotwordDetectedResult hotwordDetectedResult) { + mHotwordDetectedResult = hotwordDetectedResult; + return this; + } + + /** + * Sets a stream with bytes corresponding to the open audio stream with hotword data. + * + * <p>This data represents an audio stream in the format returned by + * {@link #getCaptureAudioFormat}. + * + * <p>Clients are expected to start consuming the stream within 1 second of receiving + * the + * event. + */ + @NonNull + public Builder setAudioStream(@NonNull ParcelFileDescriptor audioStream) { + mAudioStream = audioStream; + return this; + } + + /** + * Sets the keyphrases recognized by the voice engine with additional confidence + * information + */ + @NonNull + public Builder setKeyphraseRecognitionExtras( + @NonNull List<KeyphraseRecognitionExtra> keyphraseRecognitionExtras) { + mKeyphraseExtras = keyphraseRecognitionExtras; + return this; + } + + /** + * Builds an {@link EventPayload} instance + */ + @NonNull + public EventPayload build() { + return new EventPayload(mCaptureAvailable, mAudioFormat, mCaptureSession, + mDataFormat, mData, mHotwordDetectedResult, mAudioStream, + mKeyphraseExtras); + } + } } /** @@ -993,11 +1205,14 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector { /** * Invalidates this hotword detector so that any future calls to this result * in an IllegalStateException. - * - * @hide */ - void invalidate() { + @Override + public void destroy() { synchronized (mLock) { + if (mAvailability == STATE_KEYPHRASE_ENROLLED) { + stopRecognition(); + } + mAvailability = STATE_INVALID; notifyStateChangedLocked(); @@ -1009,6 +1224,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector { } } } + super.destroy(); } /** @@ -1171,8 +1387,9 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector { Slog.i(TAG, "onDetected"); } Message.obtain(mHandler, MSG_HOTWORD_DETECTED, - new EventPayload(event.triggerInData, event.captureAvailable, - event.captureFormat, event.captureSession, event.data, result)) + new EventPayload.Builder(event) + .setHotwordDetectedResult(result) + .build()) .sendToTarget(); } @Override diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java index e3bb589c9a19..dfe0f542b3ca 100644 --- a/core/java/android/service/voice/HotwordDetectionService.java +++ b/core/java/android/service/voice/HotwordDetectionService.java @@ -140,9 +140,7 @@ public abstract class HotwordDetectionService extends Service { Log.d(TAG, "#detectFromDspSource"); } HotwordDetectionService.this.onDetect( - new AlwaysOnHotwordDetector.EventPayload( - event.triggerInData, event.captureAvailable, - event.captureFormat, event.captureSession, event.data), + new AlwaysOnHotwordDetector.EventPayload.Builder(event).build(), timeoutMillis, new Callback(callback)); } diff --git a/core/java/android/service/voice/HotwordDetector.java b/core/java/android/service/voice/HotwordDetector.java index 969ec22beb97..96fd8bbda016 100644 --- a/core/java/android/service/voice/HotwordDetector.java +++ b/core/java/android/service/voice/HotwordDetector.java @@ -119,6 +119,17 @@ public interface HotwordDetector { void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory); /** + * Invalidates this hotword detector so that any future calls to this result + * in an {@link IllegalStateException}. + * + * <p>If there are no other {@link HotwordDetector} instances linked to the + * {@link HotwordDetectionService}, the service will be shutdown. + */ + default void destroy() { + throw new UnsupportedOperationException("Not implemented. Must override in a subclass."); + } + + /** * @hide */ static String detectorTypeToString(int detectorType) { diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java index 512a654adbab..2d662eaf0a4f 100644 --- a/core/java/android/service/voice/SoftwareHotwordDetector.java +++ b/core/java/android/service/voice/SoftwareHotwordDetector.java @@ -77,7 +77,7 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector { if (DEBUG) { Slog.i(TAG, "#startRecognition"); } - + throwIfDetectorIsNoLongerActive(); maybeCloseExistingSession(); try { @@ -100,6 +100,7 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector { if (DEBUG) { Slog.i(TAG, "#stopRecognition"); } + throwIfDetectorIsNoLongerActive(); try { mManagerService.stopListeningFromMic(); @@ -110,6 +111,19 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector { return true; } + @Override + public void destroy() { + stopRecognition(); + maybeCloseExistingSession(); + + try { + mManagerService.shutdownHotwordDetectionService(); + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); + } + super.destroy(); + } + private void maybeCloseExistingSession() { // TODO: needs to be synchronized. // TODO: implement this @@ -135,8 +149,11 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector { mHandler.sendMessage(obtainMessage( HotwordDetector.Callback::onDetected, mCallback, - new AlwaysOnHotwordDetector.EventPayload( - audioFormat, hotwordDetectedResult, audioStream))); + new AlwaysOnHotwordDetector.EventPayload.Builder() + .setCaptureAudioFormat(audioFormat) + .setAudioStream(audioStream) + .setHotwordDetectedResult(hotwordDetectedResult) + .build())); } } diff --git a/core/java/android/service/voice/VoiceInteractionManagerInternal.java b/core/java/android/service/voice/VoiceInteractionManagerInternal.java index c80640910bc2..b20dccc8adfa 100644 --- a/core/java/android/service/voice/VoiceInteractionManagerInternal.java +++ b/core/java/android/service/voice/VoiceInteractionManagerInternal.java @@ -50,6 +50,13 @@ public abstract class VoiceInteractionManagerInternal { public abstract boolean hasActiveSession(String packageName); /** + * Returns the package name of the active session. + * + * @param callingVoiceInteractor the voice interactor binder from the calling VoiceInteractor. + */ + public abstract String getVoiceInteractorPackageName(IBinder callingVoiceInteractor); + + /** * Gets the identity of the currently active HotwordDetectionService. * * @see HotwordDetectionServiceIdentity @@ -82,4 +89,4 @@ public abstract class VoiceInteractionManagerInternal { return mOwnerUid; } } -}
\ No newline at end of file +} diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index f52c9ff210d6..bf0cfbe49f31 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -271,7 +271,7 @@ public class VoiceInteractionService extends Service { // It's still guaranteed to have been stopped. // This helps with cases where the voice interaction implementation is changed // by the user. - safelyShutdownHotwordDetector(); + safelyShutdownAllHotwordDetectors(); } /** @@ -380,11 +380,13 @@ public class VoiceInteractionService extends Service { } synchronized (mLock) { // Allow only one concurrent recognition via the APIs. - safelyShutdownHotwordDetector(); + safelyShutdownAllHotwordDetectors(); mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback, mKeyphraseEnrollmentInfo, mSystemService, getApplicationContext().getApplicationInfo().targetSdkVersion, supportHotwordDetectionService, options, sharedMemory); + mHotwordDetector.registerOnDestroyListener((detector) -> onDspHotwordDetectorDestroyed( + (AlwaysOnHotwordDetector) detector)); } return mHotwordDetector; } @@ -433,10 +435,13 @@ public class VoiceInteractionService extends Service { } synchronized (mLock) { // Allow only one concurrent recognition via the APIs. - safelyShutdownHotwordDetector(); + safelyShutdownAllHotwordDetectors(); mSoftwareHotwordDetector = new SoftwareHotwordDetector( mSystemService, null, options, sharedMemory, callback); + mSoftwareHotwordDetector.registerOnDestroyListener( + (detector) -> onMicrophoneHotwordDetectorDestroyed( + (SoftwareHotwordDetector) detector)); } return mSoftwareHotwordDetector; } @@ -482,51 +487,36 @@ public class VoiceInteractionService extends Service { return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null; } - private void safelyShutdownHotwordDetector() { + private void safelyShutdownAllHotwordDetectors() { synchronized (mLock) { - shutdownDspHotwordDetectorLocked(); - shutdownMicrophoneHotwordDetectorLocked(); - } - } - - private void shutdownDspHotwordDetectorLocked() { - if (mHotwordDetector == null) { - return; - } - - try { - mHotwordDetector.stopRecognition(); - } catch (Exception ex) { - // Ignore. - } + if (mHotwordDetector != null) { + try { + mHotwordDetector.destroy(); + } catch (Exception ex) { + Log.i(TAG, "exception destroying AlwaysOnHotwordDetector", ex); + } + } - try { - mHotwordDetector.invalidate(); - } catch (Exception ex) { - // Ignore. + if (mSoftwareHotwordDetector != null) { + try { + mSoftwareHotwordDetector.destroy(); + } catch (Exception ex) { + Log.i(TAG, "exception destroying SoftwareHotwordDetector", ex); + } + } } - - mHotwordDetector = null; } - private void shutdownMicrophoneHotwordDetectorLocked() { - if (mSoftwareHotwordDetector == null) { - return; - } - - try { - mSoftwareHotwordDetector.stopRecognition(); - } catch (Exception ex) { - // Ignore. + private void onDspHotwordDetectorDestroyed(@NonNull AlwaysOnHotwordDetector detector) { + synchronized (mLock) { + mHotwordDetector = null; } + } - try { - mSystemService.shutdownHotwordDetectionService(); - } catch (Exception ex) { - // Ignore. + private void onMicrophoneHotwordDetectorDestroyed(@NonNull SoftwareHotwordDetector detector) { + synchronized (mLock) { + mSoftwareHotwordDetector = null; } - - mSoftwareHotwordDetector = null; } /** diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index c91851a8896d..a2938a8783eb 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -26,6 +26,8 @@ import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; import static android.view.View.SYSTEM_UI_FLAG_VISIBLE; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.FloatRange; import android.annotation.NonNull; @@ -890,8 +892,6 @@ public abstract class WallpaperService extends Service { * @param dimAmount Float amount between [0.0, 1.0] to dim the wallpaper. */ private void updateWallpaperDimming(float dimAmount) { - mPreviousWallpaperDimAmount = mWallpaperDimAmount; - // Custom dim amount cannot be less than the default dim amount. mWallpaperDimAmount = Math.max(mDefaultDimAmount, dimAmount); // If dim amount is 0f (additional dimming is removed), then the wallpaper should dim @@ -909,15 +909,15 @@ public abstract class WallpaperService extends Service { SurfaceControl.Transaction surfaceControlTransaction = new SurfaceControl.Transaction(); // TODO: apply the dimming to preview as well once surface transparency works in // preview mode. - if (!isPreview() && mShouldDim) { + if ((!isPreview() && mShouldDim) + || mPreviousWallpaperDimAmount != mWallpaperDimAmount) { Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount); // Animate dimming to gradually change the wallpaper alpha from the previous // dim amount to the new amount only if the dim amount changed. ValueAnimator animator = ValueAnimator.ofFloat( mPreviousWallpaperDimAmount, mWallpaperDimAmount); - animator.setDuration(mPreviousWallpaperDimAmount == mWallpaperDimAmount - ? 0 : DIMMING_ANIMATION_DURATION_MS); + animator.setDuration(DIMMING_ANIMATION_DURATION_MS); animator.addUpdateListener((ValueAnimator va) -> { final float dimValue = (float) va.getAnimatedValue(); if (mBbqSurfaceControl != null) { @@ -925,11 +925,19 @@ public abstract class WallpaperService extends Service { .setAlpha(mBbqSurfaceControl, 1 - dimValue).apply(); } }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + updateSurface(false, false, true); + } + }); animator.start(); } else { Log.v(TAG, "Setting wallpaper dimming: " + 0); surfaceControlTransaction.setAlpha(mBbqSurfaceControl, 1.0f).apply(); } + + mPreviousWallpaperDimAmount = mWallpaperDimAmount; } /** @@ -1332,6 +1340,7 @@ public abstract class WallpaperService extends Service { resetWindowPages(); mSession.finishDrawing(mWindow, null /* postDrawTransaction */); processLocalColors(mPendingXOffset, mPendingXOffsetStep); + notifyColorsChanged(); } reposition(); reportEngineShown(shouldWaitForEngineShown()); diff --git a/core/java/android/speech/RecognitionSupport.java b/core/java/android/speech/RecognitionSupport.java index 3a86d0b30639..43c8e1b956f0 100644 --- a/core/java/android/speech/RecognitionSupport.java +++ b/core/java/android/speech/RecognitionSupport.java @@ -36,13 +36,16 @@ public final class RecognitionSupport implements Parcelable { /** Support for this request is ready for use on this device for the returned languages. */ @NonNull + @DataClass.PluralOf("installedLanguage") private List<String> mInstalledLanguages = null; /** Support for this request is scheduled for download for the returned languages. */ + @DataClass.PluralOf("pendingLanguage") @NonNull private List<String> mPendingLanguages = null; /** These languages are supported but need to be downloaded before use. */ @NonNull + @DataClass.PluralOf("supportedLanguage") private List<String> mSupportedLanguages = null; @@ -231,10 +234,7 @@ public final class RecognitionSupport implements Parcelable { /** @see #setInstalledLanguages */ @DataClass.Generated.Member - public @NonNull Builder addInstalledLanguages(@NonNull String value) { - // You can refine this method's name by providing item's singular name, e.g.: - // @DataClass.PluralOf("item")) mItems = ... - + public @NonNull Builder addInstalledLanguage(@NonNull String value) { if (mInstalledLanguages == null) setInstalledLanguages(new java.util.ArrayList<>()); mInstalledLanguages.add(value); return this; @@ -253,10 +253,7 @@ public final class RecognitionSupport implements Parcelable { /** @see #setPendingLanguages */ @DataClass.Generated.Member - public @NonNull Builder addPendingLanguages(@NonNull String value) { - // You can refine this method's name by providing item's singular name, e.g.: - // @DataClass.PluralOf("item")) mItems = ... - + public @NonNull Builder addPendingLanguage(@NonNull String value) { if (mPendingLanguages == null) setPendingLanguages(new java.util.ArrayList<>()); mPendingLanguages.add(value); return this; @@ -275,10 +272,7 @@ public final class RecognitionSupport implements Parcelable { /** @see #setSupportedLanguages */ @DataClass.Generated.Member - public @NonNull Builder addSupportedLanguages(@NonNull String value) { - // You can refine this method's name by providing item's singular name, e.g.: - // @DataClass.PluralOf("item")) mItems = ... - + public @NonNull Builder addSupportedLanguage(@NonNull String value) { if (mSupportedLanguages == null) setSupportedLanguages(new java.util.ArrayList<>()); mSupportedLanguages.add(value); return this; @@ -314,10 +308,10 @@ public final class RecognitionSupport implements Parcelable { } @DataClass.Generated( - time = 1639158640137L, + time = 1644582623366L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/speech/RecognitionSupport.java", - inputSignatures = "private @android.annotation.NonNull java.util.List<java.lang.String> mInstalledLanguages\nprivate @android.annotation.NonNull java.util.List<java.lang.String> mPendingLanguages\nprivate @android.annotation.NonNull java.util.List<java.lang.String> mSupportedLanguages\nclass RecognitionSupport extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + inputSignatures = "private @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"installedLanguage\") java.util.List<java.lang.String> mInstalledLanguages\nprivate @com.android.internal.util.DataClass.PluralOf(\"pendingLanguage\") @android.annotation.NonNull java.util.List<java.lang.String> mPendingLanguages\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"supportedLanguage\") java.util.List<java.lang.String> mSupportedLanguages\nclass RecognitionSupport extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index 502558e355e6..e075c05279a2 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -496,6 +496,17 @@ public class SpeechRecognizer { Objects.requireNonNull(recognizerIntent, "intent must not be null"); Objects.requireNonNull(supportListener, "listener must not be null"); + if (DBG) { + Slog.i(TAG, "#checkRecognitionSupport called"); + if (mService == null) { + Slog.i(TAG, "Connection is not established yet"); + } + } + + if (mService == null) { + // First time connection: first establish a connection, then dispatch. + connectToSystemService(); + } putMessage(Message.obtain(mHandler, MSG_CHECK_RECOGNITION_SUPPORT, Pair.create(recognizerIntent, supportListener))); } @@ -510,7 +521,17 @@ public class SpeechRecognizer { */ public void triggerModelDownload(@NonNull Intent recognizerIntent) { Objects.requireNonNull(recognizerIntent, "intent must not be null"); - putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD)); + if (DBG) { + Slog.i(TAG, "#triggerModelDownload called"); + if (mService == null) { + Slog.i(TAG, "Connection is not established yet"); + } + } + if (mService == null) { + // First time connection: first establish a connection, then dispatch. + connectToSystemService(); + } + putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, recognizerIntent)); } /** @@ -625,7 +646,6 @@ public class SpeechRecognizer { } try { mService.triggerModelDownload(recognizerIntent); - if (DBG) Log.d(TAG, "service download support command succeeded"); } catch (final RemoteException e) { Log.e(TAG, "downloadModel() failed", e); mListener.onError(ERROR_CLIENT); @@ -705,6 +725,9 @@ public class SpeechRecognizer { } private synchronized boolean maybeInitializeManagerService() { + if (DBG) { + Log.i(TAG, "#maybeInitializeManagerService found = " + mManagerService); + } if (mManagerService != null) { return true; } @@ -712,8 +735,13 @@ public class SpeechRecognizer { mManagerService = IRecognitionServiceManager.Stub.asInterface( ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE)); - if (mManagerService == null && mListener != null) { - mListener.onError(ERROR_CLIENT); + if (DBG) { + Log.i(TAG, "#maybeInitializeManagerService instantiated =" + mManagerService); + } + if (mManagerService == null) { + if (mListener != null) { + mListener.onError(ERROR_CLIENT); + } return false; } return true; diff --git a/core/java/android/text/BidiFormatter.java b/core/java/android/text/BidiFormatter.java index 422d3472cb8d..dfa172df72ea 100644 --- a/core/java/android/text/BidiFormatter.java +++ b/core/java/android/text/BidiFormatter.java @@ -31,7 +31,7 @@ import java.util.Locale; * directionality of the text can be either estimated or passed in when known. * * <p>To support versions lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, - * you can use the support library's {@link android.support.v4.text.BidiFormatter} class. + * you can use the support library's {@link androidx.core.text.BidiFormatter} class. * * <p>These APIs provides the following functionality: * <p> diff --git a/core/java/android/text/TextDirectionHeuristics.java b/core/java/android/text/TextDirectionHeuristics.java index 354c15fae66f..85260f4af2c8 100644 --- a/core/java/android/text/TextDirectionHeuristics.java +++ b/core/java/android/text/TextDirectionHeuristics.java @@ -28,7 +28,7 @@ import java.nio.CharBuffer; * provided in the {@link android.view.View} class for {@link android.view.View#setTextDirection * setTextDirection()}, such as {@link android.view.View#TEXT_DIRECTION_RTL}. * <p>To support versions lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, - * you can use the support library's {@link android.support.v4.text.TextDirectionHeuristicsCompat} + * you can use the support library's {@link androidx.core.text.TextDirectionHeuristicsCompat} * class. * */ diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java index 4a48832c1088..ad044af91d59 100644 --- a/core/java/android/text/style/SuggestionSpan.java +++ b/core/java/android/text/style/SuggestionSpan.java @@ -217,6 +217,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); mMisspelledUnderlineColor = typedArray.getColor( com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); + typedArray.recycle(); defStyleAttr = com.android.internal.R.attr.textAppearanceGrammarErrorSuggestion; typedArray = context.obtainStyledAttributes( @@ -225,6 +226,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); mGrammarErrorUnderlineColor = typedArray.getColor( com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); + typedArray.recycle(); defStyleAttr = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion; typedArray = context.obtainStyledAttributes( @@ -233,6 +235,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); mEasyCorrectUnderlineColor = typedArray.getColor( com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); + typedArray.recycle(); defStyleAttr = com.android.internal.R.attr.textAppearanceAutoCorrectionSuggestion; typedArray = context.obtainStyledAttributes( @@ -241,6 +244,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); mAutoCorrectionUnderlineColor = typedArray.getColor( com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); + typedArray.recycle(); } public SuggestionSpan(Parcel src) { diff --git a/core/java/android/tracing/OWNERS b/core/java/android/tracing/OWNERS index f5de4eb05c54..7d1b48b69c9e 100644 --- a/core/java/android/tracing/OWNERS +++ b/core/java/android/tracing/OWNERS @@ -1,2 +1,2 @@ -cfijalkovich@google.com carmenjackson@google.com +include platform/external/perfetto:/OWNERS diff --git a/core/java/android/util/RotationUtils.java b/core/java/android/util/RotationUtils.java index 0ac2c9c25ad1..cebdbf669f3a 100644 --- a/core/java/android/util/RotationUtils.java +++ b/core/java/android/util/RotationUtils.java @@ -24,8 +24,10 @@ import static android.view.Surface.ROTATION_90; import android.annotation.Dimension; import android.graphics.Insets; import android.graphics.Matrix; +import android.graphics.Point; import android.graphics.Rect; import android.view.Surface.Rotation; +import android.view.SurfaceControl; /** * A class containing utility methods related to rotation. @@ -121,13 +123,64 @@ public class RotationUtils { /** @return the rotation needed to rotate from oldRotation to newRotation. */ @Rotation - public static int deltaRotation(int oldRotation, int newRotation) { + public static int deltaRotation(@Rotation int oldRotation, @Rotation int newRotation) { int delta = newRotation - oldRotation; if (delta < 0) delta += 4; return delta; } /** + * Rotates a surface CCW around the origin (eg. a 90-degree rotation will result in the + * bottom-left being at the origin). Use {@link #rotatePoint} to transform the top-left + * corner appropriately. + */ + public static void rotateSurface(SurfaceControl.Transaction t, SurfaceControl sc, + @Rotation int rotation) { + // Note: the matrix values look inverted, but they aren't because our coordinate-space + // is actually left-handed. + // Note: setMatrix expects values in column-major order. + switch (rotation) { + case ROTATION_0: + t.setMatrix(sc, 1.f, 0.f, 0.f, 1.f); + break; + case ROTATION_90: + t.setMatrix(sc, 0.f, -1.f, 1.f, 0.f); + break; + case ROTATION_180: + t.setMatrix(sc, -1.f, 0.f, 0.f, -1.f); + break; + case ROTATION_270: + t.setMatrix(sc, 0.f, 1.f, -1.f, 0.f); + break; + } + } + + /** + * Rotates a point CCW within a rectangle of size parentW x parentH with top/left at the + * origin as if the point is stuck to the rectangle. The rectangle is transformed such that + * it's top/left remains at the origin after the rotation. + */ + public static void rotatePoint(Point inOutPoint, @Rotation int rotation, + int parentW, int parentH) { + int origX = inOutPoint.x; + switch (rotation) { + case ROTATION_0: + return; + case ROTATION_90: + inOutPoint.x = inOutPoint.y; + inOutPoint.y = parentW - origX; + return; + case ROTATION_180: + inOutPoint.x = parentW - inOutPoint.x; + inOutPoint.y = parentH - inOutPoint.y; + return; + case ROTATION_270: + inOutPoint.x = parentH - inOutPoint.y; + inOutPoint.y = origX; + } + } + + /** * Sets a matrix such that given a rotation, it transforms physical display * coordinates to that rotation's logical coordinates. * diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java index 117d75e0981c..3aeeccaba250 100644 --- a/core/java/android/util/Slog.java +++ b/core/java/android/util/Slog.java @@ -16,6 +16,9 @@ package android.util; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; @@ -24,118 +27,265 @@ import android.os.Build; * * <p>Should be used by system components. Use {@code adb logcat --buffer=system} to fetch the logs. * + * @see Log * @hide */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class Slog { private Slog() { } + /** + * Logs {@code msg} at {@link Log#VERBOSE} level. + * + * @param tag identifies the source of a log message. It usually represents system service, + * e.g. {@code PackageManager}. + * @param msg the message to log. + * + * @see Log#v(String, String) + */ @UnsupportedAppUsage - public static int v(String tag, String msg) { + public static int v(@Nullable String tag, @NonNull String msg) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.VERBOSE, tag, msg); } - public static int v(String tag, String msg, Throwable tr) { + /** + * Logs {@code msg} at {@link Log#VERBOSE} level, attaching stack trace of the {@code tr} to + * the end of the log statement. + * + * @param tag identifies the source of a log message. It usually represents system service, + * e.g. {@code PackageManager}. + * @param msg the message to log. + * @param tr an exception to log. + * + * @see Log#v(String, String, Throwable) + */ + public static int v(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.VERBOSE, tag, msg + '\n' + Log.getStackTraceString(tr)); } + /** + * Logs {@code msg} at {@link Log#DEBUG} level. + * + * @param tag identifies the source of a log message. It usually represents system service, + * e.g. {@code PackageManager}. + * @param msg the message to log. + * + * @see Log#d(String, String) + */ @UnsupportedAppUsage - public static int d(String tag, String msg) { + public static int d(@Nullable String tag, @NonNull String msg) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag, msg); } + /** + * Logs {@code msg} at {@link Log#DEBUG} level, attaching stack trace of the {@code tr} to + * the end of the log statement. + * + * @param tag identifies the source of a log message. It usually represents system service, + * e.g. {@code PackageManager}. + * @param msg the message to log. + * @param tr an exception to log. + * + * @see Log#d(String, String, Throwable) + */ @UnsupportedAppUsage - public static int d(String tag, String msg, Throwable tr) { + public static int d(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag, msg + '\n' + Log.getStackTraceString(tr)); } + /** + * Logs {@code msg} at {@link Log#INFO} level. + * + * @param tag identifies the source of a log message. It usually represents system service, + * e.g. {@code PackageManager}. + * @param msg the message to log. + * + * @see Log#i(String, String) + */ @UnsupportedAppUsage - public static int i(String tag, String msg) { + public static int i(@Nullable String tag, @NonNull String msg) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.INFO, tag, msg); } - public static int i(String tag, String msg, Throwable tr) { + /** + * Logs {@code msg} at {@link Log#INFO} level, attaching stack trace of the {@code tr} to + * the end of the log statement. + * + * @param tag identifies the source of a log message. It usually represents system service, + * e.g. {@code PackageManager}. + * @param msg the message to log. + * @param tr an exception to log. + * + * @see Log#i(String, String, Throwable) + */ + public static int i(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.INFO, tag, msg + '\n' + Log.getStackTraceString(tr)); } + /** + * Logs {@code msg} at {@link Log#WARN} level. + * + * @param tag identifies the source of a log message. It usually represents system service, + * e.g. {@code PackageManager}. + * @param msg the message to log. + * + * @see Log#w(String, String) + */ @UnsupportedAppUsage - public static int w(String tag, String msg) { + public static int w(@Nullable String tag, @NonNull String msg) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, msg); } + /** + * Logs {@code msg} at {@link Log#WARN} level, attaching stack trace of the {@code tr} to + * the end of the log statement. + * + * @param tag identifies the source of a log message. It usually represents system service, + * e.g. {@code PackageManager}. + * @param msg the message to log. + * @param tr an exception to log. + * + * @see Log#w(String, String, Throwable) + */ @UnsupportedAppUsage - public static int w(String tag, String msg, Throwable tr) { + public static int w(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, msg + '\n' + Log.getStackTraceString(tr)); } - public static int w(String tag, Throwable tr) { + /** + * Logs stack trace of {@code tr} at {@link Log#WARN} level. + * + * @param tag identifies the source of a log message. It usually represents system service, + * e.g. {@code PackageManager}. + * @param tr an exception to log. + * + * @see Log#w(String, Throwable) + */ + public static int w(@Nullable String tag, @Nullable Throwable tr) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, Log.getStackTraceString(tr)); } + /** + * Logs {@code msg} at {@link Log#ERROR} level. + * + * @param tag identifies the source of a log message. It usually represents system service, + * e.g. {@code PackageManager}. + * @param msg the message to log. + * + * @see Log#e(String, String) + */ @UnsupportedAppUsage - public static int e(String tag, String msg) { + public static int e(@Nullable String tag, @NonNull String msg) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag, msg); } + /** + * Logs {@code msg} at {@link Log#ERROR} level, attaching stack trace of the {@code tr} to + * the end of the log statement. + * + * @param tag identifies the source of a log message. It usually represents system service, + * e.g. {@code PackageManager}. + * @param msg the message to log. + * @param tr an exception to log. + * + * @see Log#e(String, String, Throwable) + */ @UnsupportedAppUsage - public static int e(String tag, String msg, Throwable tr) { + public static int e(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) { return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag, msg + '\n' + Log.getStackTraceString(tr)); } /** - * Like {@link Log#wtf(String, String)}, but will never cause the caller to crash, and - * will always be handled asynchronously. Primarily for use by coding running within - * the system process. + * Logs a condition that should never happen. + * + * <p> + * Similar to {@link Log#wtf(String, String)}, but will never cause the caller to crash, and + * will always be handled asynchronously. Primarily to be used by the system server. + * + * @param tag identifies the source of a log message. It usually represents system service, + * e.g. {@code PackageManager}. + * @param msg the message to log. + * + * @see Log#wtf(String, String) */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static int wtf(String tag, String msg) { + public static int wtf(@Nullable String tag, @NonNull String msg) { return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, false, true); } /** - * Like {@link #wtf(String, String)}, but does not output anything to the log. + * Similar to {@link #wtf(String, String)}, but does not output anything to the log. */ - public static void wtfQuiet(String tag, String msg) { + public static void wtfQuiet(@Nullable String tag, @NonNull String msg) { Log.wtfQuiet(Log.LOG_ID_SYSTEM, tag, msg, true); } /** - * Like {@link Log#wtfStack(String, String)}, but will never cause the caller to crash, and - * will always be handled asynchronously. Primarily for use by coding running within - * the system process. + * Logs a condition that should never happen, attaching the full call stack to the log. + * + * <p> + * Similar to {@link Log#wtfStack(String, String)}, but will never cause the caller to crash, + * and will always be handled asynchronously. Primarily to be used by the system server. + * + * @param tag identifies the source of a log message. It usually represents system service, + * e.g. {@code PackageManager}. + * @param msg the message to log. + * + * @see Log#wtfStack(String, String) */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - public static int wtfStack(String tag, String msg) { + public static int wtfStack(@Nullable String tag, @NonNull String msg) { return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, true, true); } /** - * Like {@link Log#wtf(String, Throwable)}, but will never cause the caller to crash, - * and will always be handled asynchronously. Primarily for use by coding running within - * the system process. + * Logs a condition that should never happen, attaching stack trace of the {@code tr} to the + * end of the log statement. + * + * <p> + * Similar to {@link Log#wtf(String, Throwable)}, but will never cause the caller to crash, + * and will always be handled asynchronously. Primarily to be used by the system server. + * + * @param tag identifies the source of a log message. It usually represents system service, + * e.g. {@code PackageManager}. + * @param tr an exception to log. + * + * @see Log#wtf(String, Throwable) */ - public static int wtf(String tag, Throwable tr) { + public static int wtf(@Nullable String tag, @Nullable Throwable tr) { return Log.wtf(Log.LOG_ID_SYSTEM, tag, tr.getMessage(), tr, false, true); } /** - * Like {@link Log#wtf(String, String, Throwable)}, but will never cause the caller to crash, - * and will always be handled asynchronously. Primarily for use by coding running within - * the system process. + * Logs a condition that should never happen, attaching stack trace of the {@code tr} to the + * end of the log statement. + * + * <p> + * Similar to {@link Log#wtf(String, String, Throwable)}, but will never cause the caller to + * crash, and will always be handled asynchronously. Primarily to be used by the system server. + * + * @param tag identifies the source of a log message. It usually represents system service, + * e.g. {@code PackageManager}. + * @param msg the message to log. + * @param tr an exception to log. + * + * @see Log#wtf(String, String, Throwable) */ @UnsupportedAppUsage - public static int wtf(String tag, String msg, Throwable tr) { + public static int wtf(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) { return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, tr, false, true); } + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static int println(int priority, String tag, String msg) { + public static int println(@Log.Level int priority, @Nullable String tag, @NonNull String msg) { return Log.println_native(Log.LOG_ID_SYSTEM, priority, tag, msg); } } diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index de56d3ad7502..43c07c8ab97e 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -52,9 +52,10 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; +import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -348,6 +349,11 @@ public final class AccessibilityInteractionController { View requestedView = null; AccessibilityNodeInfo requestedNode = null; + boolean interruptPrefetch = + ((flags & AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE) == 0); + + ArrayList<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; + infos.clear(); try { if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { return; @@ -357,27 +363,46 @@ public final class AccessibilityInteractionController { if (requestedView != null && isShown(requestedView)) { requestedNode = populateAccessibilityNodeInfoForView( requestedView, arguments, virtualDescendantId); + mPrefetcher.mInterruptPrefetch = interruptPrefetch; + mPrefetcher.mFetchFlags = flags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK; + + if (!interruptPrefetch) { + infos.add(requestedNode); + mPrefetcher.prefetchAccessibilityNodeInfos(requestedView, + requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode), + infos); + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + } } } finally { - updateInfoForViewportAndReturnFindNodeResult( - requestedNode == null ? null : AccessibilityNodeInfo.obtain(requestedNode), - callback, interactionId, spec, interactiveRegion); + if (!interruptPrefetch) { + // Return found node and prefetched nodes in one IPC. + updateInfosForViewportAndReturnFindNodeResult(infos, callback, interactionId, spec, + interactiveRegion); + + final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest = + getSatisfiedRequestInPrefetch(requestedNode == null ? null : requestedNode, + infos, flags); + if (satisfiedRequest != null) { + returnFindNodeResult(satisfiedRequest); + } + return; + } else { + // Return found node. + updateInfoForViewportAndReturnFindNodeResult( + requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode), + callback, interactionId, spec, interactiveRegion); + } } - ArrayList<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; - infos.clear(); mPrefetcher.prefetchAccessibilityNodeInfos(requestedView, - requestedNode == null ? null : AccessibilityNodeInfo.obtain(requestedNode), - virtualDescendantId, flags, infos); + requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode), infos); mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfosForViewPort(infos, spec, interactiveRegion); final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest = - getSatisfiedRequestInPrefetch( - requestedNode == null ? null : requestedNode, infos, flags); - - if (satisfiedRequest != null && satisfiedRequest.mSatisfiedRequestNode != requestedNode) { - infos.remove(satisfiedRequest.mSatisfiedRequestNode); - } + getSatisfiedRequestInPrefetch(requestedNode == null ? null : requestedNode, infos, + flags); + // Return prefetch result separately. returnPrefetchResult(interactionId, infos, callback); if (satisfiedRequest != null) { @@ -1077,6 +1102,11 @@ public final class AccessibilityInteractionController { } } mPendingFindNodeByIdMessages.clear(); + // Remove node from prefetched infos. + if (satisfiedRequest != null && satisfiedRequest.mSatisfiedRequestNode + != requestedNode) { + infos.remove(satisfiedRequest.mSatisfiedRequestNode); + } return satisfiedRequest; } } @@ -1149,45 +1179,76 @@ public final class AccessibilityInteractionController { */ private class AccessibilityNodePrefetcher { - private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50; - private final ArrayList<View> mTempViewList = new ArrayList<View>(); + private boolean mInterruptPrefetch; + private int mFetchFlags; public void prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root, - int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos) { + List<AccessibilityNodeInfo> outInfos) { if (root == null) { return; } AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); + final boolean prefetchPredecessors = + isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS); if (provider == null) { - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { + if (prefetchPredecessors) { prefetchPredecessorsOfRealNode(view, outInfos); } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { - prefetchSiblingsOfRealNode(view, outInfos); + if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS)) { + prefetchSiblingsOfRealNode(view, outInfos, prefetchPredecessors); } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { + if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID)) { prefetchDescendantsOfRealNode(view, outInfos); } } else { - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { + if (prefetchPredecessors) { prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos); } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { - prefetchSiblingsOfVirtualNode(root, view, provider, outInfos); + if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS)) { + prefetchSiblingsOfVirtualNode(root, view, provider, outInfos, + prefetchPredecessors); } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { + if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID)) { prefetchDescendantsOfVirtualNode(root, provider, outInfos); } } + if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST) + || isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST)) { + if (shouldStopPrefetching(outInfos)) { + return; + } + PrefetchDeque<DequeNode> deque = new PrefetchDeque<>( + mFetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_MASK, + outInfos); + addChildrenOfRoot(view, root, provider, deque); + deque.performTraversalAndPrefetch(); + } if (ENFORCE_NODE_TREE_CONSISTENT) { enforceNodeTreeConsistent(root, outInfos); } } - private boolean shouldStopPrefetching(List prefetchededInfos) { - return mHandler.hasUserInteractiveMessagesWaiting() - || prefetchededInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE; + private void addChildrenOfRoot(View root, AccessibilityNodeInfo rootInfo, + AccessibilityNodeProvider rootProvider, PrefetchDeque deque) { + DequeNode rootDequeNode; + if (rootProvider == null) { + rootDequeNode = new ViewNode(root); + } else { + rootDequeNode = new VirtualNode( + AccessibilityNodeProvider.HOST_VIEW_ID, rootProvider); + } + rootDequeNode.addChildren(rootInfo, deque); + } + + private boolean isFlagSet(@AccessibilityNodeInfo.PrefetchingStrategy int strategy) { + return (mFetchFlags & strategy) != 0; + } + + public boolean shouldStopPrefetching(List prefetchedInfos) { + return ((mHandler.hasUserInteractiveMessagesWaiting() && mInterruptPrefetch) + || prefetchedInfos.size() + >= AccessibilityNodeInfo.MAX_NUMBER_OF_PREFETCHED_NODES); } private void enforceNodeTreeConsistent( @@ -1283,7 +1344,7 @@ public final class AccessibilityInteractionController { } private void prefetchSiblingsOfRealNode(View current, - List<AccessibilityNodeInfo> outInfos) { + List<AccessibilityNodeInfo> outInfos, boolean predecessorsPrefetched) { if (shouldStopPrefetching(outInfos)) { return; } @@ -1293,6 +1354,13 @@ public final class AccessibilityInteractionController { ArrayList<View> children = mTempViewList; children.clear(); try { + if (!predecessorsPrefetched) { + AccessibilityNodeInfo parentInfo = + ((ViewGroup) parent).createAccessibilityNodeInfo(); + if (parentInfo != null) { + outInfos.add(parentInfo); + } + } parentGroup.addChildrenForAccessibility(children); final int childCount = children.size(); for (int i = 0; i < childCount; i++) { @@ -1304,7 +1372,7 @@ public final class AccessibilityInteractionController { && isShown(child)) { AccessibilityNodeInfo info = null; AccessibilityNodeProvider provider = - child.getAccessibilityNodeProvider(); + child.getAccessibilityNodeProvider(); if (provider == null) { info = child.createAccessibilityNodeInfo(); } else { @@ -1327,8 +1395,8 @@ public final class AccessibilityInteractionController { if (shouldStopPrefetching(outInfos) || !(root instanceof ViewGroup)) { return; } - HashMap<View, AccessibilityNodeInfo> addedChildren = - new HashMap<View, AccessibilityNodeInfo>(); + LinkedHashMap<View, AccessibilityNodeInfo> addedChildren = + new LinkedHashMap<View, AccessibilityNodeInfo>(); ArrayList<View> children = mTempViewList; children.clear(); try { @@ -1414,17 +1482,21 @@ public final class AccessibilityInteractionController { } private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, - AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { + AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos, + boolean predecessorsPrefetched) { final long parentNodeId = current.getParentNodeId(); final int parentAccessibilityViewId = - AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); + AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); final int parentVirtualDescendantId = - AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); + AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); if (parentVirtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) { final AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo(parentVirtualDescendantId); if (parent != null) { + if (!predecessorsPrefetched) { + outInfos.add(parent); + } final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { if (shouldStopPrefetching(outInfos)) { @@ -1443,7 +1515,7 @@ public final class AccessibilityInteractionController { } } } else { - prefetchSiblingsOfRealNode(providerHost, outInfos); + prefetchSiblingsOfRealNode(providerHost, outInfos, predecessorsPrefetched); } } @@ -1626,4 +1698,159 @@ public final class AccessibilityInteractionController { mSatisfiedRequestInteractionId = satisfiedRequestInteractionId; } } + + private class PrefetchDeque<E extends DequeNode> + extends ArrayDeque<E> { + int mStrategy; + List<AccessibilityNodeInfo> mPrefetchOutput; + + PrefetchDeque(int strategy, List<AccessibilityNodeInfo> output) { + mStrategy = strategy; + mPrefetchOutput = output; + } + + /** Performs depth-first or breadth-first traversal. + * + * For depth-first search, we iterate through the children in backwards order and push them + * to the stack before taking from the head. For breadth-first search, we iterate through + * the children in order and push them to the stack before taking from the tail. + * + * Depth-first search: 0 has children 0, 1, 2, 4. 1 has children 5 and 6. + * Head Tail + * 1 2 3 4 -> pop: 1 -> 5 6 2 3 4 + * + * Breadth-first search + * Head Tail + * 4 3 2 1 -> remove last: 1 -> 6 5 3 2 + * + **/ + void performTraversalAndPrefetch() { + try { + while (!isEmpty()) { + E child = getNext(); + AccessibilityNodeInfo childInfo = child.getA11yNodeInfo(); + if (childInfo != null) { + mPrefetchOutput.add(childInfo); + } + if (mPrefetcher.shouldStopPrefetching(mPrefetchOutput)) { + return; + } + // Add children to deque. + child.addChildren(childInfo, this); + } + } finally { + clear(); + } + } + + E getNext() { + if (isStack()) { + return pop(); + } + return removeLast(); + } + + boolean isStack() { + return (mStrategy & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST) != 0; + } + } + + interface DequeNode { + AccessibilityNodeInfo getA11yNodeInfo(); + void addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque); + } + + private class ViewNode implements DequeNode { + View mView; + private final ArrayList<View> mTempViewList = new ArrayList<>(); + + ViewNode(View view) { + mView = view; + } + + @Override + public AccessibilityNodeInfo getA11yNodeInfo() { + if (mView == null) { + return null; + } + return mView.createAccessibilityNodeInfo(); + } + + @Override + public void addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque) { + if (mView == null) { + return; + } + if (!(mView instanceof ViewGroup)) { + return; + } + ArrayList<View> children = mTempViewList; + children.clear(); + try { + mView.addChildrenForAccessibility(children); + final int childCount = children.size(); + + if (deque.isStack()) { + for (int i = childCount - 1; i >= 0; i--) { + addChild(deque, children.get(i)); + } + } else { + for (int i = 0; i < childCount; i++) { + addChild(deque, children.get(i)); + } + } + } finally { + children.clear(); + } + } + + private void addChild(ArrayDeque deque, View child) { + if (isShown(child)) { + AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider(); + if (provider == null) { + deque.push(new ViewNode(child)); + } else { + deque.push(new VirtualNode(AccessibilityNodeProvider.HOST_VIEW_ID, + provider)); + } + } + } + } + + private class VirtualNode implements DequeNode { + long mInfoId; + AccessibilityNodeProvider mProvider; + + VirtualNode(long id, AccessibilityNodeProvider provider) { + mInfoId = id; + mProvider = provider; + } + @Override + public AccessibilityNodeInfo getA11yNodeInfo() { + if (mProvider == null) { + return null; + } + return mProvider.createAccessibilityNodeInfo( + AccessibilityNodeInfo.getVirtualDescendantId(mInfoId)); + } + + @Override + public void addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque) { + if (virtualRoot == null) { + return; + } + final int childCount = virtualRoot.getChildCount(); + if (deque.isStack()) { + for (int i = childCount - 1; i >= 0; i--) { + final long childNodeId = virtualRoot.getChildId(i); + deque.push(new VirtualNode(childNodeId, mProvider)); + } + } else { + for (int i = 0; i < childCount; i++) { + final long childNodeId = virtualRoot.getChildId(i); + deque.push(new VirtualNode(childNodeId, mProvider)); + } + } + } + } } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 246a8c9d17d3..7d8e99840fb1 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -42,6 +42,7 @@ import android.hardware.display.BrightnessInfo; import android.hardware.display.DeviceProductInfo; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; +import android.hardware.graphics.common.DisplayDecorationSupport; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -1856,6 +1857,19 @@ public final class Display { } /** + * Returns whether/how the specified display supports DISPLAY_DECORATION. + * + * Composition.DISPLAY_DECORATION is a special layer type which is used to + * render the screen decorations (i.e. antialiased rounded corners and + * cutouts) while taking advantage of specific hardware. + * + * @hide + */ + public DisplayDecorationSupport getDisplayDecorationSupport() { + return mGlobal.getDisplayDecorationSupport(mDisplayId); + } + + /** * A mode supported by a given display. * * @see Display#getSupportedModes() diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index c87c13d861d5..c1413beb0143 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -24,6 +24,9 @@ import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.VisibleForTesting; import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; /** * Initiates handwriting mode once it detects stylus movement in handwritable areas. @@ -58,6 +61,7 @@ public class HandwritingInitiator { private final long mTapTimeoutInMillis; private State mState = new State(); + private final HandwritingAreaTracker mHandwritingAreasTracker = new HandwritingAreaTracker(); /** * Helper method to reset the internal state of this class. @@ -83,8 +87,8 @@ public class HandwritingInitiator { private final InputMethodManager mImm; @VisibleForTesting - public HandwritingInitiator(ViewConfiguration viewConfiguration, - InputMethodManager inputMethodManager) { + public HandwritingInitiator(@NonNull ViewConfiguration viewConfiguration, + @NonNull InputMethodManager inputMethodManager) { mTouchSlop = viewConfiguration.getScaledTouchSlop(); mTapTimeoutInMillis = ViewConfiguration.getTapTimeout(); mImm = inputMethodManager; @@ -98,7 +102,7 @@ public class HandwritingInitiator { * @param motionEvent the stylus MotionEvent. */ @VisibleForTesting - public void onTouchEvent(MotionEvent motionEvent) { + public void onTouchEvent(@NonNull MotionEvent motionEvent) { final int maskedAction = motionEvent.getActionMasked(); switch (maskedAction) { case MotionEvent.ACTION_DOWN: @@ -151,11 +155,20 @@ public class HandwritingInitiator { final float y = motionEvent.getY(pointerIndex); if (largerThanTouchSlop(x, y, mState.mStylusDownX, mState.mStylusDownY)) { mState.mExceedTouchSlop = true; - tryStartHandwriting(); + View candidateView = + findBestCandidateView(mState.mStylusDownX, mState.mStylusDownY); + if (candidateView != null) { + if (candidateView == getConnectedView()) { + startHandwriting(candidateView); + } else { + candidateView.requestFocus(); + } + } } } } + @Nullable private View getConnectedView() { if (mConnectedView == null) return null; return mConnectedView.get(); @@ -178,13 +191,16 @@ public class HandwritingInitiator { clearConnectedView(); return; } + final View connectedView = getConnectedView(); if (connectedView == view) { ++mConnectionCount; } else { mConnectedView = new WeakReference<>(view); mConnectionCount = 1; - tryStartHandwriting(); + if (mState.mShouldInitHandwriting) { + tryStartHandwriting(); + } } } @@ -233,17 +249,10 @@ public class HandwritingInitiator { return; } - final ViewParent viewParent = connectedView.getParent(); - // Do a final check before startHandwriting. - if (viewParent != null && connectedView.isAttachedToWindow()) { - final Rect editorBounds = - new Rect(0, 0, connectedView.getWidth(), connectedView.getHeight()); - if (viewParent.getChildVisibleRect(connectedView, editorBounds, null)) { - final int roundedInitX = Math.round(mState.mStylusDownX); - final int roundedInitY = Math.round(mState.mStylusDownY); - if (editorBounds.contains(roundedInitX, roundedInitY)) { - startHandwriting(connectedView); - } + Rect handwritingArea = getViewHandwritingArea(connectedView); + if (handwritingArea != null) { + if (contains(handwritingArea, mState.mStylusDownX, mState.mStylusDownY)) { + startHandwriting(connectedView); } } reset(); @@ -251,10 +260,79 @@ public class HandwritingInitiator { /** For test only. */ @VisibleForTesting - public void startHandwriting(View view) { + public void startHandwriting(@NonNull View view) { mImm.startStylusHandwriting(view); } + /** + * Notify that the handwriting area for the given view might be updated. + * @param view the view whose handwriting area might be updated. + */ + public void updateHandwritingAreasForView(@NonNull View view) { + mHandwritingAreasTracker.updateHandwritingAreaForView(view); + } + + /** + * Given the location of the stylus event, return the best candidate view to initialize + * handwriting mode. + * + * @param x the x coordinates of the stylus event, in the coordinates of the window. + * @param y the y coordinates of the stylus event, in the coordinates of the window. + */ + @Nullable + private View findBestCandidateView(float x, float y) { + // If the connectedView is not null and do not set any handwriting area, it will check + // whether the connectedView's boundary contains the initial stylus position. If true, + // directly return the connectedView. + final View connectedView = getConnectedView(); + if (connectedView != null && connectedView.isAutoHandwritingEnabled()) { + final Rect handwritingArea = getViewHandwritingArea(connectedView); + if (handwritingArea != null && contains(handwritingArea, x, y)) { + return connectedView; + } + } + + // Check the registered handwriting areas. + final List<HandwritableViewInfo> handwritableViewInfos = + mHandwritingAreasTracker.computeViewInfos(); + for (HandwritableViewInfo viewInfo : handwritableViewInfos) { + final View view = viewInfo.getView(); + if (!view.isAutoHandwritingEnabled()) continue; + final Rect rect = viewInfo.getHandwritingArea(); + if (rect != null && contains(rect, x, y)) { + return viewInfo.getView(); + } + } + return null; + } + + /** + * Return the handwriting area of the given view, represented in the window's coordinate. + * If the view didn't set any handwriting area, it will return the view's boundary. + * It will return null if the view or its handwriting area is not visible. + */ + @Nullable + private static Rect getViewHandwritingArea(@NonNull View view) { + final ViewParent viewParent = view.getParent(); + if (viewParent != null && view.isAttachedToWindow() && view.isAggregatedVisible()) { + Rect handwritingArea = view.getHandwritingArea(); + if (handwritingArea == null) { + handwritingArea = new Rect(0, 0, view.getWidth(), view.getHeight()); + } + if (viewParent.getChildVisibleRect(view, handwritingArea, null)) { + return handwritingArea; + } + } + return null; + } + + /** + * Return true if the (x, y) is inside by the given {@link Rect}. + */ + private boolean contains(@NonNull Rect rect, float x, float y) { + return x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom; + } + private boolean largerThanTouchSlop(float x1, float y1, float x2, float y2) { float dx = x1 - x2; float dy = y1 - y2; @@ -291,4 +369,134 @@ public class HandwritingInitiator { private float mStylusDownX = Float.NaN; private float mStylusDownY = Float.NaN; } + + /** The helper method to check if the given view is still active for handwriting. */ + private static boolean isViewActive(@Nullable View view) { + return view != null && view.isAttachedToWindow() && view.isAggregatedVisible() + && view.isAutoHandwritingEnabled(); + } + + /** + * A class used to track the handwriting areas set by the Views. + * + * @hide + */ + @VisibleForTesting + public static class HandwritingAreaTracker { + private final List<HandwritableViewInfo> mHandwritableViewInfos; + + public HandwritingAreaTracker() { + mHandwritableViewInfos = new ArrayList<>(); + } + + /** + * Notify this tracker that the handwriting area of the given view has been updated. + * This method does three things: + * a) iterate over the all the tracked ViewInfos and remove those already invalid ones. + * b) mark the given view's ViewInfo to be dirty. So that next time when + * {@link #computeViewInfos} is called, this view's handwriting area will be recomputed. + * c) If no the given view is not in the tracked ViewInfo list, a new ViewInfo object will + * be created and added to the list. + * + * @param view the view whose handwriting area is updated. + */ + public void updateHandwritingAreaForView(@NonNull View view) { + Iterator<HandwritableViewInfo> iterator = mHandwritableViewInfos.iterator(); + boolean found = false; + while (iterator.hasNext()) { + final HandwritableViewInfo handwritableViewInfo = iterator.next(); + final View curView = handwritableViewInfo.getView(); + if (!isViewActive(curView)) { + iterator.remove(); + } + if (curView == view) { + found = true; + handwritableViewInfo.mIsDirty = true; + } + } + if (!found && isViewActive(view)) { + // The given view is not tracked. Create a new HandwritableViewInfo for it and add + // to the list. + mHandwritableViewInfos.add(new HandwritableViewInfo(view)); + } + } + + /** + * Update the handwriting areas and return a list of ViewInfos containing the view + * reference and its handwriting area. + */ + @NonNull + public List<HandwritableViewInfo> computeViewInfos() { + mHandwritableViewInfos.removeIf(viewInfo -> !viewInfo.update()); + return mHandwritableViewInfos; + } + } + + /** + * A class that reference to a View and its handwriting area(in the ViewRoot's coordinate.) + * + * @hide + */ + @VisibleForTesting + public static class HandwritableViewInfo { + final WeakReference<View> mViewRef; + Rect mHandwritingArea = null; + @VisibleForTesting + public boolean mIsDirty = true; + + @VisibleForTesting + public HandwritableViewInfo(@NonNull View view) { + mViewRef = new WeakReference<>(view); + } + + /** Return the tracked view. */ + @Nullable + public View getView() { + return mViewRef.get(); + } + + /** + * Return the tracked handwriting area, represented in the ViewRoot's coordinates. + * Notice, the caller should not modify the returned Rect. + */ + @Nullable + public Rect getHandwritingArea() { + return mHandwritingArea; + } + + /** + * Update the handwriting area in this ViewInfo. + * + * @return true if this ViewInfo is still valid. Or false if this ViewInfo has become + * invalid due to either view is no longer visible, or the handwriting area set by the + * view is removed. {@link HandwritingAreaTracker} no longer need to keep track of this + * HandwritableViewInfo this method returns false. + */ + public boolean update() { + final View view = getView(); + if (!isViewActive(view)) { + return false; + } + + if (!mIsDirty) { + return true; + } + final Rect localRect = view.getHandwritingArea(); + if (localRect == null) { + return false; + } + + ViewParent parent = view.getParent(); + if (parent != null) { + final Rect newRect = new Rect(localRect); + if (parent.getChildVisibleRect(view, newRect, null /* offset */)) { + mHandwritingArea = newRect; + } else { + mHandwritingArea = null; + } + } + mIsDirty = false; + return true; + } + } } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 32054b1cdc13..1ed35f7b6dcf 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -298,7 +298,7 @@ interface IWindowSession { */ void grantInputChannel(int displayId, in SurfaceControl surface, in IWindow window, in IBinder hostInputToken, int flags, int privateFlags, int type, - in IBinder focusGrantToken, out InputChannel outInputChannel); + in IBinder focusGrantToken, String inputHandleName, out InputChannel outInputChannel); /** * Update the flags on an input channel associated with a particular surface. diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS index 43df294466db..e313388affb6 100644 --- a/core/java/android/view/OWNERS +++ b/core/java/android/view/OWNERS @@ -11,6 +11,9 @@ jjaggi@google.com roosa@google.com jreck@google.com +# Autofill +per-file ViewStructure.java = file:/core/java/android/service/autofill/OWNERS + # Display per-file Display*.java = file:/services/core/java/com/android/server/display/OWNERS per-file Display*.aidl = file:/services/core/java/com/android/server/display/OWNERS diff --git a/core/java/android/view/OnBackInvokedCallback.java b/core/java/android/view/OnBackInvokedCallback.java index b5cd89cfa4e1..37f858abf818 100644 --- a/core/java/android/view/OnBackInvokedCallback.java +++ b/core/java/android/view/OnBackInvokedCallback.java @@ -18,6 +18,7 @@ package android.view; import android.app.Activity; import android.app.Dialog; +import android.window.BackEvent; /** * Interface for applications to register back invocation callbacks. This allows the client @@ -46,14 +47,12 @@ public interface OnBackInvokedCallback { /** * Called on back gesture progress. * - * @param touchX Absolute X location of the touch point. - * @param touchY Absolute Y location of the touch point. - * @param progress Value between 0 and 1 on how far along the back gesture is. + * @param backEvent An {@link android.window.BackEvent} object describing the progress event. * + * @see android.window.BackEvent * @hide */ - // TODO(b/210539672): combine back progress params into BackEvent. - default void onBackProgressed(int touchX, int touchY, float progress) { }; + default void onBackProgressed(BackEvent backEvent) { }; /** * Called when a back gesture or back button press has been cancelled. diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index d3061e21efeb..ce5496865885 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -49,6 +49,7 @@ import android.hardware.HardwareBuffer; import android.hardware.display.DeviceProductInfo; import android.hardware.display.DisplayedContentSample; import android.hardware.display.DisplayedContentSamplingAttributes; +import android.hardware.graphics.common.DisplayDecorationSupport; import android.os.Build; import android.os.IBinder; import android.os.Parcel; @@ -235,7 +236,8 @@ public final class SurfaceControl implements Parcelable { float shadowRadius); private static native void nativeSetGlobalShadowSettings(@Size(4) float[] ambientColor, @Size(4) float[] spotColor, float lightPosY, float lightPosZ, float lightRadius); - private static native boolean nativeGetDisplayDecorationSupport(IBinder displayToken); + private static native DisplayDecorationSupport nativeGetDisplayDecorationSupport( + IBinder displayToken); private static native void nativeSetFrameRate(long transactionObj, long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy); @@ -2694,16 +2696,18 @@ public final class SurfaceControl implements Parcelable { } /** - * Returns whether a display supports DISPLAY_DECORATION. + * Returns whether/how a display supports DISPLAY_DECORATION. * * @param displayToken * The token for the display. * - * @return Whether the display supports DISPLAY_DECORATION. + * @return A class describing how the display supports DISPLAY_DECORATION or null if it does + * not. * + * TODO (b/218524164): Move this out of SurfaceControl. * @hide */ - public static boolean getDisplayDecorationSupport(IBinder displayToken) { + public static DisplayDecorationSupport getDisplayDecorationSupport(IBinder displayToken) { return nativeGetDisplayDecorationSupport(displayToken); } @@ -3696,21 +3700,32 @@ public final class SurfaceControl implements Parcelable { /** * Sets the buffer transform that should be applied to the current buffer. * + * This can be used in combination with + * {@link AttachedSurfaceControl#addOnBufferTransformHintChangedListener(AttachedSurfaceControl.OnBufferTransformHintChangedListener)} + * to pre-rotate the buffer for the current display orientation. This can + * improve the performance of displaying the associated buffer. + * * @param sc The SurfaceControl to update * @param transform The transform to apply to the buffer. * @return this */ public @NonNull Transaction setBufferTransform(@NonNull SurfaceControl sc, - /* TODO: Mark the intdef */ int transform) { + @SurfaceControl.BufferTransform int transform) { checkPreconditions(sc); nativeSetBufferTransform(mNativeObject, sc.mNativeObject, transform); return this; } /** - * Updates the region for the content on this surface updated in this transaction. + * Updates the region for the content on this surface updated in this transaction. The + * damage region is the area of the buffer that has changed since the previously + * sent buffer. This can be used to reduce the amount of recomposition that needs + * to happen when only a small region of the buffer is being updated, such as for + * a small blinking cursor or a loading indicator. * - * If unspecified, the complete surface is assumed to be damaged. + * @param sc The SurfaceControl on which to set the damage region + * @param region The region to set. If null, the entire buffer is assumed dirty. This is + * equivalent to not setting a damage region at all. */ public @NonNull Transaction setDamageRegion(@NonNull SurfaceControl sc, @Nullable Region region) { diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index 7e0d887a8f79..2edfda5d065c 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -27,8 +27,10 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; -import android.view.accessibility.IAccessibilityEmbeddedConnection; +import android.util.Log; import android.view.InsetsState; +import android.view.WindowManagerGlobal; +import android.view.accessibility.IAccessibilityEmbeddedConnection; import java.util.Objects; @@ -43,11 +45,13 @@ import java.util.Objects; * {@link SurfaceView#setChildSurfacePackage}. */ public class SurfaceControlViewHost { + private final static String TAG = "SurfaceControlViewHost"; private final ViewRootImpl mViewRoot; private WindowlessWindowManager mWm; private SurfaceControl mSurfaceControl; private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; + private boolean mReleased = false; private final class ISurfaceControlViewHostImpl extends ISurfaceControlViewHost.Stub { @Override @@ -268,6 +272,8 @@ public class SurfaceControlViewHost { @NonNull WindowlessWindowManager wwm, boolean useSfChoreographer) { mWm = wwm; mViewRoot = new ViewRootImpl(c, d, mWm, useSfChoreographer); + WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot); + mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection(); } @@ -292,7 +298,10 @@ public class SurfaceControlViewHost { .build(); mWm = new WindowlessWindowManager(context.getResources().getConfiguration(), mSurfaceControl, hostToken); + mViewRoot = new ViewRootImpl(context, display, mWm); + WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot); + mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection(); } @@ -301,12 +310,15 @@ public class SurfaceControlViewHost { */ @Override protected void finalize() throws Throwable { - // We aren't on the UI thread here so we need to pass false to - // doDie + if (mReleased) { + return; + } + Log.e(TAG, "SurfaceControlViewHost finalized without being released: " + this); + // We aren't on the UI thread here so we need to pass false to doDie mViewRoot.die(false /* immediate */); + WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot); } - /** * Return a SurfacePackage for the root SurfaceControl of the embedded hierarchy. * Rather than be directly reparented using {@link SurfaceControl.Transaction} this @@ -413,5 +425,14 @@ public class SurfaceControlViewHost { public void release() { // ViewRoot will release mSurfaceControl for us. mViewRoot.die(true /* immediate */); + WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot); + mReleased = true; + } + + /** + * @hide + */ + public IBinder getFocusGrantToken() { + return mWm.getFocusGrantToken(); } } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index a2fcf80af656..1a458ce5c8ba 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -23,7 +23,6 @@ import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAY import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; -import android.content.ContentResolver; import android.content.Context; import android.content.res.CompatibilityInfo.Translator; import android.graphics.BLASTBufferQueue; @@ -42,7 +41,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.SystemClock; -import android.provider.Settings; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceControl.Transaction; @@ -135,7 +133,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private boolean mDisableBackgroundLayer = false; /** - * We use this lock to protect access to mSurfaceControl and + * We use this lock to protect access to mSurfaceControl and * SurfaceViewPositionUpdateListener#mPositionChangedTransaction. Both are accessed on the UI * thread and the render thread. */ @@ -376,11 +374,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } private void updateSurfaceAlpha() { - if (!mUseAlpha) { - if (DEBUG) { - Log.d(TAG, System.identityHashCode(this) - + " updateSurfaceAlpha: setUseAlpha() is not called, ignored."); - } + if (!mUseAlpha || !mHaveFrame || mSurfaceControl == null) { return; } final float viewAlpha = getAlpha(); @@ -389,88 +383,16 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall + " updateSurfaceAlpha:" + " translucent color is not supported for a surface placed z-below."); } - if (!mHaveFrame) { - if (DEBUG) { - Log.d(TAG, System.identityHashCode(this) - + " updateSurfaceAlpha: has no surface."); - } - return; - } final ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot == null) { - if (DEBUG) { - Log.d(TAG, System.identityHashCode(this) - + " updateSurfaceAlpha: ViewRootImpl not available."); - } - return; - } - if (mSurfaceControl == null) { - if (DEBUG) { - Log.d(TAG, System.identityHashCode(this) - + "updateSurfaceAlpha:" - + " surface is not yet created, or already released."); - } - return; - } - final Surface parent = viewRoot.mSurface; - if (parent == null || !parent.isValid()) { - if (DEBUG) { - Log.d(TAG, System.identityHashCode(this) - + " updateSurfaceAlpha: ViewRootImpl has no valid surface"); - } return; } final float alpha = getFixedAlpha(); if (alpha != mSurfaceAlpha) { - if (isHardwareAccelerated()) { - /* - * Schedule a callback that reflects an alpha value onto the underlying surfaces. - * This gets called on a RenderThread worker thread, so members accessed here must - * be protected by a lock. - */ - viewRoot.registerRtFrameCallback(frame -> { - try { - synchronized (mSurfaceControlLock) { - if (!parent.isValid()) { - if (DEBUG) { - Log.d(TAG, System.identityHashCode(this) - + " updateSurfaceAlpha RT:" - + " ViewRootImpl has no valid surface"); - } - return; - } - if (mSurfaceControl == null) { - if (DEBUG) { - Log.d(TAG, System.identityHashCode(this) - + "updateSurfaceAlpha RT:" - + " mSurfaceControl has already released"); - } - return; - } - if (DEBUG) { - Log.d(TAG, System.identityHashCode(this) - + " updateSurfaceAlpha RT: set alpha=" + alpha); - } - - mFrameCallbackTransaction.setAlpha(mSurfaceControl, alpha); - applyOrMergeTransaction(mFrameCallbackTransaction, frame); - } - // It's possible that mSurfaceControl is released in the UI thread before - // the transaction completes. If that happens, an exception is thrown, which - // must be caught immediately. - } catch (Exception e) { - Log.e(TAG, System.identityHashCode(this) - + "updateSurfaceAlpha RT: Exception during surface transaction", e); - } - }); - damageInParent(); - } else { - if (DEBUG) { - Log.d(TAG, System.identityHashCode(this) - + " updateSurfaceAlpha: set alpha=" + alpha); - } - mTmpTransaction.setAlpha(mSurfaceControl, alpha).apply(); - } + final Transaction transaction = new Transaction(); + transaction.setAlpha(mSurfaceControl, alpha); + viewRoot.applyTransactionOnDraw(transaction); + damageInParent(); mSurfaceAlpha = alpha; } } @@ -527,12 +449,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mRequestedVisible = false; updateSurface(); - - // We don't release this as part of releaseSurfaces as - // that is also called on transient visibility changes. We can't - // recreate this Surface, so only release it when we are fully - // detached. - tryReleaseSurfaces(true /* releaseSurfacePackage*/); + releaseSurfaces(true /* releaseSurfacePackage*/); mHaveFrame = false; super.onDetachedFromWindow(); @@ -625,7 +542,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall public void setClipBounds(Rect clipBounds) { super.setClipBounds(clipBounds); - if (!mClipSurfaceToBounds) { + if (!mClipSurfaceToBounds || mSurfaceControl == null) { return; } @@ -635,18 +552,15 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall invalidate(); } - if (mSurfaceControl != null) { - if (mClipBounds != null) { - mTmpRect.set(mClipBounds); - } else { - mTmpRect.set(0, 0, mSurfaceWidth, mSurfaceHeight); - } - SyncRtSurfaceTransactionApplier applier = new SyncRtSurfaceTransactionApplier(this); - applier.scheduleApply( - new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(mSurfaceControl) - .withWindowCrop(mTmpRect) - .build()); + if (mClipBounds != null) { + mTmpRect.set(mClipBounds); + } else { + mTmpRect.set(0, 0, mSurfaceWidth, mSurfaceHeight); } + final Transaction transaction = new Transaction(); + transaction.setWindowCrop(mSurfaceControl, mTmpRect); + applyTransactionOnVriDraw(transaction); + invalidate(); } private void clearSurfaceViewPort(Canvas canvas) { @@ -782,37 +696,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall if (viewRoot == null) { return true; } - final Surface parent = viewRoot.mSurface; - if (parent == null || !parent.isValid()) { - return true; - } - - /* - * Schedule a callback that reflects an alpha value onto the underlying surfaces. - * This gets called on a RenderThread worker thread, so members accessed here must - * be protected by a lock. - */ - viewRoot.registerRtFrameCallback(frame -> { - try { - synchronized (mSurfaceControlLock) { - if (!parent.isValid() || mSurfaceControl == null) { - return; - } - - updateRelativeZ(mFrameCallbackTransaction); - applyOrMergeTransaction(mFrameCallbackTransaction, frame); - } - // It's possible that mSurfaceControl is released in the UI thread before - // the transaction completes. If that happens, an exception is thrown, which - // must be caught immediately. - } catch (Exception e) { - Log.e(TAG, System.identityHashCode(this) - + "setZOrderOnTop RT: Exception during surface transaction", e); - } - }); - + final Transaction transaction = new SurfaceControl.Transaction(); + updateRelativeZ(transaction); + viewRoot.applyTransactionOnDraw(transaction); invalidate(); - return true; } @@ -863,7 +750,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall return t; } - private void tryReleaseSurfaces(boolean releaseSurfacePackage) { + private void releaseSurfaces(boolean releaseSurfacePackage) { mSurfaceAlpha = 1f; synchronized (mSurfaceControlLock) { @@ -892,12 +779,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mSurfacePackage = null; } - ViewRootImpl viewRoot = getViewRootImpl(); - if (viewRoot != null) { - viewRoot.applyTransactionOnDraw(transaction); - } else { - transaction.apply(); - } + applyTransactionOnVriDraw(transaction); } } @@ -1041,7 +923,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall if (viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) { notifySurfaceDestroyed(); - tryReleaseSurfaces(false /* releaseSurfacePackage*/); + releaseSurfaces(false /* releaseSurfacePackage*/); return; } @@ -1182,7 +1064,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } finally { mIsCreating = false; if (mSurfaceControl != null && !mSurfaceCreated) { - tryReleaseSurfaces(false /* releaseSurfacePackage*/); + releaseSurfaces(false /* releaseSurfacePackage*/); } } } catch (Exception ex) { @@ -1317,17 +1199,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } /** - * A place to over-ride for applying child-surface transactions. - * These can be synchronized with the viewroot surface using deferTransaction. - * - * Called from RenderWorker while UI thread is paused. - * @hide - */ - protected void applyChildSurfaceTransaction_renderWorker(SurfaceControl.Transaction t, - Surface viewRootSurface, long nextViewRootFrameNumber) { - } - - /** * Sets the surface position and scale. Can be called on * the UI thread as well as on the renderer thread. * @@ -1435,11 +1306,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall if (mViewVisibility) { mPositionChangedTransaction.show(mSurfaceControl); } - final ViewRootImpl viewRoot = getViewRootImpl(); - if (viewRoot != null) { - applyChildSurfaceTransaction_renderWorker(mPositionChangedTransaction, - viewRoot.mSurface, frameNumber); - } applyOrMergeTransaction(mPositionChangedTransaction, frameNumber); mPendingTransaction = false; } catch (Exception ex) { @@ -1837,12 +1703,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mSurfacePackage.release(); } reparentSurfacePackage(transaction, p); - final ViewRootImpl viewRoot = getViewRootImpl(); - if (viewRoot != null) { - viewRoot.applyTransactionOnDraw(transaction); - } else { - transaction.apply(); - } + applyTransactionOnVriDraw(transaction); } mSurfacePackage = p; invalidate(); @@ -1948,4 +1809,13 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } } + private void applyTransactionOnVriDraw(Transaction t) { + final ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot != null) { + // If we are using BLAST, merge the transaction with the viewroot buffer transaction. + viewRoot.applyTransactionOnDraw(t); + } else { + t.apply(); + } + } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f74b5995c129..179f6ee83227 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4745,9 +4745,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private List<Rect> mSystemGestureExclusionRects = null; private List<Rect> mKeepClearRects = null; private boolean mPreferKeepClear = false; + private Rect mHandwritingArea = null; /** - * Used to track {@link #mSystemGestureExclusionRects} and {@link #mKeepClearRects} + * Used to track {@link #mSystemGestureExclusionRects}, {@link #mKeepClearRects} and + * {@link #mHandwritingArea}. */ public RenderNode.PositionUpdateListener mPositionUpdateListener; private Runnable mPositionChangedUpdate; @@ -11710,7 +11712,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private void updatePositionUpdateListener() { final ListenerInfo info = getListenerInfo(); if (getSystemGestureExclusionRects().isEmpty() - && collectPreferKeepClearRects().isEmpty()) { + && collectPreferKeepClearRects().isEmpty() + && (info.mHandwritingArea == null || !isAutoHandwritingEnabled())) { if (info.mPositionUpdateListener != null) { mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener); info.mPositionChangedUpdate = null; @@ -11720,6 +11723,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, info.mPositionChangedUpdate = () -> { updateSystemGestureExclusionRects(); updateKeepClearRects(); + updateHandwritingArea(); }; info.mPositionUpdateListener = new RenderNode.PositionUpdateListener() { @Override @@ -11876,6 +11880,51 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Set a list of handwriting areas in this view. If there is any stylus {@link MotionEvent} + * occurs within those areas, it will trigger stylus handwriting mode. This can be disabled by + * disabling the auto handwriting initiation by calling + * {@link #setAutoHandwritingEnabled(boolean)} with false. + * + * @attr rects a list of handwriting area in the view's local coordiniates. + * + * @see android.view.inputmethod.InputMethodManager#startStylusHandwriting(View) + * @see #setAutoHandwritingEnabled(boolean) + * + * @hide + */ + public void setHandwritingArea(@Nullable Rect rect) { + final ListenerInfo info = getListenerInfo(); + info.mHandwritingArea = rect; + updatePositionUpdateListener(); + postUpdate(this::updateHandwritingArea); + } + + /** + * Return the handwriting areas set on this view, in its local coordinates. + * Notice: the caller of this method should not modify the Rect returned. + * @see #setHandwritingArea(Rect) + * + * @hide + */ + @Nullable + public Rect getHandwritingArea() { + final ListenerInfo info = mListenerInfo; + if (info != null) { + return info.mHandwritingArea; + } + return null; + } + + void updateHandwritingArea() { + // If autoHandwritingArea is not enabled, do nothing. + if (!isAutoHandwritingEnabled()) return; + final AttachInfo ai = mAttachInfo; + if (ai != null) { + ai.mViewRootImpl.getHandwritingInitiator().updateHandwritingAreasForView(this); + } + } + + /** * Compute the view's coordinate within the surface. * * <p>Computes the coordinates of this view in its surface. The argument @@ -31181,6 +31230,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { mPrivateFlags4 &= ~PFLAG4_AUTO_HANDWRITING_ENABLED; } + updatePositionUpdateListener(); + postUpdate(this::updateHandwritingArea); } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 39fc2c044307..8236fbbc3e81 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -583,6 +583,12 @@ public final class ViewRootImpl implements ViewParent, boolean mForceNextWindowRelayout; CountDownLatch mWindowDrawCountDown; + // Whether we have used applyTransactionOnDraw to schedule an RT + // frame callback consuming a passed in transaction. In this case + // we also need to schedule a commit callback so we can observe + // if the draw was skipped, and the BBQ pending transactions. + boolean mHasPendingTransactions; + boolean mIsDrawing; int mLastSystemUiVisibility; int mClientWindowLayoutFlags; @@ -1788,10 +1794,16 @@ public final class ViewRootImpl implements ViewParent, } void pokeDrawLockIfNeeded() { - final int displayState = mAttachInfo.mDisplayState; - if (mView != null && mAdded && mTraversalScheduled - && (displayState == Display.STATE_DOZE - || displayState == Display.STATE_DOZE_SUSPEND)) { + if (!Display.isDozeState(mAttachInfo.mDisplayState)) { + // Only need to acquire wake lock for DOZE state. + return; + } + if (mWindowAttributes.type != WindowManager.LayoutParams.TYPE_BASE_APPLICATION) { + // Non-activity windows should be responsible to hold wake lock by themself, because + // usually they are system windows. + return; + } + if (mAdded && mTraversalScheduled && mAttachInfo.mHasWindowFocus) { try { mWindowSession.pokeDrawLock(mWindow); } catch (RemoteException ex) { @@ -4184,6 +4196,9 @@ public final class ViewRootImpl implements ViewParent, mRtLastAttemptedDrawFrameNum); tmpTransaction.merge(pendingTransactions); } + if (!useBlastSync && !reportNextDraw) { + tmpTransaction.apply(); + } // Post at front of queue so the buffer can be processed immediately and allow RT // to continue processing new buffers. If RT tries to process buffers before the sync @@ -4214,7 +4229,7 @@ public final class ViewRootImpl implements ViewParent, final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates(); final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions(); - if (!useBlastSync && !needsCallbackForBlur && !reportNextDraw) { + if (!useBlastSync && !needsCallbackForBlur && !reportNextDraw && !mHasPendingTransactions) { return null; } @@ -4226,11 +4241,14 @@ public final class ViewRootImpl implements ViewParent, + " nextDrawUseBlastSync=" + useBlastSync + " reportNextDraw=" + reportNextDraw + " hasBlurUpdates=" + hasBlurUpdates - + " hasBlastSyncConsumer=" + (blastSyncConsumer != null)); + + " hasBlastSyncConsumer=" + (blastSyncConsumer != null) + + " mHasPendingTransactions=" + mHasPendingTransactions); } final BackgroundBlurDrawable.BlurRegion[] blurRegionsForFrame = needsCallbackForBlur ? mBlurRegionAggregator.getBlurRegionsCopyForRT() : null; + final boolean hasPendingTransactions = mHasPendingTransactions; + mHasPendingTransactions = false; // The callback will run on the render thread. return new FrameDrawingCallback() { @@ -4257,7 +4275,7 @@ public final class ViewRootImpl implements ViewParent, return null; } - if (!useBlastSync && !reportNextDraw) { + if (!useBlastSync && !reportNextDraw && !hasPendingTransactions) { return null; } @@ -10700,7 +10718,10 @@ public final class ViewRootImpl implements ViewParent, if (mRemoved || !isHardwareEnabled()) { t.apply(); } else { - registerRtFrameCallback(frame -> mergeWithNextTransaction(t, frame)); + mHasPendingTransactions = true; + registerRtFrameCallback(frame -> { + mergeWithNextTransaction(t, frame); + }); } return true; } @@ -10841,6 +10862,7 @@ public final class ViewRootImpl implements ViewParent, private void unregisterCompatOnBackInvokedCallback() { if (mCompatOnBackInvokedCallback != null) { mOnBackInvokedDispatcher.unregisterOnBackInvokedCallback(mCompatOnBackInvokedCallback); + mCompatOnBackInvokedCallback = null; } } @@ -10854,4 +10876,8 @@ public final class ViewRootImpl implements ViewParent, mLastGivenInsets.reset(); requestLayout(); } + + IWindowSession getWindowSession() { + return mWindowSession; + } } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index c92a3a086a8b..93cb0dd7a234 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -159,6 +159,8 @@ public final class WindowManagerGlobal { new ArrayList<WindowManager.LayoutParams>(); private final ArraySet<View> mDyingViews = new ArraySet<View>(); + private final ArrayList<ViewRootImpl> mWindowlessRoots = new ArrayList<ViewRootImpl>(); + private Runnable mSystemPropertyUpdater; private WindowManagerGlobal() { @@ -387,7 +389,25 @@ public final class WindowManagerGlobal { } } - root = new ViewRootImpl(view.getContext(), display); + IWindowSession windowlessSession = null; + // If there is a parent set, but we can't find it, it may be coming + // from a SurfaceControlViewHost hierarchy. + if (wparams.token != null && panelParentView == null) { + for (int i = 0; i < mWindowlessRoots.size(); i++) { + ViewRootImpl maybeParent = mWindowlessRoots.get(i); + if (maybeParent.getWindowToken() == wparams.token) { + windowlessSession = maybeParent.getWindowSession(); + break; + } + } + } + + if (windowlessSession == null) { + root = new ViewRootImpl(view.getContext(), display); + } else { + root = new ViewRootImpl(view.getContext(), display, + windowlessSession); + } view.setLayoutParams(wparams); @@ -720,6 +740,20 @@ public final class WindowManagerGlobal { throw e.rethrowFromSystemServer(); } } + + /** @hide */ + public void addWindowlessRoot(ViewRootImpl impl) { + synchronized (mLock) { + mWindowlessRoots.add(impl); + } + } + + /** @hide */ + public void removeWindowlessRoot(ViewRootImpl impl) { + synchronized (mLock) { + mWindowlessRoots.remove(impl); + } + } } final class WindowLeaked extends AndroidRuntimeException { diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 56f0915b785e..21221521d21f 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -160,14 +160,15 @@ public class WindowlessWindowManager implements IWindowSession { if (((attrs.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0)) { try { - if(mRealWm instanceof IWindowSession.Stub) { + if (mRealWm instanceof IWindowSession.Stub) { mRealWm.grantInputChannel(displayId, - new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"), - window, mHostInputToken, attrs.flags, attrs.privateFlags, attrs.type, - mFocusGrantToken, outInputChannel); + new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"), + window, mHostInputToken, attrs.flags, attrs.privateFlags, attrs.type, + mFocusGrantToken, attrs.getTitle().toString(), outInputChannel); } else { mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags, - attrs.privateFlags, attrs.type, mFocusGrantToken, outInputChannel); + attrs.privateFlags, attrs.type, mFocusGrantToken, + attrs.getTitle().toString(), outInputChannel); } } catch (RemoteException e) { Log.e(TAG, "Failed to grant input to surface: ", e); @@ -485,7 +486,7 @@ public class WindowlessWindowManager implements IWindowSession { @Override public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type, IBinder focusGrantToken, - InputChannel outInputChannel) { + String inputHandleName, InputChannel outInputChannel) { } @Override diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 82e823fa77e6..07b7a18b6795 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -19,6 +19,8 @@ package android.view.accessibility; import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CLIENT; import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK; import static android.os.Build.VERSION_CODES.S; +import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_MASK; +import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_MASK; import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.NonNull; @@ -301,10 +303,11 @@ public final class AccessibilityInteractionClient * @param connectionId The id of a connection for interacting with the system. * @return The root {@link AccessibilityNodeInfo} if found, null otherwise. */ - public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) { + public AccessibilityNodeInfo getRootInActiveWindow(int connectionId, + @AccessibilityNodeInfo.PrefetchingStrategy int strategy) { return findAccessibilityNodeInfoByAccessibilityId(connectionId, AccessibilityWindowInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, - false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null); + false, strategy, null); } /** @@ -529,11 +532,6 @@ public final class AccessibilityInteractionClient public @Nullable AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId( int connectionId, int accessibilityWindowId, long accessibilityNodeId, boolean bypassCache, int prefetchFlags, Bundle arguments) { - if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0 - && (prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) == 0) { - throw new IllegalArgumentException("FLAG_PREFETCH_SIBLINGS" - + " requires FLAG_PREFETCH_PREDECESSORS"); - } try { IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { @@ -560,7 +558,7 @@ public final class AccessibilityInteractionClient } if (!cache.isEnabled()) { // Skip prefetching if cache is disabled. - prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK; + prefetchFlags &= ~FLAG_PREFETCH_MASK; } if (DEBUG) { Log.i(LOG_TAG, "Node cache miss for " @@ -573,12 +571,18 @@ public final class AccessibilityInteractionClient } } else { // No need to prefech nodes in bypass cache case. - prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK; + prefetchFlags &= ~FLAG_PREFETCH_MASK; } // Skip prefetching if window is scrolling. - if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0 + if ((prefetchFlags & FLAG_PREFETCH_MASK) != 0 && isWindowScrolling(accessibilityWindowId)) { - prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK; + prefetchFlags &= ~FLAG_PREFETCH_MASK; + } + + final int descendantPrefetchFlags = prefetchFlags & FLAG_PREFETCH_DESCENDANTS_MASK; + if ((descendantPrefetchFlags & (descendantPrefetchFlags - 1)) != 0) { + throw new IllegalArgumentException("There can be no more than one descendant" + + " prefetching strategy"); } final int interactionId = mInteractionIdCounter.getAndIncrement(); if (shouldTraceClient()) { @@ -599,21 +603,41 @@ public final class AccessibilityInteractionClient Binder.restoreCallingIdentity(identityToken); } if (packageNames != null) { - AccessibilityNodeInfo info = - getFindAccessibilityNodeInfoResultAndClear(interactionId); - if (shouldTraceCallback()) { - logTraceCallback(connection, "findAccessibilityNodeInfoByAccessibilityId", - "InteractionId:" + interactionId + ";connectionId=" - + connectionId + ";Result: " + info); - } - if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0 - && info != null) { - setInteractionWaitingForPrefetchResult(interactionId, connectionId, - packageNames); + if ((prefetchFlags + & AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE) != 0) { + List<AccessibilityNodeInfo> infos = + getFindAccessibilityNodeInfosResultAndClear( + interactionId); + if (shouldTraceCallback()) { + logTraceCallback(connection, + "findAccessibilityNodeInfoByAccessibilityId", + "InteractionId:" + interactionId + ";connectionId=" + + connectionId + ";Result: " + infos); + } + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, + bypassCache, packageNames); + if (infos != null && !infos.isEmpty()) { + return infos.get(0); + } + } else { + AccessibilityNodeInfo info = + getFindAccessibilityNodeInfoResultAndClear(interactionId); + if (shouldTraceCallback()) { + logTraceCallback(connection, + "findAccessibilityNodeInfoByAccessibilityId", + "InteractionId:" + interactionId + ";connectionId=" + + connectionId + ";Result: " + info); + } + if ((prefetchFlags & FLAG_PREFETCH_MASK) != 0 + && info != null) { + setInteractionWaitingForPrefetchResult(interactionId, connectionId, + packageNames); + } + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, + bypassCache, packageNames); + return info; } - finalizeAndCacheAccessibilityNodeInfo(info, connectionId, - bypassCache, packageNames); - return info; + } } else { if (DEBUG) { diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index a31cacfdfd2b..aeef76c3d048 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -23,8 +23,10 @@ import static java.util.Collections.EMPTY_LIST; import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; @@ -62,6 +64,8 @@ import com.android.internal.R; import com.android.internal.util.CollectionUtils; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -128,23 +132,99 @@ public class AccessibilityNodeInfo implements Parcelable { public static final long LEASHED_NODE_ID = makeNodeId(LEASHED_ITEM_ID, AccessibilityNodeProvider.HOST_VIEW_ID); + /** + * Prefetching strategy that prefetches the ancestors of the requested node. + * <p> Ancestors will be prefetched before siblings and descendants. + * + * @see #getChild(int, int) + * @see #getParent(int) + * @see AccessibilityWindowInfo#getRoot(int) + * @see AccessibilityService#getRootInActiveWindow(int) + * @see AccessibilityEvent#getSource(int) + */ + public static final int FLAG_PREFETCH_ANCESTORS = 0x00000001; + + /** + * Prefetching strategy that prefetches the siblings of the requested node. + * <p> To avoid disconnected trees, this flag will also prefetch the parent. Siblings will be + * prefetched before descendants. + * + * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags. + */ + public static final int FLAG_PREFETCH_SIBLINGS = 0x00000002; + + /** + * Prefetching strategy that prefetches the descendants in a hybrid depth first and breadth + * first approach. + * <p> The children of the root node is prefetched before recursing on the children. This + * must not be combined with {@link #FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST} or + * {@link #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST} or this will trigger an + * IllegalArgumentException. + * + * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags. + */ + public static final int FLAG_PREFETCH_DESCENDANTS_HYBRID = 0x00000004; + + /** + * Prefetching strategy that prefetches the descendants of the requested node depth-first. + * <p> This must not be combined with {@link #FLAG_PREFETCH_DESCENDANTS_HYBRID} or + * {@link #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST} or this will trigger an + * IllegalArgumentException. + * + * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags. + */ + public static final int FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST = 0x00000008; + + /** + * Prefetching strategy that prefetches the descendants of the requested node breadth-first. + * <p> This must not be combined with {@link #FLAG_PREFETCH_DESCENDANTS_HYBRID} or + * {@link #FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST} or this will trigger an + * IllegalArgumentException. + * + * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags. + */ + public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 0x00000010; + + /** + * Prefetching flag that specifies prefetching should not be interrupted by a request to + * retrieve a node or perform an action on a node. + * + * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags. + */ + public static final int FLAG_PREFETCH_UNINTERRUPTIBLE = 0x00000020; + /** @hide */ - public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001; + public static final int FLAG_PREFETCH_MASK = 0x0000003f; /** @hide */ - public static final int FLAG_PREFETCH_SIBLINGS = 0x00000002; + public static final int FLAG_PREFETCH_DESCENDANTS_MASK = 0x0000001C; + + /** + * Maximum batch size of prefetched nodes for a request. + */ + @SuppressLint("MinMaxConstant") + public static final int MAX_NUMBER_OF_PREFETCHED_NODES = 50; /** @hide */ - public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000004; + @IntDef(flag = true, prefix = { "FLAG_PREFETCH" }, value = { + FLAG_PREFETCH_ANCESTORS, + FLAG_PREFETCH_SIBLINGS, + FLAG_PREFETCH_DESCENDANTS_HYBRID, + FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST, + FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST, + FLAG_PREFETCH_UNINTERRUPTIBLE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PrefetchingStrategy {} /** @hide */ - public static final int FLAG_PREFETCH_MASK = 0x00000007; + public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000080; /** @hide */ - public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008; + public static final int FLAG_REPORT_VIEW_IDS = 0x00000100; /** @hide */ - public static final int FLAG_REPORT_VIEW_IDS = 0x00000010; + public static final int FLAG_REPORT_MASK = 0x00000180; // Actions. @@ -1079,11 +1159,6 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Get the child at given index. - * <p> - * <strong>Note:</strong> It is a client responsibility to recycle the - * received info by calling {@link AccessibilityNodeInfo#recycle()} - * to avoid creating of multiple instances. - * </p> * * @param index The child index. * @return The child node. @@ -1092,6 +1167,23 @@ public class AccessibilityNodeInfo implements Parcelable { * */ public AccessibilityNodeInfo getChild(int index) { + return getChild(index, FLAG_PREFETCH_DESCENDANTS_HYBRID); + } + + + /** + * Get the child at given index. + * + * @param index The child index. + * @param prefetchingStrategy the prefetching strategy. + * @return The child node. + * + * @throws IllegalStateException If called outside of an AccessibilityService. + * + * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching. + */ + @Nullable + public AccessibilityNodeInfo getChild(int index, @PrefetchingStrategy int prefetchingStrategy) { enforceSealed(); if (mChildNodeIds == null) { return null; @@ -1103,11 +1195,11 @@ public class AccessibilityNodeInfo implements Parcelable { final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); if (mLeashedChild != null && childId == LEASHED_NODE_ID) { return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mLeashedChild, - ROOT_NODE_ID, false, FLAG_PREFETCH_DESCENDANTS, null); + ROOT_NODE_ID, false, prefetchingStrategy, null); } return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId, - childId, false, FLAG_PREFETCH_DESCENDANTS, null); + childId, false, prefetchingStrategy, null); } /** @@ -1816,23 +1908,56 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Gets the parent. - * <p> - * <strong>Note:</strong> It is a client responsibility to recycle the - * received info by calling {@link AccessibilityNodeInfo#recycle()} - * to avoid creating of multiple instances. - * </p> * * @return The parent. */ public AccessibilityNodeInfo getParent() { enforceSealed(); if (mLeashedParent != null && mLeashedParentNodeId != UNDEFINED_NODE_ID) { - return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId); + return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId, + FLAG_PREFETCH_ANCESTORS | FLAG_PREFETCH_SIBLINGS); } return getNodeForAccessibilityId(mConnectionId, mWindowId, mParentNodeId); } /** + * Gets the parent. + * + * <p> + * Use {@code prefetchingStrategy} to determine the types of + * nodes prefetched from the app if the requested node is not in the cache and must be retrieved + * by the app. The default strategy for {@link #getParent()} is a combination of ancestor and + * sibling strategies. The app will prefetch until all nodes fulfilling the strategies are + * fetched, another node request is sent, or the maximum prefetch batch size of + * {@link #MAX_NUMBER_OF_PREFETCHED_NODES} nodes is reached. To prevent interruption by another + * request and to force prefetching of the max batch size, use + * {@link AccessibilityNodeInfo#FLAG_PREFETCH_UNINTERRUPTIBLE}. + * </p> + * + * @param prefetchingStrategy the prefetching strategy. + * @return The parent. + * + * @throws IllegalStateException If called outside of an AccessibilityService. + * + * @see #FLAG_PREFETCH_ANCESTORS + * @see #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST + * @see #FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST + * @see #FLAG_PREFETCH_DESCENDANTS_HYBRID + * @see #FLAG_PREFETCH_SIBLINGS + * @see #FLAG_PREFETCH_UNINTERRUPTIBLE + */ + @Nullable + public AccessibilityNodeInfo getParent(@PrefetchingStrategy int prefetchingStrategy) { + enforceSealed(); + if (mLeashedParent != null && mLeashedParentNodeId != UNDEFINED_NODE_ID) { + return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId, + prefetchingStrategy); + } + return getNodeForAccessibilityId(mConnectionId, mWindowId, mParentNodeId, + prefetchingStrategy); + } + + /** * @return The parent node id. * * @hide @@ -4507,17 +4632,31 @@ public class AccessibilityNodeInfo implements Parcelable { private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId, int windowId, long accessibilityId) { + return getNodeForAccessibilityId(connectionId, windowId, accessibilityId, + FLAG_PREFETCH_ANCESTORS + | FLAG_PREFETCH_DESCENDANTS_HYBRID | FLAG_PREFETCH_SIBLINGS); + } + + private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId, + int windowId, long accessibilityId, @PrefetchingStrategy int prefetchingStrategy) { if (!canPerformRequestOverConnection(connectionId, windowId, accessibilityId)) { return null; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(connectionId, - windowId, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS - | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null); + windowId, accessibilityId, false, prefetchingStrategy, null); } private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId, IBinder leashToken, long accessibilityId) { + return getNodeForAccessibilityId(connectionId, leashToken, accessibilityId, + FLAG_PREFETCH_ANCESTORS + | FLAG_PREFETCH_DESCENDANTS_HYBRID | FLAG_PREFETCH_SIBLINGS); + } + + private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId, + IBinder leashToken, long accessibilityId, + @PrefetchingStrategy int prefetchingStrategy) { if (!((leashToken != null) && (getAccessibilityViewId(accessibilityId) != UNDEFINED_ITEM_ID) && (connectionId != UNDEFINED_CONNECTION_ID))) { @@ -4525,8 +4664,7 @@ public class AccessibilityNodeInfo implements Parcelable { } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(connectionId, - leashToken, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS - | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null); + leashToken, accessibilityId, false, prefetchingStrategy, null); } /** @hide */ diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index cf2ea15707cb..036316e15cb9 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -74,10 +74,9 @@ public class AccessibilityRecord { private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200; private static final int GET_SOURCE_PREFETCH_FLAGS = - AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS - | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS - | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS; - + AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS + | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS + | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID; @UnsupportedAppUsage boolean mSealed; @@ -185,16 +184,30 @@ public class AccessibilityRecord { * @return The info of the source. */ public @Nullable AccessibilityNodeInfo getSource() { + return getSource(GET_SOURCE_PREFETCH_FLAGS); + } + + /** + * Gets the {@link AccessibilityNodeInfo} of the event source. + * + * @param prefetchingStrategy the prefetching strategy. + * @return The info of the source. + * + * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching. + */ + @Nullable + public AccessibilityNodeInfo getSource( + @AccessibilityNodeInfo.PrefetchingStrategy int prefetchingStrategy) { enforceSealed(); if ((mConnectionId == UNDEFINED) || (mSourceWindowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) || (AccessibilityNodeInfo.getAccessibilityViewId(mSourceNodeId) - == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)) { + == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)) { return null; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mSourceWindowId, - mSourceNodeId, false, GET_SOURCE_PREFETCH_FLAGS, null); + mSourceNodeId, false, prefetchingStrategy, null); } /** diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index 540f5dc27f7e..f155badc218f 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -219,13 +219,27 @@ public final class AccessibilityWindowInfo implements Parcelable { * @return The root node. */ public AccessibilityNodeInfo getRoot() { + return getRoot(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID); + } + + /** + * Gets the root node in the window's hierarchy. + * + * @param prefetchingStrategy the prefetching strategy. + * @return The root node. + * + * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching. + */ + @Nullable + public AccessibilityNodeInfo getRoot( + @AccessibilityNodeInfo.PrefetchingStrategy int prefetchingStrategy) { if (mConnectionId == UNDEFINED_WINDOW_ID) { return null; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mId, AccessibilityNodeInfo.ROOT_NODE_ID, - true, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null); + true, prefetchingStrategy, null); } /** @@ -382,6 +396,14 @@ public final class AccessibilityWindowInfo implements Parcelable { * Gets if this window is active. An active window is the one * the user is currently touching or the window has input focus * and the user is not touching any window. + * <p> + * This is defined as the window that most recently fired one + * of the following events: + * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}, + * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}, + * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}. + * In other words, the last window shown that also has input focus. + * </p> * * @return Whether this is the active window. */ diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 60ccf67249b7..b7994db0cb28 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -3100,11 +3100,51 @@ public final class AutofillManager { } /** - * Checks the id of autofill whether supported the fill dialog. + * If autofill suggestions for a dialog-style UI are available for {@code view}, shows a dialog + * allowing the user to select a suggestion and returns {@code true}. + * <p> + * The dialog may not be available if the autofill service does not support it, or if the + * autofill request has not returned a response yet. + * <p> + * It is recommended to call this method the first time a user focuses on an autofill-able form, + * and to avoid showing the input method if the dialog is shown. If this method returns + * {@code false}, you should then instead show the input method (assuming that is how the + * view normally handles the focus event). If the user re-focuses on the view, you should not + * call this method again so as to not disrupt usage of the input method. + * + * @param view the view for which to show autofill suggestions. This is typically a view + * receiving a focus event. The autofill suggestions shown will include content for + * related views as well. + * @return {@code true} if the autofill dialog is being shown + */ + // TODO(b/210926084): Consider whether to include the one-time show logic within this method. + public boolean showAutofillDialog(@NonNull View view) { + Objects.requireNonNull(view); + if (shouldShowAutofillDialog(view.getAutofillId())) { + // If the id matches a trigger id, this will trigger the fill dialog. + notifyViewEntered(view); + return true; + } + return false; + } + + /** + * Like {@link #showAutofillDialog(View)} but for virtual views. * - * @hide + * @param virtualId id identifying the virtual child inside the parent view. */ - public boolean isShowFillDialog(AutofillId id) { + // TODO(b/210926084): Consider whether to include the one-time show logic within this method. + public boolean showAutofillDialog(@NonNull View view, int virtualId) { + Objects.requireNonNull(view); + if (shouldShowAutofillDialog(getAutofillId(view, virtualId))) { + // If the id matches a trigger id, this will trigger the fill dialog. + notifyViewEntered(view, virtualId, /* bounds= */ null, /* flags= */ 0); + return true; + } + return false; + } + + private boolean shouldShowAutofillDialog(AutofillId id) { if (!hasFillDialogUiFeature() || mFillDialogTriggerIds == null) { return false; } diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 7e6e6fd23654..9a706673583b 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -278,6 +278,7 @@ public final class InputMethodInfo implements Parcelable { .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */)) .setIsAsciiCapable(a.getBoolean(com.android.internal.R.styleable .InputMethod_Subtype_isAsciiCapable, false)).build(); + a.recycle(); if (!subtype.isAuxiliary()) { isAuxIme = false; } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index c713a54fdf7d..2359d8d67f67 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1764,7 +1764,7 @@ public final class InputMethodManager { } /** - * This method is still kept for a while until android.support.v7.widget.SearchView ver. 26.0 + * This method is still kept for a while until androidx.appcompat.widget.SearchView ver. 26.0 * is publicly released because previous implementations of that class had relied on this method * via reflection. * @@ -1777,7 +1777,7 @@ public final class InputMethodManager { synchronized (mH) { try { Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be" - + " removed soon. If you are using android.support.v7.widget.SearchView," + + " removed soon. If you are using androidx.appcompat.widget.SearchView," + " please update to version 26.0 or newer version."); if (mCurRootView == null || mCurRootView.getView() == null) { Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()"); diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java index 13d44daa5a48..edcbce9a2774 100644 --- a/core/java/android/view/textservice/SpellCheckerInfo.java +++ b/core/java/android/view/textservice/SpellCheckerInfo.java @@ -124,6 +124,7 @@ public final class SpellCheckerInfo implements Parcelable { .SpellChecker_Subtype_subtypeExtraValue), a.getInt(com.android.internal.R.styleable .SpellChecker_Subtype_subtypeId, 0)); + a.recycle(); mSubtypes.add(subtype); } } diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java index de9f76d6eea1..f554f895a981 100644 --- a/core/java/android/widget/ArrayAdapter.java +++ b/core/java/android/widget/ArrayAdapter.java @@ -61,7 +61,7 @@ import java.util.List; * </p> * <p class="note"><strong>Note:</strong> * If you are considering using array adapter with a ListView, consider using - * {@link android.support.v7.widget.RecyclerView} instead. + * {@link androidx.recyclerview.widget.RecyclerView} instead. * RecyclerView offers similar features with better performance and more flexibility than * ListView provides. * See the diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java index ec072097f03c..1bde2351b2da 100755 --- a/core/java/android/widget/DatePickerCalendarDelegate.java +++ b/core/java/android/widget/DatePickerCalendarDelegate.java @@ -209,6 +209,7 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate { // Generate a non-activated color using the disabled alpha. final TypedArray ta = mContext.obtainStyledAttributes(ATTRS_DISABLED_ALPHA); final float disabledAlpha = ta.getFloat(0, 0.30f); + ta.recycle(); defaultColor = multiplyAlphaComponent(activatedColor, disabledAlpha); } diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java index 2c612804d718..9c0900b35de6 100644 --- a/core/java/android/widget/EditText.java +++ b/core/java/android/widget/EditText.java @@ -17,6 +17,7 @@ package android.widget; import android.content.Context; +import android.graphics.Rect; import android.text.Editable; import android.text.Selection; import android.text.Spannable; @@ -173,6 +174,12 @@ public class EditText extends TextView { return EditText.class.getName(); } + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + setHandwritingArea(new Rect(0, 0, w, h)); + } + /** @hide */ @Override protected boolean supportsAutoSizeText() { diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index d969a8894181..b2bc0764ce80 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -57,7 +57,7 @@ import com.android.internal.R; * @attr ref android.R.styleable#Gallery_gravity * * @deprecated This widget is no longer supported. Other horizontally scrolling - * widgets include {@link HorizontalScrollView} and {@link android.support.v4.view.ViewPager} + * widgets include {@link HorizontalScrollView} and {@link androidx.viewpager.widget.ViewPager} * from the support library. */ @Deprecated diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 3ad7b46d503a..15cd17b20f4f 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -63,12 +63,12 @@ import java.util.List; * <p>Scroll view supports vertical scrolling only. For horizontal scrolling, * use {@link HorizontalScrollView} instead.</p> * - * <p>Never add a {@link android.support.v7.widget.RecyclerView} or {@link ListView} to + * <p>Never add a {@link androidx.recyclerview.widget.RecyclerView} or {@link ListView} to * a scroll view. Doing so results in poor user interface performance and a poor user * experience.</p> * * <p class="note"> - * For vertical scrolling, consider {@link android.support.v4.widget.NestedScrollView} + * For vertical scrolling, consider {@link androidx.core.widget.NestedScrollView} * instead of scroll view which offers greater user interface flexibility and * support for the material design scrolling patterns.</p> * diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index d3600ef9f557..872e65a3ac55 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -72,7 +72,7 @@ import com.android.internal.R; * {@link #setSwitchTextAppearance(android.content.Context, int) switchTextAppearance} and * the related setSwitchTypeface() methods control that of the thumb. * - * <p>{@link android.support.v7.widget.SwitchCompat} is a version of + * <p>{@link androidx.recyclerview.widget.RecyclerView} is a version of * the Switch widget which runs on devices back to API 7.</p> * * <p>See the <a href="{@docRoot}guide/topics/ui/controls/togglebutton.html">Toggle Buttons</a> diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index c0c7641775f1..3dfb4a5a084a 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -5081,7 +5081,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * * @param color A color value in the form 0xAARRGGBB. * Do not pass a resource ID. To get a color value from a resource ID, call - * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}. + * {@link androidx.core.content.ContextCompat#getColor(Context, int) getColor}. * * @see #setTextColor(ColorStateList) * @see #getTextColors() diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java index dc9a585b56e2..a453c2818566 100644 --- a/core/java/android/widget/TimePickerClockDelegate.java +++ b/core/java/android/widget/TimePickerClockDelegate.java @@ -373,6 +373,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { // Generate a non-activated color using the disabled alpha. final TypedArray ta = mContext.obtainStyledAttributes(ATTRS_DISABLED_ALPHA); final float disabledAlpha = ta.getFloat(0, 0.30f); + ta.recycle(); defaultColor = multiplyAlphaComponent(activatedColor, disabledAlpha); } diff --git a/core/java/android/window/BackEvent.aidl b/core/java/android/window/BackEvent.aidl new file mode 100644 index 000000000000..821f1fa9830f --- /dev/null +++ b/core/java/android/window/BackEvent.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +/** + * @hide + */ +parcelable BackEvent; diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java new file mode 100644 index 000000000000..14985c987f97 --- /dev/null +++ b/core/java/android/window/BackEvent.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.RemoteAnimationTarget; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Represents an event that is sent out by the system during back navigation gesture. + * Holds information about the touch event, swipe direction and overall progress of the gesture + * interaction. + * + * @hide + */ +public class BackEvent implements Parcelable { + /** Indicates that the edge swipe starts from the left edge of the screen */ + public static final int EDGE_LEFT = 0; + /** Indicates that the edge swipe starts from the right edge of the screen */ + public static final int EDGE_RIGHT = 1; + + @IntDef({ + EDGE_LEFT, + EDGE_RIGHT, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SwipeEdge{} + + private final int mTouchX; + private final int mTouchY; + private final float mProgress; + + @SwipeEdge + private final int mSwipeEdge; + @Nullable + private final RemoteAnimationTarget mDepartingAnimationTarget; + + /** + * Creates a new {@link BackEvent} instance. + * + * @param touchX Absolute X location of the touch point. + * @param touchY Absolute Y location of the touch point. + * @param progress Value between 0 and 1 on how far along the back gesture is. + * @param swipeEdge Indicates which edge the swipe starts from. + * @param departingAnimationTarget The remote animation target of the departing application + * window. + */ + public BackEvent(int touchX, int touchY, float progress, @SwipeEdge int swipeEdge, + @Nullable RemoteAnimationTarget departingAnimationTarget) { + mTouchX = touchX; + mTouchY = touchY; + mProgress = progress; + mSwipeEdge = swipeEdge; + mDepartingAnimationTarget = departingAnimationTarget; + } + + private BackEvent(@NonNull Parcel in) { + mTouchX = in.readInt(); + mTouchY = in.readInt(); + mProgress = in.readFloat(); + mSwipeEdge = in.readInt(); + mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR); + } + + public static final Creator<BackEvent> CREATOR = new Creator<BackEvent>() { + @Override + public BackEvent createFromParcel(Parcel in) { + return new BackEvent(in); + } + + @Override + public BackEvent[] newArray(int size) { + return new BackEvent[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mTouchX); + dest.writeInt(mTouchY); + dest.writeFloat(mProgress); + dest.writeInt(mSwipeEdge); + dest.writeTypedObject(mDepartingAnimationTarget, flags); + } + + /** + * Returns a value between 0 and 1 on how far along the back gesture is. + */ + public float getProgress() { + return mProgress; + } + + /** + * Returns the absolute X location of the touch point. + */ + public int getTouchX() { + return mTouchX; + } + + /** + * Returns the absolute Y location of the touch point. + */ + public int getTouchY() { + return mTouchY; + } + + /** + * Returns the screen edge that the swipe starts from. + */ + public int getSwipeEdge() { + return mSwipeEdge; + } + + /** + * Returns the {@link RemoteAnimationTarget} of the top departing application window, + * or {@code null} if the top window should not be moved for the current type of back + * destination. + */ + @Nullable + public RemoteAnimationTarget getDepartingAnimationTarget() { + return mDepartingAnimationTarget; + } + + @Override + public String toString() { + return "BackEvent{" + + "mTouchX=" + mTouchX + + ", mTouchY=" + mTouchY + + ", mProgress=" + mProgress + + ", mSwipeEdge" + mSwipeEdge + + ", mDepartingAnimationTarget" + mDepartingAnimationTarget + + "}"; + } +} diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl index a42863c3126f..47796de11dd5 100644 --- a/core/java/android/window/IOnBackInvokedCallback.aidl +++ b/core/java/android/window/IOnBackInvokedCallback.aidl @@ -17,6 +17,8 @@ package android.window; +import android.window.BackEvent; + /** * Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager * and called from back handling process when back is invoked. @@ -38,7 +40,7 @@ oneway interface IOnBackInvokedCallback { * @param touchY Absolute Y location of the touch point. * @param progress Value between 0 and 1 on how far along the back gesture is. */ - void onBackProgressed(int touchX, int touchY, float progress); + void onBackProgressed(in BackEvent backEvent); /** * Called when a back gesture or back button press has been cancelled. diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index d37d3b42872f..03de4796ed74 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -55,10 +55,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { private static final boolean IS_BACK_PREDICTABILITY_ENABLED = SystemProperties .getInt(BACK_PREDICTABILITY_PROP, 0) > 0; - /** The currently most prioritized callback. */ - @Nullable - private OnBackInvokedCallbackWrapper mTopCallback; - /** Convenience hashmap to quickly decide if a callback has been added. */ private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>(); /** Holds all callbacks by priorities. */ @@ -72,8 +68,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window) { mWindowSession = windowSession; mWindow = window; - if (mTopCallback != null) { - setTopOnBackInvokedCallback(mTopCallback); + if (!mAllCallbacks.isEmpty()) { + setTopOnBackInvokedCallback(getTopCallback()); } } @@ -81,6 +77,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { public void detachFromWindow() { mWindow = null; mWindowSession = null; + clear(); } // TODO: Take an Executor for the callback to run on. @@ -110,11 +107,13 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { mOnBackInvokedCallbacks.get(prevPriority).remove(callback); } + OnBackInvokedCallback previousTopCallback = getTopCallback(); callbacks.add(callback); mAllCallbacks.put(callback, priority); - if (mTopCallback == null || (mTopCallback.getCallback() != callback - && mAllCallbacks.get(mTopCallback.getCallback()) <= priority)) { - setTopOnBackInvokedCallback(new OnBackInvokedCallbackWrapper(callback, priority)); + if (previousTopCallback == null + || (previousTopCallback != callback + && mAllCallbacks.get(previousTopCallback) <= priority)) { + setTopOnBackInvokedCallback(callback); } } @@ -126,11 +125,17 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } return; } + OnBackInvokedCallback previousTopCallback = getTopCallback(); Integer priority = mAllCallbacks.get(callback); - mOnBackInvokedCallbacks.get(priority).remove(callback); + ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority); + callbacks.remove(callback); + if (callbacks.isEmpty()) { + mOnBackInvokedCallbacks.remove(priority); + } mAllCallbacks.remove(callback); - if (mTopCallback != null && mTopCallback.getCallback() == callback) { - findAndSetTopOnBackInvokedCallback(); + // Re-populate the top callback to WM if the removed callback was previously the top one. + if (previousTopCallback == callback) { + setTopOnBackInvokedCallback(getTopCallback()); } } @@ -141,41 +146,26 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { /** Clears all registered callbacks on the instance. */ public void clear() { - mAllCallbacks.clear(); - mTopCallback = null; - mOnBackInvokedCallbacks.clear(); - } - - /** - * Iterates through all callbacks to find the most prioritized one and pushes it to - * window manager. - */ - private void findAndSetTopOnBackInvokedCallback() { - if (mAllCallbacks.isEmpty()) { + if (!mAllCallbacks.isEmpty()) { + // Clear binder references in WM. setTopOnBackInvokedCallback(null); - return; } - - for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) { - ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority); - if (!callbacks.isEmpty()) { - OnBackInvokedCallbackWrapper callback = new OnBackInvokedCallbackWrapper( - callbacks.get(callbacks.size() - 1), priority); - setTopOnBackInvokedCallback(callback); - return; - } - } - setTopOnBackInvokedCallback(null); + mAllCallbacks.clear(); + mOnBackInvokedCallbacks.clear(); } - // Pushes the top priority callback to window manager. - private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallbackWrapper callback) { - mTopCallback = callback; + private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) { if (mWindowSession == null || mWindow == null) { return; } try { - mWindowSession.setOnBackInvokedCallback(mWindow, mTopCallback); + if (callback == null) { + mWindowSession.setOnBackInvokedCallback(mWindow, null); + } else { + int priority = mAllCallbacks.get(callback); + mWindowSession.setOnBackInvokedCallback( + mWindow, new OnBackInvokedCallbackWrapper(callback, priority)); + } } catch (RemoteException e) { Log.e(TAG, "Failed to set OnBackInvokedCallback to WM. Error: " + e); } @@ -202,9 +192,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } @Override - public void onBackProgressed(int touchX, int touchY, float progress) + public void onBackProgressed(BackEvent backEvent) throws RemoteException { - Handler.getMain().post(() -> mCallback.onBackProgressed(touchX, touchY, progress)); + Handler.getMain().post(() -> mCallback.onBackProgressed(backEvent)); } @Override @@ -220,7 +210,16 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { @Override public OnBackInvokedCallback getTopCallback() { - return mTopCallback == null ? null : mTopCallback.getCallback(); + if (mAllCallbacks.isEmpty()) { + return null; + } + for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) { + ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority); + if (!callbacks.isEmpty()) { + return callbacks.get(callbacks.size() - 1); + } + } + return null; } /** diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java index 7b6df6c7118a..e58f4f06daea 100644 --- a/core/java/com/android/internal/logging/MetricsLogger.java +++ b/core/java/com/android/internal/logging/MetricsLogger.java @@ -22,7 +22,6 @@ import android.os.Build; import android.view.View; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.util.FrameworkStatsLog; /** * Writes sysui_multi_event records to the system event log. @@ -53,10 +52,7 @@ public class MetricsLogger { } protected void saveLog(LogMaker log) { - // TODO(b/116684537): Flag guard logging to event log and statsd socket. EventLogTags.writeSysuiMultiAction(log.serialize()); - FrameworkStatsLog.write(FrameworkStatsLog.KEY_VALUE_PAIRS_ATOM, - /* UID is retrieved from statsd side */ 0, log.getEntries()); } public static final int VIEW_UNKNOWN = MetricsEvent.VIEW_UNKNOWN; diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 51eb4296d7ae..46f54ce5ef59 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -308,5 +308,7 @@ oneway interface IStatusBar /** Notifies System UI about an update to the media tap-to-transfer receiver state. */ void updateMediaTapToTransferReceiverDisplay( int displayState, - in MediaRoute2Info routeInfo); + in MediaRoute2Info routeInfo, + in Icon appIcon, + in CharSequence appName); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 0c45e5b3eab4..6c17df1295b2 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -208,5 +208,7 @@ interface IStatusBarService /** Notifies System UI about an update to the media tap-to-transfer receiver state. */ void updateMediaTapToTransferReceiverDisplay( int displayState, - in MediaRoute2Info routeInfo); + in MediaRoute2Info routeInfo, + in Icon appIcon, + in CharSequence appName); } diff --git a/core/java/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java index 7a712e50e6a8..ced272225f48 100644 --- a/core/java/com/android/internal/util/ContrastColorUtil.java +++ b/core/java/com/android/internal/util/ContrastColorUtil.java @@ -627,7 +627,7 @@ public class ContrastColorUtil { } /** - * Framework copy of functions needed from android.support.v4.graphics.ColorUtils. + * Framework copy of functions needed from androidx.core.graphics.ColorUtils. */ private static class ColorUtilsFromCompat { private static final double XYZ_WHITE_REFERENCE_X = 95.047; diff --git a/core/java/com/android/internal/widget/GridLayoutManager.java b/core/java/com/android/internal/widget/GridLayoutManager.java index 09e6a991b1ac..3873e3b97f93 100644 --- a/core/java/com/android/internal/widget/GridLayoutManager.java +++ b/core/java/com/android/internal/widget/GridLayoutManager.java @@ -29,7 +29,7 @@ import java.util.Arrays; /** * Note: This GridLayoutManager widget may lack of latest fix because it is ported from - * oc-dr1-release version of android.support.v7.widget.GridLayoutManager due to compatibility + * oc-dr1-release version of androidx.gridlayout.widget.GridLayoutManager due to compatibility * concern with other internal widgets, like {@link RecyclerView} and {@link LinearLayoutManager}, * and is merely used for {@link com.android.internal.app.ChooserActivity}. * diff --git a/core/java/com/android/internal/widget/PagerAdapter.java b/core/java/com/android/internal/widget/PagerAdapter.java index 910a720eda03..c595f5cbed20 100644 --- a/core/java/com/android/internal/widget/PagerAdapter.java +++ b/core/java/com/android/internal/widget/PagerAdapter.java @@ -24,10 +24,10 @@ import android.view.ViewGroup; /** * Base class providing the adapter to populate pages inside of - * a {@link android.support.v4.view.ViewPager}. You will most likely want to use a more + * a {@link androidx.viewpager.view.ViewPager}. You will most likely want to use a more * specific implementation of this, such as - * {@link android.support.v4.app.FragmentPagerAdapter} or - * {@link android.support.v4.app.FragmentStatePagerAdapter}. + * {@link androidx.fragment.app.FragmentPagerAdapter} or + * {@link androidx.fragment.app.FragmentStatePagerAdapter}. * * <p>When you implement a PagerAdapter, you must override the following methods * at minimum:</p> diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java index be15a9bba107..e27557a0ef90 100644 --- a/core/java/com/android/internal/widget/RecyclerView.java +++ b/core/java/com/android/internal/widget/RecyclerView.java @@ -1299,7 +1299,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views. * This can be useful if you have multiple RecyclerViews with adapters that use the same * view types, for example if you have several data sets with the same kinds of item views - * displayed by a {@link android.support.v4.view.ViewPager ViewPager}. + * displayed by a {@link androidx.viewpager.view.ViewPager ViewPager}. * * @param pool Pool to set. If this parameter is null a new pool will be created and used. */ @@ -9764,13 +9764,13 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * Some general properties that a LayoutManager may want to use. */ public static class Properties { - /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation */ + /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_android_orientation */ public int orientation; - /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount */ + /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_spanCount */ public int spanCount; - /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout */ + /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_reverseLayout */ public boolean reverseLayout; - /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd */ + /** @attr ref androidx.recyclerview.R.styleable#RecyclerView_stackFromEnd */ public boolean stackFromEnd; } } diff --git a/core/java/com/android/internal/widget/SubtitleView.java b/core/java/com/android/internal/widget/SubtitleView.java index 21e63c5f2ed9..4d6151d8dd0a 100644 --- a/core/java/com/android/internal/widget/SubtitleView.java +++ b/core/java/com/android/internal/widget/SubtitleView.java @@ -115,6 +115,7 @@ public class SubtitleView extends View { break; } } + a.recycle(); // Set up density-dependent properties. // TODO: Move these to a default style. diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 955f46b977b7..63b704cedc86 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -252,7 +252,6 @@ cc_library_shared { "libandroid_net", "libandroidicu", "libbattery", - "libbpf_android", "libnetdutils", "libmemtrack", "libandroidfw", diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 24c0d2a9795e..2a4f812eb1de 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -67,6 +67,7 @@ per-file com_android_internal_net_* = file:/services/core/java/com/android/serve ### Graphics ### per-file android_graphics_* = file:/graphics/java/android/graphics/OWNERS per-file android_hardware_HardwareBuffer.cpp = file:/graphics/java/android/graphics/OWNERS +per-file android_hardware_SyncFence.cpp = file:/graphics/java/android/graphics/OWNERS ### Text ### per-file android_text_* = file:/core/java/android/text/OWNERS diff --git a/core/jni/android_app_admin_SecurityLog.cpp b/core/jni/android_app_admin_SecurityLog.cpp index e5a13db3dfb0..d197edceb9f7 100644 --- a/core/jni/android_app_admin_SecurityLog.cpp +++ b/core/jni/android_app_admin_SecurityLog.cpp @@ -85,10 +85,6 @@ static const JNINativeMethod gRegisterMethods[] = { (void*) android_app_admin_SecurityLog_isLoggingEnabled }, { "writeEvent", - "(ILjava/lang/String;)I", - (void*) SLog::writeEventString - }, - { "writeEvent", "(I[Ljava/lang/Object;)I", (void*) SLog::writeEventArray }, diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 2bec733d954c..dc55c0512941 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2476,37 +2476,47 @@ static jint android_media_AudioSystem_getMinSampleRate(JNIEnv *env, jobject thiz return 4000; // SAMPLE_RATE_HZ_MIN (for API) } -static jint -android_media_AudioSystem_setAssistantUid(JNIEnv *env, jobject thiz, jint uid) -{ - status_t status = AudioSystem::setAssistantUid(uid); +static std::vector<uid_t> convertJIntArrayToUidVector(JNIEnv *env, jintArray jArray) { + std::vector<uid_t> nativeVector; + if (jArray != nullptr) { + jsize len = env->GetArrayLength(jArray); + + if (len > 0) { + int *nativeArray = nullptr; + nativeArray = env->GetIntArrayElements(jArray, 0); + if (nativeArray != nullptr) { + for (size_t i = 0; i < len; i++) { + nativeVector.push_back(nativeArray[i]); + } + env->ReleaseIntArrayElements(jArray, nativeArray, 0); + } + } + } + return nativeVector; +} + +static jint android_media_AudioSystem_setAssistantServicesUids(JNIEnv *env, jobject thiz, + jintArray uids) { + std::vector<uid_t> nativeUidsVector = convertJIntArrayToUidVector(env, uids); + + status_t status = AudioSystem::setAssistantServicesUids(nativeUidsVector); + return (jint)nativeToJavaStatus(status); } -static jint android_media_AudioSystem_setHotwordDetectionServiceUid(JNIEnv *env, jobject thiz, - jint uid) { - status_t status = AudioSystem::setHotwordDetectionServiceUid(uid); +static jint android_media_AudioSystem_setActiveAssistantServicesUids(JNIEnv *env, jobject thiz, + jintArray activeUids) { + std::vector<uid_t> nativeActiveUidsVector = convertJIntArrayToUidVector(env, activeUids); + + status_t status = AudioSystem::setActiveAssistantServicesUids(nativeActiveUidsVector); + return (jint)nativeToJavaStatus(status); } static jint android_media_AudioSystem_setA11yServicesUids(JNIEnv *env, jobject thiz, jintArray uids) { - std::vector<uid_t> nativeUidsVector; - - if (uids != nullptr) { - jsize len = env->GetArrayLength(uids); - - if (len > 0) { - int *nativeUids = nullptr; - nativeUids = env->GetIntArrayElements(uids, 0); - if (nativeUids != nullptr) { - for (size_t i = 0; i < len; i++) { - nativeUidsVector.push_back(nativeUids[i]); - } - env->ReleaseIntArrayElements(uids, nativeUids, 0); - } - } - } + std::vector<uid_t> nativeUidsVector = convertJIntArrayToUidVector(env, uids); + status_t status = AudioSystem::setA11yServicesUids(nativeUidsVector); return (jint)nativeToJavaStatus(status); } @@ -3000,9 +3010,10 @@ static const JNINativeMethod gMethods[] = (void *)android_media_AudioSystem_getReportedSurroundFormats}, {"setSurroundFormatEnabled", "(IZ)I", (void *)android_media_AudioSystem_setSurroundFormatEnabled}, - {"setAssistantUid", "(I)I", (void *)android_media_AudioSystem_setAssistantUid}, - {"setHotwordDetectionServiceUid", "(I)I", - (void *)android_media_AudioSystem_setHotwordDetectionServiceUid}, + {"setAssistantServicesUids", "([I)I", + (void *)android_media_AudioSystem_setAssistantServicesUids}, + {"setActiveAssistantServicesUids", "([I)I", + (void *)android_media_AudioSystem_setActiveAssistantServicesUids}, {"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids}, {"isHapticPlaybackSupported", "()Z", (void *)android_media_AudioSystem_isHapticPlaybackSupported}, diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 2488b57c7f2a..336161c8c37e 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -245,6 +245,13 @@ static struct { jmethodID onTransactionCommitted; } gTransactionCommittedListenerClassInfo; +static struct { + jclass clazz; + jmethodID ctor; + jfieldID format; + jfieldID alphaInterpretation; +} gDisplayDecorationSupportInfo; + class JNamedColorSpace { public: // ColorSpace.Named.SRGB.ordinal() = 0; @@ -1792,13 +1799,29 @@ static void nativeSetGlobalShadowSettings(JNIEnv* env, jclass clazz, jfloatArray client->setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ, lightRadius); } -static jboolean nativeGetDisplayDecorationSupport(JNIEnv* env, jclass clazz, - jobject displayTokenObject) { +static jobject nativeGetDisplayDecorationSupport(JNIEnv* env, jclass clazz, + jobject displayTokenObject) { sp<IBinder> displayToken(ibinderForJavaObject(env, displayTokenObject)); if (displayToken == nullptr) { - return JNI_FALSE; + return nullptr; + } + const auto support = SurfaceComposerClient::getDisplayDecorationSupport(displayToken); + if (!support) { + return nullptr; + } + + jobject jDisplayDecorationSupport = + env->NewObject(gDisplayDecorationSupportInfo.clazz, gDisplayDecorationSupportInfo.ctor); + if (jDisplayDecorationSupport == nullptr) { + jniThrowException(env, "java/lang/OutOfMemoryError", nullptr); + return nullptr; } - return static_cast<jboolean>(SurfaceComposerClient::getDisplayDecorationSupport(displayToken)); + + env->SetIntField(jDisplayDecorationSupport, gDisplayDecorationSupportInfo.format, + static_cast<jint>(support.value().format)); + env->SetIntField(jDisplayDecorationSupport, gDisplayDecorationSupportInfo.alphaInterpretation, + static_cast<jint>(support.value().alphaInterpretation)); + return jDisplayDecorationSupport; } static jlong nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) { @@ -2131,7 +2154,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeMirrorSurface }, {"nativeSetGlobalShadowSettings", "([F[FFFF)V", (void*)nativeSetGlobalShadowSettings }, - {"nativeGetDisplayDecorationSupport", "(Landroid/os/IBinder;)Z", + {"nativeGetDisplayDecorationSupport", + "(Landroid/os/IBinder;)Landroid/hardware/graphics/common/DisplayDecorationSupport;", (void*)nativeGetDisplayDecorationSupport}, {"nativeGetHandle", "(J)J", (void*)nativeGetHandle }, @@ -2390,6 +2414,17 @@ int register_android_view_SurfaceControl(JNIEnv* env) gTransactionCommittedListenerClassInfo.onTransactionCommitted = GetMethodIDOrDie(env, transactionCommittedListenerClazz, "onTransactionCommitted", "()V"); + + jclass displayDecorationSupportClazz = + FindClassOrDie(env, "android/hardware/graphics/common/DisplayDecorationSupport"); + gDisplayDecorationSupportInfo.clazz = MakeGlobalRefOrDie(env, displayDecorationSupportClazz); + gDisplayDecorationSupportInfo.ctor = + GetMethodIDOrDie(env, displayDecorationSupportClazz, "<init>", "()V"); + gDisplayDecorationSupportInfo.format = + GetFieldIDOrDie(env, displayDecorationSupportClazz, "format", "I"); + gDisplayDecorationSupportInfo.alphaInterpretation = + GetFieldIDOrDie(env, displayDecorationSupportClazz, "alphaInterpretation", "I"); + return err; } diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto index 5c6116a09b77..279a5d0c17f8 100644 --- a/core/proto/android/content/package_item_info.proto +++ b/core/proto/android/content/package_item_info.proto @@ -115,4 +115,5 @@ message ApplicationInfoProto { } optional Detail detail = 17; repeated string overlay_paths = 18; + repeated string known_activity_embedding_certs = 19; } diff --git a/core/proto/android/internal/binder_latency.proto b/core/proto/android/internal/binder_latency.proto index 8b11f5b34645..edd9b712f4dc 100644 --- a/core/proto/android/internal/binder_latency.proto +++ b/core/proto/android/internal/binder_latency.proto @@ -35,6 +35,7 @@ message Dims { SYSTEM_SERVER = 1; TELEPHONY = 2; BLUETOOTH = 3; + WIFI = 4; } enum ServiceClassName { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f29de56e7788..68c8143299c5 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1905,6 +1905,13 @@ <permission android:name="android.permission.MANAGE_WIFI_AUTO_JOIN" android:protectionLevel="signature|privileged" /> + <!-- Allows applications to get notified when a Wi-Fi interface request cannot + be satisfied without tearing down one or more other interfaces, and provide a decision + whether to approve the request or reject it. + <p>Not for use by third-party applications. --> + <permission android:name="android.permission.MANAGE_WIFI_INTERFACES" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi @hide Allows apps to create and manage IPsec tunnels. <p>Only granted to applications that are currently bound by the system for creating and managing IPsec-based interfaces. @@ -4403,6 +4410,16 @@ <permission android:name="android.permission.SCHEDULE_EXACT_ALARM" android:protectionLevel="normal|appop"/> + <!-- Allows apps to use exact alarms just like with SCHEDULE_EXACT_ALARM but without needing + to request this permission from the user. + <p><b>This is only for apps that rely on exact alarms for their core functionality.</b> + App stores may enforce policies to audit and review the use of this permission. Any app that + requests this but is found to not require exact alarms for its primary function may be + removed from the app store. + --> + <permission android:name="android.permission.USE_EXACT_ALARM" + android:protectionLevel="normal"/> + <!-- Allows an application to query tablet mode state and monitor changes in it. <p>Not for use by third-party applications. @@ -5900,10 +5917,9 @@ <!-- @SystemApi Allows an application to manage the wallpaper effects generation service. - @hide <p>Not for use by third-party applications.</p> --> + @hide <p>Not for use by third-party applications.</p> --> <permission android:name="android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION" - android:protectionLevel="signature" /> - + android:protectionLevel="signature|role" /> <!-- Allows an app to set the theme overlay in /vendor/overlay being used. @@ -6239,7 +6255,10 @@ android:protectionLevel="signature|privileged" /> <!-- Allows an application to read nearby streaming policy. The policy controls - whether to allow the device to stream its notifications and apps to nearby devices. --> + whether to allow the device to stream its notifications and apps to nearby devices. + Applications that are not the device owner will need this permission to call + {@link android.app.admin.DevicePolicyManager#getNearbyNotificationStreamingPolicy} or + {@link android.app.admin.DevicePolicyManager#getNearbyAppStreamingPolicy}. --> <permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" android:protectionLevel="normal" /> @@ -6361,6 +6380,11 @@ <permission android:name="android.permission.TIS_EXTENSION_INTERFACE" android:protectionLevel="signature|privileged|vendorPrivileged" /> + <!-- @SystemApi Allows an application to write to the security log buffer in logd. + @hide --> + <permission android:name="android.permission.WRITE_SECURITY_LOG" + android:protectionLevel="signature|privileged" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> @@ -6619,6 +6643,14 @@ android:exported="false"> </activity> + <activity android:name="com.android.server.logcat.LogAccessConfirmationActivity" + android:theme="@style/Theme.Dialog.Confirmation" + android:excludeFromRecents="true" + android:process=":ui" + android:label="@string/log_access_confirmation_title" + android:exported="false"> + </activity> + <activity android:name="com.android.server.notification.NASLearnMoreActivity" android:theme="@style/Theme.Dialog.Confirmation" android:excludeFromRecents="true" diff --git a/core/res/res/drawable/toast_frame.xml b/core/res/res/drawable/toast_frame.xml index 44c00c0521b4..a8cdef6d05f1 100644 --- a/core/res/res/drawable/toast_frame.xml +++ b/core/res/res/drawable/toast_frame.xml @@ -17,7 +17,7 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <solid android:color="?android:attr/colorBackground" /> + <solid android:color="?android:attr/colorSurface" /> <corners android:radius="28dp" /> </shape> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 090b1c527d15..fca2bd15787d 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2332,6 +2332,17 @@ contrast between the window background and the icon. Note the shape would also be masking like an icon. --> <attr name="windowSplashScreenIconBackgroundColor" format="color"/> + + <!-- Specify whether this application always wants the icon to be displayed on the splash + screen. --> + <attr name="windowSplashScreenBehavior"> + <!-- The icon is shown when the launching activity sets the splashScreenStyle to + SPLASH_SCREEN_STYLE_ICON. If the launching activity does not specify any style, + follow the system behavior. --> + <enum name="default" value="0" /> + <!-- The icon is shown unless the launching app specified SPLASH_SCREEN_STYLE_EMPTY --> + <enum name="icon_preferred" value="1" /> + </attr> </declare-styleable> <!-- The set of attributes that describe a AlertDialog's theme. --> @@ -3978,31 +3989,34 @@ <attr name="isAccessibilityTool" format="boolean" /> <!-- Animated image of the accessibility service purpose or behavior, to help users - understand how the service can help them.--> + understand how the service can help them. --> <attr name="animatedImageDrawable" format="reference"/> - <!-- Html description of the accessibility service, to help users understand - how the service can help them.--> + <!-- Html description of the accessibility service usage, availability, or limitations (e.g. + isn't supported by all apps). --> <attr name="htmlDescription" format="reference"/> - - <!-- Short description of the accessibility service purpose or behavior.--> + <!-- Description of the accessibility service usage, availability, or limitations (e.g. + isn't supported by all apps). --> <attr name="description" /> <!-- Brief summary of the accessibility service purpose or behavior. --> <attr name="summary" /> + <!-- Detailed intro of the accessibility service purpose or behavior. --> + <attr name="intro" format="reference" /> </declare-styleable> <!-- Use <code>accessibility-shortcut-target</code> as the root tag of the XML resource that describes an activity, which is referenced from the <code>android.accessibilityshortcut.target</code> meta-data entry. --> <declare-styleable name="AccessibilityShortcutTarget"> - <!-- Short description of the target of accessibility shortcut purpose or behavior.--> + <!-- Description of the target of accessibility shortcut usage, availability, or limitations + (e.g. isn't supported by all apps). --> <attr name="description" /> <!-- Brief summary of the target of accessibility shortcut purpose or behavior. --> <attr name="summary" /> <!-- Animated image of the target of accessibility shortcut purpose or behavior, to help users understand how the target of accessibility shortcut can help them.--> <attr name="animatedImageDrawable" format="reference"/> - <!-- Html description of the target of accessibility shortcut purpose or behavior, to help - users understand how the target of accessibility shortcut can help them. --> + <!-- Html description of the target of accessibility shortcut usage, availability, or + limitations (e.g. isn't supported by all apps). --> <attr name="htmlDescription" format="reference"/> <!-- Component name of an activity that allows the user to modify the settings for this target of accessibility shortcut. --> @@ -4012,6 +4026,8 @@ settings to remind users this accessibility service has a {@link android.service.quicksettings.TileService}. --> <attr name="tileService" format="string" /> + <!-- Detailed intro of the target of accessibility shortcut purpose or behavior. --> + <attr name="intro" format="reference" /> </declare-styleable> <!-- Use <code>print-service</code> as the root tag of the XML resource that diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index cb40e86f1535..6dc975b4112c 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1048,6 +1048,24 @@ <p>The default value of this attribute is <code>false</code>. --> <attr name="allowEmbedded" format="boolean" /> + <!-- A reference to an array resource containing the signing certificate digests, one of which a + client is required to be signed with in order to embed the activity. If the client is not + signed with one of the certificates in the set, and the activity does not allow embedding + by untrusted hosts via {@link android.R.attr#allowUntrustedActivityEmbedding} flag, the + embedding request will fail. + <p>The digest should be computed over the DER encoding of the trusted certificate using the + SHA-256 digest algorithm. + <p>If only a single signer is declared this can also be a string resource, or the digest + can be declared inline as the value for this attribute. + <p>If the attribute is declared both on the application and the activity level, the value + on the activity level takes precedence. --> + <attr name="knownActivityEmbeddingCerts" format="reference|string" /> + + <!-- Indicate that the activity can be embedded by untrusted hosts. In this case the + interactions and visibility of the embedded activity may be limited. + <p>The default value of this attribute is <code>false</code>. --> + <attr name="allowUntrustedActivityEmbedding" format="boolean" /> + <!-- Specifies whether this {@link android.app.Activity} should be shown on top of the lock screen whenever the lockscreen is up and this activity has another activity behind it with the {@link android.R.attr#showWhenLocked} attribute set. That @@ -2011,6 +2029,7 @@ when the application's user data is cleared. The default value is false. --> <attr name="resetEnabledSettingsOnAppDataCleared" format="boolean" /> + <attr name="knownActivityEmbeddingCerts" /> </declare-styleable> <!-- An attribution is a logical part of an app and is identified by a tag. @@ -3033,6 +3052,8 @@ <!-- Indicates whether the activity can be displayed on a remote device which may or may not be running Android. --> <attr name="canDisplayOnRemoteDevices" format="boolean"/> + <attr name="allowUntrustedActivityEmbedding" /> + <attr name="knownActivityEmbeddingCerts" /> </declare-styleable> <!-- The <code>activity-alias</code> tag declares a new @@ -3073,6 +3094,8 @@ <attr name="exported" /> <attr name="parentActivityName" /> <attr name="attributionTags" /> + <attr name="allowUntrustedActivityEmbedding" /> + <attr name="knownActivityEmbeddingCerts" /> </declare-styleable> <!-- The <code>meta-data</code> tag is used to attach additional diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 775527df86d8..5e6d05a03e7e 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1307,6 +1307,7 @@ <!-- Control the behavior when the user double-taps the home button. 0 - Nothing 1 - Recent apps view in SystemUI + 2 - Picture-in-picture menu This needs to match the constants in policy/src/com/android/internal/policy/impl/PhoneWindowManager.java --> @@ -4276,6 +4277,9 @@ <!-- URI for in call notification sound --> <string translatable="false" name="config_inCallNotificationSound">/product/media/audio/ui/InCallNotification.ogg</string> + <!-- URI for camera shutter sound --> + <string translatable="false" name="config_cameraShutterSound">/product/media/audio/ui/camera_click.ogg</string> + <!-- URI for default ringtone sound file to be used for silent ringer vibration --> <string translatable="false" name="config_defaultRingtoneVibrationSound"></string> @@ -5697,4 +5701,92 @@ --> <string-array name="config_dockExtconStateMapping"> </string-array> + + <!-- Whether or not the monitoring on the apps' background battery drain is enabled --> + <bool name="config_bg_current_drain_monitor_enabled">true</bool> + + <!-- The threshold of the background current drain (in percentage) to the restricted + standby bucket. + --> + <array name="config_bg_current_drain_threshold_to_restricted_bucket"> + <item>2.0</item> <!-- regular device --> + <item>4.0</item> <!-- low ram device --> + </array> + + <!-- The threshold of the background current drain (in percentage) to the background + restricted level. + --> + <array name="config_bg_current_drain_threshold_to_bg_restricted"> + <item>4.0</item> <!-- regular device --> + <item>8.0</item> <!-- low ram device --> + </array> + + <!-- The background current drain monitoring window size. --> + <integer name="config_bg_current_drain_window">86400</integer> + + <!-- The types of battery drain we're checking on each app; if the sum of the battery drain + exceeds the threshold, it'll be moved to restricted standby bucket. The value must be + one of or combination of the definitions in AppBatteryPolicy. + --> + <integer name="config_bg_current_drain_types_to_restricted_bucket">4</integer> + + <!-- The types of battery drain we're checking on each app; if the sum of the battery drain + exceeds the threshold, it'll be moved to background restricted level. The value must be + one of or combination of the definitions in AppBatteryPolicy. + --> + <integer name="config_bg_current_drain_types_to_bg_restricted">12</integer> + + <!-- The power usage components we're monitoring. Must one of the definition in BatteryConsumer. + --> + <integer name="config_bg_current_drain_power_components">-1</integer> + + <!-- Whether or not enable the different threshold based on the durations of + certain event type. + --> + <bool name="config_bg_current_drain_event_duration_based_threshold_enabled">false</bool> + + <!-- The threshold of the background current drain (in percentage) to the restricted + standby bucket for legitimate case with higher background current drain. + --> + <array name="config_bg_current_drain_high_threshold_to_restricted_bucket"> + <item>30.0</item> <!-- regular device --> + <item>60.0</item> <!-- low ram device --> + </array> + + <!-- The threshold of the background current drain (in percentage) to the background + restricted level for legitimate case with higher background current drain. + --> + <array name="config_bg_current_drain_high_threshold_to_bg_restricted"> + <item>20.0</item> <!-- regular device --> + <item>40.0</item> <!-- low ram device --> + </array> + + <!-- The threshold of minimal time of hosting a foreground service with type "mediaPlayback" + or a media session, over the given window, so it'd subject towards the higher background + current drain threshold. + --> + <integer name="config_bg_current_drain_media_playback_min_duration">1800</integer> + + <!-- The threshold of minimal time of hosting a foreground service with type "location" + over the given window, so it'd subject towards the higher background + current drain threshold. + --> + <integer name="config_bg_current_drain_location_min_duration">1800</integer> + + <!-- The behavior for an app with a FGS and its notification is still showing, when the system + detects it's abusive and should be put into bg restricted level. True - we'll + show the prompt to user, False - we'll not show it. + --> + <bool name="config_bg_prompt_fgs_with_noti_to_bg_restricted">false</bool> + + <!-- The types of state where we'll exempt its battery usage during that state. + The state here must be one or a combination of STATE_TYPE_* in BaseAppStateTracker. + --> + <integer name="config_bg_current_drain_exempted_types">9</integer> + + <!-- The behavior when an app has the permission ACCESS_BACKGROUND_LOCATION granted, + whether or not the system will use a higher threshold towards its background battery usage + because of it. + --> + <bool name="config_bg_current_drain_high_threshold_by_bg_location">false</bool> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index f05137e144d9..7bf34a127523 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3275,6 +3275,10 @@ <public name="toExtendRight" /> <public name="toExtendBottom" /> <public name="tileService" /> + <public name="windowSplashScreenBehavior" /> + <public name="allowUntrustedActivityEmbedding" /> + <public name="knownActivityEmbeddingCerts" /> + <public name="intro" /> </staging-public-group> <staging-public-group type="id" first-id="0x01de0000"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 6297ed90fcba..e41aa45e0a44 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5719,6 +5719,20 @@ <!-- Title for the harmful app warning dialog. [CHAR LIMIT=40] --> <string name="harmful_app_warning_title">Harmful app detected</string> + <!-- Title for the log access confirmation dialog. [CHAR LIMIT=40] --> + <string name="log_access_confirmation_title">System log access request</string> + <!-- Label for the allow button on the log access confirmation dialog. [CHAR LIMIT=20] --> + <string name="log_access_confirmation_allow">Only this time</string> + <!-- Label for the deny button on the log access confirmation dialog. [CHAR LIMIT=20] --> + <string name="log_access_confirmation_deny">Don\u2019t allow</string> + + <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]--> + <string name="log_access_confirmation_body"><xliff:g id="log_access_app_name" example="Example App">%s</xliff:g> requests system logs for functional debugging. + These logs might contain information that apps and services on your device have written.</string> + + <!-- Privacy notice do not show [CHAR LIMIT=20] --> + <string name="log_access_do_not_show_again">Don\u2019t show again</string> + <!-- Text describing a permission request for one app to show another app's slices [CHAR LIMIT=NONE] --> <string name="slices_permission_request"><xliff:g id="app" example="Example App">%1$s</xliff:g> wants to show <xliff:g id="app_2" example="Other Example App">%2$s</xliff:g> slices</string> @@ -6266,4 +6280,8 @@ ul.</string> </string> <!-- Action label of notification for user to check background apps. [CHAR LIMIT=NONE] --> <string name="notification_action_check_bg_apps">Check active apps</string> + + <!-- Strings for VirtualDeviceManager --> + <!-- Error message indicating the camera cannot be accessed when running on a virtual device. [CHAR LIMIT=NONE] --> + <string name="vdm_camera_access_denied">Cannot access camera from this device</string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d731180bd56a..ff1c70a273dc 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3699,6 +3699,9 @@ <java-symbol type="string" name="config_defaultSystemCaptionsService" /> <java-symbol type="string" name="config_defaultSystemCaptionsManagerService" /> <java-symbol type="string" name="config_defaultAmbientContextDetectionService" /> + <java-symbol type="string" name="config_defaultAmbientContextConsentComponent" /> + <java-symbol type="string" name="config_ambientContextPackageNameExtraKey" /> + <java-symbol type="string" name="config_ambientContextEventArrayExtraKey" /> <java-symbol type="string" name="config_retailDemoPackage" /> <java-symbol type="string" name="config_retailDemoPackageSignature" /> @@ -3749,6 +3752,7 @@ <java-symbol type="bool" name="config_handleVolumeAliasesUsingVolumeGroups" /> <java-symbol type="dimen" name="config_inCallNotificationVolume" /> <java-symbol type="string" name="config_inCallNotificationSound" /> + <java-symbol type="string" name="config_cameraShutterSound" /> <java-symbol type="integer" name="config_autoGroupAtCount" /> <java-symbol type="bool" name="config_dozeAlwaysOnDisplayAvailable" /> <java-symbol type="bool" name="config_dozeAlwaysOnEnabled" /> @@ -3868,6 +3872,11 @@ <java-symbol type="string" name="harmful_app_warning_title" /> <java-symbol type="layout" name="harmful_app_warning_dialog" /> + <java-symbol type="string" name="log_access_confirmation_allow" /> + <java-symbol type="string" name="log_access_confirmation_deny" /> + <java-symbol type="string" name="log_access_confirmation_title" /> + <java-symbol type="string" name="log_access_confirmation_body" /> + <java-symbol type="string" name="config_defaultAssistantAccessComponent" /> <java-symbol type="string" name="slices_permission_request" /> @@ -4724,5 +4733,24 @@ <java-symbol type="bool" name="config_lowPowerStandbyEnabledByDefault" /> <java-symbol type="integer" name="config_lowPowerStandbyNonInteractiveTimeout" /> + <!-- For VirtualDeviceManager --> + <java-symbol type="string" name="vdm_camera_access_denied" /> + <java-symbol type="color" name="camera_privacy_light"/> + + <java-symbol type="bool" name="config_bg_current_drain_monitor_enabled" /> + <java-symbol type="array" name="config_bg_current_drain_threshold_to_restricted_bucket" /> + <java-symbol type="array" name="config_bg_current_drain_threshold_to_bg_restricted" /> + <java-symbol type="integer" name="config_bg_current_drain_window" /> + <java-symbol type="integer" name="config_bg_current_drain_types_to_restricted_bucket" /> + <java-symbol type="integer" name="config_bg_current_drain_types_to_bg_restricted" /> + <java-symbol type="integer" name="config_bg_current_drain_power_components" /> + <java-symbol type="bool" name="config_bg_current_drain_event_duration_based_threshold_enabled" /> + <java-symbol type="array" name="config_bg_current_drain_high_threshold_to_restricted_bucket" /> + <java-symbol type="array" name="config_bg_current_drain_high_threshold_to_bg_restricted" /> + <java-symbol type="integer" name="config_bg_current_drain_media_playback_min_duration" /> + <java-symbol type="integer" name="config_bg_current_drain_location_min_duration" /> + <java-symbol type="bool" name="config_bg_prompt_fgs_with_noti_to_bg_restricted" /> + <java-symbol type="integer" name="config_bg_current_drain_exempted_types" /> + <java-symbol type="bool" name="config_bg_current_drain_high_threshold_by_bg_location" /> </resources> diff --git a/core/tests/coretests/res/values/strings.xml b/core/tests/coretests/res/values/strings.xml index 21613a82deef..e51eab60a998 100644 --- a/core/tests/coretests/res/values/strings.xml +++ b/core/tests/coretests/res/values/strings.xml @@ -143,6 +143,9 @@ <!-- ResourcesLocaleResolutionTest --> <string name="dummy_string">dummy string</string> + <!-- Intro of the accessibility shortcut [CHAR LIMIT=NONE] --> + <string name="accessibility_shortcut_intro">Accessibility shortcut intro</string> + <!-- Description of the accessibility shortcut [CHAR LIMIT=NONE] --> <string name="accessibility_shortcut_description">Accessibility shortcut description</string> diff --git a/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml b/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml index 5fc536aafd0d..52fe65d3ad79 100644 --- a/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml +++ b/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml @@ -23,4 +23,4 @@ android:htmlDescription="@string/accessibility_shortcut_html_description" android:settingsActivity="com.example.shortcut.target.SettingsActivity" android:tileService="com.example.shortcut.target.TileService" -/>
\ No newline at end of file + android:intro="@string/accessibility_shortcut_intro" />
\ No newline at end of file diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java index 76fb520c91bc..f605a0075e8a 100644 --- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java +++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java @@ -89,6 +89,16 @@ public class AccessibilityShortcutInfoTest { } @Test + public void testIntro() { + final String intro = mTargetContext.getResources() + .getString(R.string.accessibility_shortcut_intro); + + assertNotNull("Can't find intro string", intro); + assertThat("Intro is not correct", + mShortcutInfo.loadIntro(mPackageManager), is(intro)); + } + + @Test public void testAnimatedImageRes() { assertThat("Animated image resource id is not correct", mShortcutInfo.getAnimatedImageRes(), is(R.drawable.bitmap_drawable)); diff --git a/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java b/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java new file mode 100644 index 000000000000..c1b6666a2d17 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.activity; + +import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; +import static android.content.Context.OVERRIDABLE_COMPONENT_CALLBACKS; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; + +import android.app.Activity; +import android.app.WindowConfiguration; +import android.app.activity.ActivityThreadTest.TestActivity; +import android.compat.testing.PlatformCompatChangeRule; +import android.content.ComponentCallbacks; +import android.content.TestComponentCallbacks2; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; + +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + +/** + * Test for verifying {@link Activity#registerComponentCallbacks(ComponentCallbacks)} behavior. + * Build/Install/Run: + * atest FrameworksCoreTests:android.app.activity.RegisterComponentCallbacksTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class RegisterComponentCallbacksTest { + @Rule + public ActivityScenarioRule rule = new ActivityScenarioRule<>(TestActivity.class); + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + @Test + public void testRegisterComponentCallbacks() { + final ActivityScenario scenario = rule.getScenario(); + final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2(); + final Configuration config = new Configuration(); + config.fontScale = 1.2f; + config.windowConfiguration.setWindowingMode( + WindowConfiguration.WINDOWING_MODE_FREEFORM); + config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100)); + final int trimMemoryLevel = TRIM_MEMORY_RUNNING_LOW; + + scenario.onActivity(activity -> { + // It should be no-op to unregister a ComponentCallbacks without registration. + activity.unregisterComponentCallbacks(callbacks); + + activity.registerComponentCallbacks(callbacks); + // Verify #onConfigurationChanged + activity.onConfigurationChanged(config); + assertThat(callbacks.mConfiguration).isEqualTo(config); + // Verify #onTrimMemory + activity.onTrimMemory(trimMemoryLevel); + assertThat(callbacks.mLevel).isEqualTo(trimMemoryLevel); + // verify #onLowMemory + activity.onLowMemory(); + assertThat(callbacks.mLowMemoryCalled).isTrue(); + + activity.unregisterComponentCallbacks(callbacks); + }); + } + + @DisableCompatChanges(OVERRIDABLE_COMPONENT_CALLBACKS) + @Test + public void testRegisterComponentCallbacksBeforeT() { + final ActivityScenario scenario = rule.getScenario(); + final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2(); + final Configuration config = new Configuration(); + config.fontScale = 1.2f; + config.windowConfiguration.setWindowingMode( + WindowConfiguration.WINDOWING_MODE_FREEFORM); + config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100)); + final int trimMemoryLevel = TRIM_MEMORY_RUNNING_LOW; + + scenario.onActivity(activity -> { + // It should be no-op to unregister a ComponentCallbacks without registration. + activity.unregisterComponentCallbacks(callbacks); + + activity.registerComponentCallbacks(callbacks); + // Verify #onConfigurationChanged + activity.onConfigurationChanged(config); + assertWithMessage("The ComponentCallbacks must be added to #getApplicationContext " + + "before T.").that(callbacks.mConfiguration).isNull(); + // Verify #onTrimMemory + activity.onTrimMemory(trimMemoryLevel); + assertWithMessage("The ComponentCallbacks must be added to #getApplicationContext " + + "before T.").that(callbacks.mLevel).isEqualTo(0); + // verify #onLowMemory + activity.onLowMemory(); + assertWithMessage("The ComponentCallbacks must be added to #getApplicationContext " + + "before T.").that(callbacks.mLowMemoryCalled).isFalse(); + + activity.unregisterComponentCallbacks(callbacks); + }); + } +} diff --git a/core/tests/coretests/src/android/companion/virtual/audio/VirtualAudioSessionTest.java b/core/tests/coretests/src/android/companion/virtual/audio/VirtualAudioSessionTest.java new file mode 100644 index 000000000000..d66cb712b1ca --- /dev/null +++ b/core/tests/coretests/src/android/companion/virtual/audio/VirtualAudioSessionTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.audio; + +import static android.media.AudioFormat.CHANNEL_IN_MONO; +import static android.media.AudioFormat.CHANNEL_OUT_MONO; +import static android.media.AudioFormat.ENCODING_PCM_16BIT; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.testng.Assert.assertThrows; + +import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback; +import android.content.Context; +import android.content.ContextWrapper; +import android.media.AudioFormat; +import android.media.AudioPlaybackConfiguration; +import android.media.AudioRecordingConfiguration; +import android.platform.test.annotations.Presubmit; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class VirtualAudioSessionTest { + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + @Mock + private AudioConfigurationChangeCallback mCallback; + private static final int APP_UID = 100; + private static final int APP_UID2 = 200; + private static final AudioFormat AUDIO_CAPTURE_FORMAT = + new AudioFormat.Builder() + .setSampleRate(48000) + .setEncoding(ENCODING_PCM_16BIT) + .setChannelMask(CHANNEL_IN_MONO) + .build(); + private static final AudioFormat AUDIO_INJECT_FORMAT = + new AudioFormat.Builder() + .setSampleRate(48000) + .setEncoding(ENCODING_PCM_16BIT) + .setChannelMask(CHANNEL_OUT_MONO) + .build(); + private Context mContext; + private VirtualAudioSession mVirtualAudioSession; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); + mVirtualAudioSession = new VirtualAudioSession( + mContext, mCallback, /* executor= */ null); + } + + @Test + public void startAudioCapture_isSuccessful() { + AudioCapture audioCapture = mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT); + + assertThat(audioCapture).isNotNull(); + assertThat(mVirtualAudioSession.getAudioCapture()).isEqualTo(audioCapture); + } + + @Test + public void startAudioCapture_audioCaptureAlreadyStarted_throws() { + mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT); + + assertThrows(IllegalStateException.class, + () -> mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT)); + } + + @Test + public void startAudioInjection_isSuccessful() { + AudioInjection audioInjection = mVirtualAudioSession.startAudioInjection( + AUDIO_INJECT_FORMAT); + + assertThat(audioInjection).isNotNull(); + assertThat(mVirtualAudioSession.getAudioInjection()).isEqualTo(audioInjection); + } + + @Test + public void startAudioInjection_audioInjectionAlreadyStarted_throws() { + mVirtualAudioSession.startAudioInjection(AUDIO_INJECT_FORMAT); + + assertThrows(IllegalStateException.class, + () -> mVirtualAudioSession.startAudioInjection(AUDIO_INJECT_FORMAT)); + } + + @Test + public void onAppsNeedingAudioRoutingChanged_neverStartAudioCaptureOrInjection_throws() { + int[] uids = new int[]{APP_UID}; + + assertThrows(IllegalStateException.class, + () -> mVirtualAudioSession.onAppsNeedingAudioRoutingChanged(uids)); + } + + @Test + public void onAppsNeedingAudioRoutingChanged_cachesReroutedApps() { + mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT); + mVirtualAudioSession.startAudioInjection(AUDIO_INJECT_FORMAT); + int[] appUids = new int[]{APP_UID}; + + mVirtualAudioSession.onAppsNeedingAudioRoutingChanged(appUids); + + assertThat(Arrays.equals(mVirtualAudioSession.getReroutedAppUids().toArray(), + appUids)).isTrue(); + } + + @Test + public void onAppsNeedingAudioRoutingChanged_receiveManyTimes_reroutedAppsSizeIsCorrect() { + mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT); + mVirtualAudioSession.startAudioInjection(AUDIO_INJECT_FORMAT); + int[] appUids = new int[]{APP_UID, APP_UID2}; + + mVirtualAudioSession.onAppsNeedingAudioRoutingChanged(new int[]{1234}); + mVirtualAudioSession.onAppsNeedingAudioRoutingChanged(new int[]{5678}); + mVirtualAudioSession.onAppsNeedingAudioRoutingChanged(appUids); + + assertThat(Arrays.equals(mVirtualAudioSession.getReroutedAppUids().toArray(), + appUids)).isTrue(); + assertThat(mVirtualAudioSession.getReroutedAppUids().size()).isEqualTo(2); + } + + @Test + public void close_releasesCaptureAndInjection() { + mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT); + mVirtualAudioSession.startAudioInjection(AUDIO_INJECT_FORMAT); + + mVirtualAudioSession.close(); + + assertThat(mVirtualAudioSession.getAudioCapture()).isNull(); + assertThat(mVirtualAudioSession.getAudioInjection()).isNull(); + } + + @Test + public void onPlaybackConfigChanged_sendsCallback() { + List<AudioPlaybackConfiguration> configs = new ArrayList<>(); + + mVirtualAudioSession.onPlaybackConfigChanged(configs); + + verify(mCallback).onPlaybackConfigChanged(configs); + } + + @Test + public void onRecordingConfigChanged_sendCallback() { + List<AudioRecordingConfiguration> configs = new ArrayList<>(); + + mVirtualAudioSession.onRecordingConfigChanged(configs); + + verify(mCallback).onRecordingConfigChanged(configs); + } +} diff --git a/core/tests/coretests/src/android/content/ContextWrapperTest.java b/core/tests/coretests/src/android/content/ContextWrapperTest.java index ecaf1f42fbca..495770245b49 100644 --- a/core/tests/coretests/src/android/content/ContextWrapperTest.java +++ b/core/tests/coretests/src/android/content/ContextWrapperTest.java @@ -16,7 +16,7 @@ package android.content; -import static android.content.ContextWrapper.COMPONENT_CALLBACK_ON_WRAPPER; +import static android.content.Context.OVERRIDABLE_COMPONENT_CALLBACKS; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -61,7 +61,7 @@ public class ContextWrapperTest { * register {@link ComponentCallbacks} to {@link ContextWrapper#getApplicationContext} before * {@link ContextWrapper#attachBaseContext(Context)}. */ - @DisableCompatChanges(COMPONENT_CALLBACK_ON_WRAPPER) + @DisableCompatChanges(OVERRIDABLE_COMPONENT_CALLBACKS) @Test public void testRegisterComponentCallbacksWithoutBaseContextBeforeT() { final ContextWrapper wrapper = new TestContextWrapper(null /* base */); diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS index 0b945895ad7f..a69c6ffef8d8 100644 --- a/core/tests/coretests/src/android/content/OWNERS +++ b/core/tests/coretests/src/android/content/OWNERS @@ -1,6 +1,7 @@ per-file AssetTest.java = file:/core/java/android/content/res/OWNERS -per-file ContextTest.java = file:/services/core/java/com/android/server/wm/OWNERS +per-file Context* = file:/services/core/java/com/android/server/wm/OWNERS +per-file Context* = charlesccchen@google.com per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS -per-file ComponentCallbacksControllerTest = file:/services/core/java/com/android/server/wm/OWNERS -per-file ComponentCallbacksControllerTest = charlesccchen@google.com +per-file *ComponentCallbacks* = file:/services/core/java/com/android/server/wm/OWNERS +per-file *ComponentCallbacks* = charlesccchen@google.com diff --git a/core/tests/coretests/src/android/content/TestComponentCallbacks2.java b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java index 6ae7fc44c4ce..5c8787aeda9c 100644 --- a/core/tests/coretests/src/android/content/TestComponentCallbacks2.java +++ b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java @@ -20,10 +20,10 @@ import android.content.res.Configuration; import androidx.annotation.NonNull; -class TestComponentCallbacks2 implements ComponentCallbacks2 { - android.content.res.Configuration mConfiguration; - boolean mLowMemoryCalled; - int mLevel; +public class TestComponentCallbacks2 implements ComponentCallbacks2 { + public Configuration mConfiguration = null; + public boolean mLowMemoryCalled = false; + public int mLevel = 0; @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { diff --git a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java index 7af96c3f9c6b..36072c3523ce 100644 --- a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java @@ -104,6 +104,17 @@ public class AtomicFormulaTest { } @Test + public void testValidAtomicFormula_stringValue_appCertificateLineageIsNotAutoHashed() { + String appCert = "cert"; + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE_LINEAGE, appCert); + + assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.APP_CERTIFICATE_LINEAGE); + assertThat(stringAtomicFormula.getValue()).matches(appCert); + assertThat(stringAtomicFormula.getIsHashedValue()).isTrue(); + } + + @Test public void testValidAtomicFormula_stringValue_installerCertificateIsNotAutoHashed() { String installerCert = "cert"; StringAtomicFormula stringAtomicFormula = @@ -285,6 +296,34 @@ public class AtomicFormulaTest { } @Test + public void testFormulaMatches_string_multipleAppCertificateLineage_true() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE_LINEAGE, "cert", /* isHashedValue= */ true); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder() + .setPackageName("com.test.app") + .setAppCertificateLineage(Arrays.asList("test-cert", "cert")) + .build(); + + assertThat(stringAtomicFormula.matches(appInstallMetadata)).isTrue(); + } + + @Test + public void testFormulaMatches_string_multipleAppCertificateLineage_false() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE_LINEAGE, "cert", /* isHashedValue= */ true); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder() + .setPackageName("com.test.app") + .setAppCertificateLineage(Arrays.asList("test-cert", "another-cert")) + .build(); + + assertThat(stringAtomicFormula.matches(appInstallMetadata)).isFalse(); + } + + @Test public void testFormulaMatches_string_multipleInstallerCertificates_true() { StringAtomicFormula stringAtomicFormula = new StringAtomicFormula( @@ -324,6 +363,15 @@ public class AtomicFormulaTest { } @Test + public void testIsAppCertificateLineageFormula_string_true() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE_LINEAGE, "cert", /* isHashedValue= */false); + + assertThat(stringAtomicFormula.isAppCertificateLineageFormula()).isTrue(); + } + + @Test public void testIsAppCertificateFormula_string_false() { StringAtomicFormula stringAtomicFormula = new StringAtomicFormula( @@ -334,6 +382,16 @@ public class AtomicFormulaTest { } @Test + public void testIsAppCertificateLineageFormula_string_false() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.PACKAGE_NAME, "com.test.app", /* isHashedValue= */ + false); + + assertThat(stringAtomicFormula.isAppCertificateLineageFormula()).isFalse(); + } + + @Test public void testIsInstallerFormula_string_false() { StringAtomicFormula stringAtomicFormula = new StringAtomicFormula( @@ -442,6 +500,15 @@ public class AtomicFormulaTest { } @Test + public void testIsAppCertificateLineageFormula_long_false() { + LongAtomicFormula longAtomicFormula = + new AtomicFormula.LongAtomicFormula( + AtomicFormula.VERSION_CODE, AtomicFormula.GTE, 1); + + assertThat(longAtomicFormula.isAppCertificateLineageFormula()).isFalse(); + } + + @Test public void testIsInstallerFormula_long_false() { LongAtomicFormula longAtomicFormula = new LongAtomicFormula( @@ -479,6 +546,14 @@ public class AtomicFormulaTest { } @Test + public void testIsAppCertificateLineageFormula_bool_false() { + BooleanAtomicFormula boolFormula = + new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true); + + assertThat(boolFormula.isAppCertificateLineageFormula()).isFalse(); + } + + @Test public void testIsInstallerFormula_bool_false() { BooleanAtomicFormula boolFormula = new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true); @@ -491,6 +566,7 @@ public class AtomicFormulaTest { return new AppInstallMetadata.Builder() .setPackageName("abc") .setAppCertificates(Collections.singletonList("abc")) + .setAppCertificateLineage(Collections.singletonList("abc")) .setInstallerCertificates(Collections.singletonList("abc")) .setInstallerName("abc") .setVersionCode(-1) diff --git a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java index 593e70e6b257..a202efbeb276 100644 --- a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java @@ -249,6 +249,28 @@ public class CompoundFormulaTest { } @Test + public void testIsAppCertificateLineageFormula_false() { + CompoundFormula compoundFormula = + new CompoundFormula( + CompoundFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)); + + assertThat(compoundFormula.isAppCertificateLineageFormula()).isFalse(); + } + + @Test + public void testIsAppCertificateLineageFormula_true() { + AtomicFormula appCertFormula = + new AtomicFormula.StringAtomicFormula(AtomicFormula.APP_CERTIFICATE_LINEAGE, + "app.cert", /* isHashed= */false); + CompoundFormula compoundFormula = + new CompoundFormula( + CompoundFormula.AND, + Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2, appCertFormula)); + + assertThat(compoundFormula.isAppCertificateLineageFormula()).isTrue(); + } + + @Test public void testIsInstallerFormula_false() { CompoundFormula compoundFormula = new CompoundFormula( @@ -288,6 +310,7 @@ public class CompoundFormulaTest { return new AppInstallMetadata.Builder() .setPackageName("abc") .setAppCertificates(Collections.singletonList("abc")) + .setAppCertificateLineage(Collections.singletonList("abc")) .setInstallerCertificates(Collections.singletonList("abc")) .setInstallerName("abc") .setVersionCode(-1) diff --git a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java index 70712e40fd5d..54acb1e50318 100644 --- a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java @@ -32,8 +32,8 @@ import java.util.Collections; @RunWith(JUnit4.class) public class InstallerAllowedByManifestFormulaTest { - private static final InstallerAllowedByManifestFormula - FORMULA = new InstallerAllowedByManifestFormula(); + private static final InstallerAllowedByManifestFormula FORMULA = + new InstallerAllowedByManifestFormula(); @Test public void testFormulaMatches_installerAndCertBothInManifest() { @@ -115,6 +115,7 @@ public class InstallerAllowedByManifestFormulaTest { return new AppInstallMetadata.Builder() .setPackageName("abc") .setAppCertificates(Collections.emptyList()) + .setAppCertificateLineage(Collections.emptyList()) .setInstallerCertificates(Collections.emptyList()) .setInstallerName("abc") .setVersionCode(-1) diff --git a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java index 7e4c138ccd3c..9058a711ae03 100644 --- a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java @@ -54,6 +54,20 @@ public class IntegrityFormulaTest { } @Test + public void createEqualsFormula_appCertificateLineage() { + String appCertificate = "com.test.app"; + IntegrityFormula formula = + IntegrityFormula.Application.certificateLineageContains(appCertificate); + + AtomicFormula.StringAtomicFormula stringAtomicFormula = + (AtomicFormula.StringAtomicFormula) formula; + + assertThat(stringAtomicFormula.getKey()).isEqualTo(AtomicFormula.APP_CERTIFICATE_LINEAGE); + assertThat(stringAtomicFormula.getValue()).matches(appCertificate); + assertThat(stringAtomicFormula.getIsHashedValue()).isTrue(); + } + + @Test public void createEqualsFormula_installerName() { String installerName = "com.test.app"; IntegrityFormula formula = IntegrityFormula.Installer.packageNameEquals(installerName); @@ -138,8 +152,10 @@ public class IntegrityFormulaTest { IntegrityFormula formula1 = IntegrityFormula.Application.packageNameEquals(packageName); IntegrityFormula formula2 = IntegrityFormula.Application.certificatesContain(certificateName); + IntegrityFormula formula3 = + IntegrityFormula.Application.certificateLineageContains(certificateName); - IntegrityFormula compoundFormula = IntegrityFormula.all(formula1, formula2); + IntegrityFormula compoundFormula = IntegrityFormula.all(formula1, formula2, formula3); assertThat(compoundFormula.getTag()).isEqualTo(COMPOUND_FORMULA_TAG); } @@ -151,8 +167,10 @@ public class IntegrityFormulaTest { IntegrityFormula formula1 = IntegrityFormula.Application.packageNameEquals(packageName); IntegrityFormula formula2 = IntegrityFormula.Application.certificatesContain(certificateName); + IntegrityFormula formula3 = + IntegrityFormula.Application.certificateLineageContains(certificateName); - IntegrityFormula compoundFormula = IntegrityFormula.any(formula1, formula2); + IntegrityFormula compoundFormula = IntegrityFormula.any(formula1, formula2, formula3); assertThat(compoundFormula.getTag()).isEqualTo(COMPOUND_FORMULA_TAG); } diff --git a/core/tests/coretests/src/android/util/RotationUtilsTest.java b/core/tests/coretests/src/android/util/RotationUtilsTest.java index 5dbe03e8b42b..826eb3070c7e 100644 --- a/core/tests/coretests/src/android/util/RotationUtilsTest.java +++ b/core/tests/coretests/src/android/util/RotationUtilsTest.java @@ -17,12 +17,14 @@ package android.util; import static android.util.RotationUtils.rotateBounds; +import static android.util.RotationUtils.rotatePoint; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static org.junit.Assert.assertEquals; +import android.graphics.Point; import android.graphics.Rect; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -58,4 +60,23 @@ public class RotationUtilsTest { rotateBounds(testResult, testParent, ROTATION_270); assertEquals(new Rect(520, 40, 580, 120), testResult); } + + @Test + public void testRotatePoint() { + int parentW = 1000; + int parentH = 600; + Point testPt = new Point(60, 40); + + Point testResult = new Point(testPt); + rotatePoint(testResult, ROTATION_90, parentW, parentH); + assertEquals(new Point(40, 940), testResult); + + testResult.set(testPt.x, testPt.y); + rotatePoint(testResult, ROTATION_180, parentW, parentH); + assertEquals(new Point(940, 560), testResult); + + testResult.set(testPt.x, testPt.y); + rotatePoint(testResult, ROTATION_270, parentW, parentH); + assertEquals(new Point(560, 60), testResult); + } } diff --git a/core/tests/coretests/src/android/view/stylus/HandwritableViewInfoTest.java b/core/tests/coretests/src/android/view/stylus/HandwritableViewInfoTest.java new file mode 100644 index 000000000000..4bea54ba8303 --- /dev/null +++ b/core/tests/coretests/src/android/view/stylus/HandwritableViewInfoTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.stylus; + +import static android.view.stylus.HandwritingTestUtil.createView; + +import static com.google.common.truth.Truth.assertThat; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.view.HandwritingInitiator; +import android.view.View; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class HandwritableViewInfoTest { + + @Test + public void constructorTest() { + final Rect rect = new Rect(1, 2, 3, 4); + final View view = createView(rect); + final HandwritingInitiator.HandwritableViewInfo handwritableViewInfo = + new HandwritingInitiator.HandwritableViewInfo(view); + + assertThat(handwritableViewInfo.getView()).isEqualTo(view); + // It's labeled dirty by default. + assertTrue(handwritableViewInfo.mIsDirty); + } + + @Test + public void update() { + final Rect rect = new Rect(1, 2, 3, 4); + final View view = createView(rect); + final HandwritingInitiator.HandwritableViewInfo handwritableViewInfo = + new HandwritingInitiator.HandwritableViewInfo(view); + + assertThat(handwritableViewInfo.getView()).isEqualTo(view); + + final boolean isViewInfoValid = handwritableViewInfo.update(); + + assertTrue(isViewInfoValid); + assertThat(handwritableViewInfo.getHandwritingArea()).isEqualTo(rect); + assertFalse(handwritableViewInfo.mIsDirty); + } + + @Test + public void update_viewDisableAutoHandwriting() { + final Rect rect = new Rect(1, 2, 3, 4); + final View view = HandwritingTestUtil.createView(rect, false /* autoHandwritingEnabled */); + final HandwritingInitiator.HandwritableViewInfo handwritableViewInfo = + new HandwritingInitiator.HandwritableViewInfo(view); + + assertThat(handwritableViewInfo.getView()).isEqualTo(view); + + final boolean isViewInfoValid = handwritableViewInfo.update(); + + // Return false because the view disabled autoHandwriting. + assertFalse(isViewInfoValid); + // The view disabled the autoHandwriting, and it won't update the handwriting area. + assertThat(handwritableViewInfo.getHandwritingArea()).isNull(); + } + +} diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingAreaTrackerTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingAreaTrackerTest.java new file mode 100644 index 000000000000..db4707a07435 --- /dev/null +++ b/core/tests/coretests/src/android/view/stylus/HandwritingAreaTrackerTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.stylus; + +import static android.view.stylus.HandwritingTestUtil.createView; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Instrumentation; +import android.content.Context; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.view.HandwritingInitiator; +import android.view.View; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + + +/** + * Tests for {@link HandwritingInitiator.HandwritingAreaTracker} + * + * Build/Install/Run: + * atest FrameworksCoreTests:android.view.stylus.HandwritingAreaTrackerTest + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class HandwritingAreaTrackerTest { + HandwritingInitiator.HandwritingAreaTracker mHandwritingAreaTracker; + Context mContext; + + @Before + public void setup() { + final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mContext = mInstrumentation.getTargetContext(); + mHandwritingAreaTracker = new HandwritingInitiator.HandwritingAreaTracker(); + } + + @Test + public void updateHandwritingAreaForView_singleView() { + Rect rect = new Rect(0, 0, 100, 100); + View view = createView(rect); + mHandwritingAreaTracker.updateHandwritingAreaForView(view); + + List<HandwritingInitiator.HandwritableViewInfo> viewInfos = + mHandwritingAreaTracker.computeViewInfos(); + + assertThat(viewInfos.size()).isEqualTo(1); + assertThat(viewInfos.get(0).getHandwritingArea()).isEqualTo(rect); + assertThat(viewInfos.get(0).getView()).isEqualTo(view); + } + + @Test + public void updateHandwritingAreaForView_multipleViews() { + Rect rect1 = new Rect(0, 0, 100, 100); + Rect rect2 = new Rect(100, 100, 200, 200); + + View view1 = createView(rect1); + View view2 = createView(rect2); + mHandwritingAreaTracker.updateHandwritingAreaForView(view1); + mHandwritingAreaTracker.updateHandwritingAreaForView(view2); + + List<HandwritingInitiator.HandwritableViewInfo> viewInfos = + mHandwritingAreaTracker.computeViewInfos(); + + assertThat(viewInfos.size()).isEqualTo(2); + assertThat(viewInfos.get(0).getView()).isEqualTo(view1); + assertThat(viewInfos.get(0).getHandwritingArea()).isEqualTo(rect1); + + assertThat(viewInfos.get(1).getView()).isEqualTo(view2); + assertThat(viewInfos.get(1).getHandwritingArea()).isEqualTo(rect2); + } + + @Test + public void updateHandwritingAreaForView_afterDisableAutoHandwriting() { + Rect rect1 = new Rect(0, 0, 100, 100); + Rect rect2 = new Rect(100, 100, 200, 200); + + View view1 = createView(rect1); + View view2 = createView(rect2); + mHandwritingAreaTracker.updateHandwritingAreaForView(view1); + mHandwritingAreaTracker.updateHandwritingAreaForView(view2); + + // There should be 2 views tracked. + assertThat(mHandwritingAreaTracker.computeViewInfos().size()).isEqualTo(2); + + // Disable autoHandwriting for view1 and update handwriting area. + view1.setAutoHandwritingEnabled(false); + mHandwritingAreaTracker.updateHandwritingAreaForView(view1); + + List<HandwritingInitiator.HandwritableViewInfo> viewInfos = + mHandwritingAreaTracker.computeViewInfos(); + // The view1 has disabled the autoHandwriting, it's not tracked anymore. + assertThat(viewInfos.size()).isEqualTo(1); + + // view2 is still tracked. + assertThat(viewInfos.get(0).getView()).isEqualTo(view2); + assertThat(viewInfos.get(0).getHandwritingArea()).isEqualTo(rect2); + } + + @Test + public void updateHandwritingAreaForView_removesInactiveView() { + Rect rect1 = new Rect(0, 0, 100, 100); + Rect rect2 = new Rect(100, 100, 200, 200); + + View view1 = createView(rect1); + View view2 = createView(rect2); + mHandwritingAreaTracker.updateHandwritingAreaForView(view1); + mHandwritingAreaTracker.updateHandwritingAreaForView(view2); + + // There should be 2 viewInfos tracked. + assertThat(mHandwritingAreaTracker.computeViewInfos().size()).isEqualTo(2); + + // Disable autoHandwriting for view1, but update handwriting area for view2. + view1.setAutoHandwritingEnabled(false); + mHandwritingAreaTracker.updateHandwritingAreaForView(view2); + + List<HandwritingInitiator.HandwritableViewInfo> viewInfos = + mHandwritingAreaTracker.computeViewInfos(); + // The view1 has disabled the autoHandwriting, it's not tracked anymore. + assertThat(viewInfos.size()).isEqualTo(1); + + // view2 is still tracked. + assertThat(viewInfos.get(0).getView()).isEqualTo(view2); + assertThat(viewInfos.get(0).getHandwritingArea()).isEqualTo(rect2); + } +} diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java index e11fe17bac9f..1ae9649dfe06 100644 --- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java @@ -19,6 +19,7 @@ package android.view.stylus; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; +import static android.view.stylus.HandwritingTestUtil.createView; import static com.google.common.truth.Truth.assertThat; @@ -38,7 +39,6 @@ import android.view.InputDevice; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; -import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -78,7 +78,8 @@ public class HandwritingInitiatorTest { mHandwritingInitiator = spy(new HandwritingInitiator(viewConfiguration, inputMethodManager)); - mTestView = createMockView(sHwArea, true); + mTestView = createView(sHwArea, true); + mHandwritingInitiator.updateHandwritingAreasForView(mTestView); } @Test @@ -195,8 +196,25 @@ public class HandwritingInitiatorTest { } @Test + public void onTouchEvent_focusView_stylusMoveOnce_withinHWArea() { + final int x1 = (sHwArea.left + sHwArea.right) / 2; + final int y1 = (sHwArea.top + sHwArea.bottom) / 2; + MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent1); + + final int x2 = x1 + TOUCH_SLOP * 2; + final int y2 = y1; + + MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent2); + + // HandwritingInitiator will request focus for the registered view. + verify(mTestView, times(1)).requestFocus(); + } + + @Test public void autoHandwriting_whenDisabled_wontStartHW() { - View mockView = createMockView(sHwArea, false); + View mockView = createView(sHwArea, false); mHandwritingInitiator.onInputConnectionCreated(mockView); final int x1 = (sHwArea.left + sHwArea.right) / 2; final int y1 = (sHwArea.top + sHwArea.bottom) / 2; @@ -273,25 +291,4 @@ public class HandwritingInitiatorTest { 1 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_TOUCHSCREEN, 0 /* flags */); } - - private View createMockView(Rect viewBound, boolean autoHandwritingEnabled) { - // mock a parent so that HandwritingInitiator can get - ViewGroup parent = new ViewGroup(mContext) { - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - // We don't layout this view. - } - @Override - public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) { - r.set(viewBound); - return true; - } - }; - - View mockView = mock(View.class); - when(mockView.isAttachedToWindow()).thenReturn(true); - when(mockView.isAutoHandwritingEnabled()).thenReturn(autoHandwritingEnabled); - parent.addView(mockView); - return mockView; - } } diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java b/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java new file mode 100644 index 000000000000..6daf880fa2ab --- /dev/null +++ b/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.stylus; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.Instrumentation; +import android.content.Context; +import android.graphics.Rect; +import android.view.View; +import android.view.ViewGroup; + +import androidx.test.platform.app.InstrumentationRegistry; + +public class HandwritingTestUtil { + public static View createView(Rect handwritingArea) { + return createView(handwritingArea, true); + } + + public static View createView(Rect handwritingArea, boolean autoHandwritingEnabled) { + final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + final Context context = instrumentation.getTargetContext(); + // mock a parent so that HandwritingInitiator can get + final ViewGroup parent = new ViewGroup(context) { + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + // We don't layout this view. + } + @Override + public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) { + r.set(handwritingArea); + return true; + } + }; + + View view = spy(new View(context)); + when(view.isAttachedToWindow()).thenReturn(true); + when(view.isAggregatedVisible()).thenReturn(true); + when(view.getHandwritingArea()).thenReturn(handwritingArea); + view.setAutoHandwritingEnabled(autoHandwritingEnabled); + parent.addView(view); + return view; + } +} diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 2d5f8335c8ff..ac5daf09c6f9 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -508,6 +508,8 @@ applications that come with the platform <permission name="android.permission.MANAGE_APP_HIBERNATION"/> <!-- Permission required for CTS test - ResourceObserverNativeTest --> <permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" /> + <!-- Permission required for CTS test - MediaCodecResourceTest --> + <permission name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" /> <!-- Permission required for CTS test - CtsAlarmManagerTestCases --> <permission name="android.permission.SCHEDULE_PRIORITIZED_ALARM" /> <!-- Permission required for CTS test - SystemMediaRouter2Test --> @@ -547,17 +549,6 @@ applications that come with the platform <permission name="android.permission.INTERACT_ACROSS_USERS"/> </privapp-permissions> - <privapp-permissions package="com.android.traceur"> - <!-- Permissions required to receive BUGREPORT_STARTED intent --> - <permission name="android.permission.DUMP"/> - <!-- Permissions required to start/stop tracing --> - <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/> - <!-- Permissions required for quick settings tile --> - <permission name="android.permission.STATUS_BAR"/> - <!-- Permissions required to query Betterbug --> - <permission name="android.permission.QUERY_ALL_PACKAGES"/> - </privapp-permissions> - <privapp-permissions package="com.android.tv"> <permission name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/> <permission name="android.permission.DVB_DEVICE"/> diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java index dde757b4eb01..3ec5b9cc7dae 100644 --- a/graphics/java/android/graphics/PixelFormat.java +++ b/graphics/java/android/graphics/PixelFormat.java @@ -29,7 +29,7 @@ public class PixelFormat { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({RGBA_8888, RGBX_8888, RGBA_F16, RGBA_1010102, RGB_888, RGB_565}) + @IntDef({RGBA_8888, RGBX_8888, RGBA_F16, RGBA_1010102, RGB_888, RGB_565, R_8}) public @interface Format { } // NOTE: these constants must match the values from graphics/common/x.x/types.hal @@ -93,6 +93,9 @@ public class PixelFormat { /** @hide */ public static final int HSV_888 = 0x37; + /** @hide */ + public static final int R_8 = 0x38; + /** * @deprecated use {@link android.graphics.ImageFormat#JPEG * ImageFormat.JPEG} instead. @@ -142,6 +145,10 @@ public class PixelFormat { info.bitsPerPixel = 64; info.bytesPerPixel = 8; break; + case R_8: + info.bitsPerPixel = 8; + info.bytesPerPixel = 1; + break; default: throw new IllegalArgumentException("unknown pixel format " + format); } @@ -235,6 +242,8 @@ public class PixelFormat { return "HSV_888"; case JPEG: return "JPEG"; + case R_8: + return "R_8"; default: return Integer.toString(format); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 8f368c2bee22..d35ecbde529b 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -924,6 +924,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Checks if an activity is embedded and its presentation is customized by a * {@link android.window.TaskFragmentOrganizer} to only occupy a portion of Task bounds. */ + @Override public boolean isActivityEmbedded(@NonNull Activity activity) { return mPresenter.isActivityEmbedded(activity.getActivityToken()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index 9a6df23ca971..4c505f6583fc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -17,6 +17,7 @@ package com.android.wm.shell.back; import android.view.MotionEvent; +import android.window.BackEvent; import com.android.wm.shell.common.annotations.ExternalThread; @@ -29,7 +30,7 @@ public interface BackAnimation { /** * Called when a {@link MotionEvent} is generated by a back gesture. */ - void onBackMotion(MotionEvent event); + void onBackMotion(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge); /** * Sets whether the back gesture is past the trigger threshold or not. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index a5140c3aafff..32ac43da951c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -36,6 +36,7 @@ import android.os.SystemProperties; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceControl; +import android.window.BackEvent; import android.window.BackNavigationInfo; import android.window.IOnBackInvokedCallback; @@ -127,8 +128,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } @Override - public void onBackMotion(MotionEvent event) { - mShellExecutor.execute(() -> onMotionEvent(event)); + public void onBackMotion(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) { + mShellExecutor.execute(() -> onMotionEvent(event, swipeEdge)); } @Override @@ -183,12 +184,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * Called when a new motion event needs to be transferred to this * {@link BackAnimationController} */ - public void onMotionEvent(MotionEvent event) { + public void onMotionEvent(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) { int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { initAnimation(event); } else if (action == MotionEvent.ACTION_MOVE) { - onMove(event); + onMove(event, swipeEdge); } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { onGestureFinished(); } @@ -264,7 +265,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mTransaction.setVisibility(screenshotSurface, true); } - private void onMove(MotionEvent event) { + private void onMove(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) { if (!mBackGestureStarted || mBackNavigationInfo == null) { return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 6ed1ba9f561f..6ffcf101693e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -618,7 +618,8 @@ public class BubbleController { } /** Contains information to help position things on the screen. */ - BubblePositioner getPositioner() { + @VisibleForTesting + public BubblePositioner getPositioner() { return mBubblePositioner; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 9ae67a9bf227..da8308ee7bab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -60,6 +60,7 @@ import android.widget.LinearLayout; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.wm.shell.R; import com.android.wm.shell.TaskView; @@ -418,8 +419,9 @@ public class BubbleExpandedView extends LinearLayout { mPointerView.setBackground(mCurrentPointer); } - private String getBubbleKey() { - return mBubble != null ? mBubble.getKey() : "null"; + @VisibleForTesting + public String getBubbleKey() { + return mBubble != null ? mBubble.getKey() : mIsOverflow ? BubbleOverflow.KEY : null; } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt index dd751d24e770..eb7929b8ca54 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt @@ -54,6 +54,7 @@ class BubbleOverflow( /** Call before use and again if cleanUpExpandedState was called. */ fun initialize(controller: BubbleController) { + createExpandedView() getExpandedView()?.initialize(controller, controller.stackView, true /* isOverflow */) } @@ -123,13 +124,15 @@ class BubbleOverflow( overflowBtn?.updateDotVisibility(true /* animate */) } + fun createExpandedView(): BubbleExpandedView? { + expandedView = inflater.inflate(R.layout.bubble_expanded_view, + null /* root */, false /* attachToRoot */) as BubbleExpandedView + expandedView?.applyThemeAttrs() + updateResources() + return expandedView + } + override fun getExpandedView(): BubbleExpandedView? { - if (expandedView == null) { - expandedView = inflater.inflate(R.layout.bubble_expanded_view, - null /* root */, false /* attachToRoot */) as BubbleExpandedView - expandedView?.applyThemeAttrs() - updateResources() - } return expandedView } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index c2eb08ca09dd..9219baa06157 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -787,7 +787,7 @@ public class BubbleStackView extends FrameLayout ta.recycle(); final Runnable onBubbleAnimatedOut = () -> { - if (getBubbleCount() == 0 && !mBubbleData.isShowingOverflow()) { + if (getBubbleCount() == 0) { mBubbleController.onAllBubblesAnimatedOut(); } }; @@ -1651,7 +1651,6 @@ public class BubbleStackView extends FrameLayout } else { bubble.cleanupViews(); } - updatePointerPosition(false /* forIme */); updateExpandedView(); logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED); return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java index 1039e2ac4fd9..51067a45381c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java @@ -191,6 +191,19 @@ public class SystemWindows { return null; } + /** + * Gets a token associated with the view that can be used to grant the view focus. + */ + public IBinder getFocusGrantToken(View view) { + SurfaceControlViewHost root = mViewRoots.get(view); + if (root == null) { + Slog.e(TAG, "Couldn't get focus grant token since view does not exist in " + + "SystemWindow:" + view); + return null; + } + return root.getFocusGrantToken(); + } + private class PerDisplay { final int mDisplayId; private final SparseArray<SysUiWindowManager> mWwms = new SparseArray<>(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java index bdf9d513f298..7014fcc0e874 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java @@ -140,11 +140,8 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana /** * Whether the layout is eligible to be shown according to the internal state of the subclass. - * Returns true by default if subclass doesn't override this method. */ - protected boolean eligibleToShowLayout() { - return true; - } + protected abstract boolean eligibleToShowLayout(); @Override public void setConfiguration(Configuration configuration) { @@ -214,8 +211,7 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana boolean layoutDirectionUpdated = mTaskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection(); if (boundsUpdated || layoutDirectionUpdated) { - // Reposition the UI surfaces. - updateSurfacePosition(); + updateSurface(); } if (layout != null && layoutDirectionUpdated) { @@ -226,7 +222,6 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana return true; } - /** * Updates the visibility of the layout. * @@ -253,8 +248,7 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana displayLayout.getStableBounds(curStableBounds); mDisplayLayout = displayLayout; if (!prevStableBounds.equals(curStableBounds)) { - // Stable bounds changed, update UI surface positions. - updateSurfacePosition(); + updateSurface(); mStableBounds.set(curStableBounds); } } @@ -304,6 +298,14 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana } /** + * Updates the surface following a change in the task bounds, display layout stable bounds, + * or the layout direction. + */ + protected void updateSurface() { + updateSurfacePosition(); + } + + /** * Updates the position of the surface with respect to the task bounds and display layout * stable bounds. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java index b22b829eebd8..bc1d19bfcec2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java @@ -36,8 +36,6 @@ class LetterboxEduDialogLayout extends FrameLayout { // The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque). // 204 is simply 255 * 0.8. static final int BACKGROUND_DIM_ALPHA = 204; - - private LetterboxEduWindowManager mWindowManager; private View mDialogContainer; private Drawable mBackgroundDim; @@ -58,10 +56,6 @@ class LetterboxEduDialogLayout extends FrameLayout { super(context, attrs, defStyleAttr, defStyleRes); } - void inject(LetterboxEduWindowManager windowManager) { - mWindowManager = windowManager; - } - View getDialogContainer() { return mDialogContainer; } @@ -70,13 +64,6 @@ class LetterboxEduDialogLayout extends FrameLayout { return mBackgroundDim; } - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - // Need to relayout after visibility changes since they affect size. - mWindowManager.relayout(); - } - /** * Register a callback for the dismiss button and background dim. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java index d5aefb1a7e5c..074610bfce49 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java @@ -16,11 +16,14 @@ package com.android.wm.shell.compatui.letterboxedu; +import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING; + import android.annotation.Nullable; import android.app.TaskInfo; import android.content.Context; import android.content.SharedPreferences; import android.graphics.Rect; +import android.provider.Settings; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; @@ -93,9 +96,12 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { @Override protected boolean eligibleToShowLayout() { - // If the layout isn't null then it was previously showing, and we shouldn't check if the - // user has seen the letterbox education before. - return mEligibleForLetterboxEducation && (mLayout != null + // - If taskbar education is showing, the letterbox education shouldn't be shown for the + // given task until the taskbar education is dismissed and the compat info changes (then + // the controller will create a new instance of this class since this one isn't eligible). + // - If the layout isn't null then it was previously showing, and we shouldn't check if the + // user has seen the letterbox education before. + return mEligibleForLetterboxEducation && !isTaskbarEduShowing() && (mLayout != null || !getHasSeenLetterboxEducation()); } @@ -103,7 +109,6 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { protected View createLayout() { setSeenLetterboxEducation(); mLayout = inflateLayout(); - mLayout.inject(this); mAnimationController.startEnterAnimation(mLayout, /* endCallback= */ this::setDismissOnClickListener); @@ -145,15 +150,22 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { } @Override + protected void updateSurface() { + // We need to relayout because the layout dimensions depend on the task bounds. + relayout(); + } + + @Override protected void updateSurfacePosition(Rect taskBounds, Rect stableBounds) { - updateSurfacePosition(/* positionX= */ taskBounds.left, /* positionY= */ taskBounds.top); + // Nothing to do, since the position of the surface is fixed to the top left corner (0,0) + // of the task (parent surface), which is the default position of a surface. } @Override protected WindowManager.LayoutParams getWindowLayoutParams() { final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds(); - return getWindowLayoutParams(/* width= */ taskBounds.right - taskBounds.left, - /* height= */ taskBounds.bottom - taskBounds.top); + return getWindowLayoutParams(/* width= */ taskBounds.width(), /* height= */ + taskBounds.height()); } private boolean getHasSeenLetterboxEducation() { @@ -167,4 +179,9 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { private String getPrefKey() { return String.valueOf(mContext.getUserId()); } + + private boolean isTaskbarEduShowing() { + return Settings.Secure.getInt(mContext.getContentResolver(), + LAUNCHER_TASKBAR_EDUCATION_SHOWING, /* def= */ 0) == 1; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index 5ebdceba135b..e8bae0f94bf0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -45,10 +45,12 @@ import android.app.WindowConfiguration; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ClipDescription; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.LauncherApps; +import android.content.pm.ResolveInfo; import android.graphics.Insets; import android.graphics.Rect; import android.os.Bundle; @@ -62,9 +64,11 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.InstanceId; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; import java.lang.annotation.Retention; @@ -106,12 +110,19 @@ public class DragAndDropPolicy { */ void start(DisplayLayout displayLayout, ClipData data, InstanceId loggerSessionId) { mLoggerSessionId = loggerSessionId; - mSession = new DragSession(mContext, mActivityTaskManager, displayLayout, data); + mSession = new DragSession(mActivityTaskManager, displayLayout, data); // TODO(b/169894807): Also update the session data with task stack changes mSession.update(); } /** + * Returns the last running task. + */ + ActivityManager.RunningTaskInfo getLatestRunningTask() { + return mSession.runningTaskInfo; + } + + /** * Returns the target's regions based on the current state of the device and display. */ @NonNull @@ -248,32 +259,68 @@ public class DragAndDropPolicy { final UserHandle user = intent.getParcelableExtra(EXTRA_USER); mStarter.startShortcut(packageName, id, position, opts, user); } else { - mStarter.startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT), - null, position, opts); + final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT); + mStarter.startIntent(launchIntent, getStartIntentFillInIntent(launchIntent, position), + position, opts); + } + } + + /** + * Returns the fill-in intent to use when starting an app from a drop. + */ + @VisibleForTesting + Intent getStartIntentFillInIntent(PendingIntent launchIntent, @SplitPosition int position) { + // Get the drag app + final List<ResolveInfo> infos = launchIntent.queryIntentComponents(0 /* flags */); + final ComponentName dragIntentActivity = !infos.isEmpty() + ? infos.get(0).activityInfo.getComponentName() + : null; + + // Get the current app (either fullscreen or the remaining app post-drop if in splitscreen) + final boolean inSplitScreen = mSplitScreen != null + && mSplitScreen.isSplitScreenVisible(); + final ComponentName currentActivity; + if (!inSplitScreen) { + currentActivity = mSession.runningTaskInfo != null + ? mSession.runningTaskInfo.baseActivity + : null; + } else { + final int nonReplacedSplitPosition = position == SPLIT_POSITION_TOP_OR_LEFT + ? SPLIT_POSITION_BOTTOM_OR_RIGHT + : SPLIT_POSITION_TOP_OR_LEFT; + ActivityManager.RunningTaskInfo nonReplacedTaskInfo = + mSplitScreen.getTaskInfo(nonReplacedSplitPosition); + currentActivity = nonReplacedTaskInfo.baseActivity; + } + + if (currentActivity.equals(dragIntentActivity)) { + // Only apply MULTIPLE_TASK if we are dragging the same activity + final Intent fillInIntent = new Intent(); + fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Adding MULTIPLE_TASK"); + return fillInIntent; } + return null; } /** * Per-drag session data. */ private static class DragSession { - private final Context mContext; private final ActivityTaskManager mActivityTaskManager; private final ClipData mInitialDragData; final DisplayLayout displayLayout; Intent dragData; - int runningTaskId; + ActivityManager.RunningTaskInfo runningTaskInfo; @WindowConfiguration.WindowingMode int runningTaskWinMode = WINDOWING_MODE_UNDEFINED; @WindowConfiguration.ActivityType int runningTaskActType = ACTIVITY_TYPE_STANDARD; - boolean runningTaskIsResizeable; boolean dragItemSupportsSplitscreen; - DragSession(Context context, ActivityTaskManager activityTaskManager, + DragSession(ActivityTaskManager activityTaskManager, DisplayLayout dispLayout, ClipData data) { - mContext = context; mActivityTaskManager = activityTaskManager; mInitialDragData = data; displayLayout = dispLayout; @@ -287,10 +334,9 @@ public class DragAndDropPolicy { mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */); if (!tasks.isEmpty()) { final ActivityManager.RunningTaskInfo task = tasks.get(0); + runningTaskInfo = task; runningTaskWinMode = task.getWindowingMode(); runningTaskActType = task.getActivityType(); - runningTaskId = task.taskId; - runningTaskIsResizeable = task.isResizeable; } final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index 7307ba30fd67..d395f956a41c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -26,7 +26,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.SuppressLint; import android.app.ActivityManager; -import android.app.ActivityTaskManager; import android.app.StatusBarManager; import android.content.ClipData; import android.content.Context; @@ -35,7 +34,6 @@ import android.graphics.Color; import android.graphics.Insets; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.os.RemoteException; import android.view.DragEvent; import android.view.SurfaceControl; import android.view.WindowInsets; @@ -51,7 +49,6 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; import java.util.ArrayList; -import java.util.List; /** * Coordinates the visible drop targets for the current drag. @@ -166,17 +163,8 @@ public class DragLayout extends LinearLayout { boolean alreadyInSplit = mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible(); if (!alreadyInSplit) { - List<ActivityManager.RunningTaskInfo> tasks = null; - // Figure out the splashscreen info for the existing task. - try { - tasks = ActivityTaskManager.getService().getTasks(1, - false /* filterOnlyVisibleRecents */, - false /* keepIntentExtra */); - } catch (RemoteException e) { - // don't show an icon / will just use the defaults - } - if (tasks != null && !tasks.isEmpty()) { - ActivityManager.RunningTaskInfo taskInfo1 = tasks.get(0); + ActivityManager.RunningTaskInfo taskInfo1 = mPolicy.getLatestRunningTask(); + if (taskInfo1 != null) { Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo); int bgColor1 = getResizingBackgroundColor(taskInfo1); mDropZoneView1.setAppInfo(bgColor1, icon1); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 6ec8f5b924f8..71cff025a7a8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -28,7 +28,6 @@ import android.graphics.Rect; import android.graphics.RectF; import android.os.Debug; import android.os.Handler; -import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.util.Size; @@ -126,7 +125,6 @@ public class PhonePipMenuController implements PipMenuController { private int mMenuState; private PipMenuView mPipMenuView; - private IBinder mPipMenuInputToken; private ActionListener mMediaActionListener = new ActionListener() { @Override @@ -206,7 +204,6 @@ public class PhonePipMenuController implements PipMenuController { mApplier = null; mSystemWindows.removeView(mPipMenuView); mPipMenuView = null; - mPipMenuInputToken = null; } /** @@ -392,7 +389,6 @@ public class PhonePipMenuController implements PipMenuController { if (mApplier == null) { mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView); - mPipMenuInputToken = mPipMenuView.getViewRootImpl().getInputToken(); } return mApplier != null; @@ -539,7 +535,8 @@ public class PhonePipMenuController implements PipMenuController { try { WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */, - mPipMenuInputToken, menuState != MENU_STATE_NONE /* grantFocus */); + mSystemWindows.getFocusGrantToken(mPipMenuView), + menuState != MENU_STATE_NONE /* grantFocus */); } catch (RemoteException e) { Log.e(TAG, "Unable to update focus as menu appears/disappears", e); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index 32861b698daa..f838a0bece81 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -143,6 +143,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis mSystemWindows.addView(mPipMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), 0, SHELL_ROOT_LAYER_PIP); + mPipMenuView.setFocusGrantToken(mSystemWindows.getFocusGrantToken(mPipMenuView)); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java index 0141b6a1859e..84eae9e0febb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java @@ -28,6 +28,7 @@ import android.app.PendingIntent; import android.app.RemoteAction; import android.content.Context; import android.os.Handler; +import android.os.IBinder; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; @@ -69,6 +70,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { private final ImageView mArrowRight; private final ImageView mArrowDown; private final ImageView mArrowLeft; + private IBinder mFocusGrantToken = null; public TvPipMenuView(@NonNull Context context) { this(context, null); @@ -108,6 +110,10 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { mListener = listener; } + void setFocusGrantToken(IBinder token) { + mFocusGrantToken = token; + } + void show(boolean inMoveMode, int gravity) { if (DEBUG) Log.d(TAG, "show(), inMoveMode: " + inMoveMode); grantWindowFocus(true); @@ -162,7 +168,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { try { WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */, - getViewRootImpl().getInputToken(), grantFocus); + mFocusGrantToken, grantFocus); } catch (Exception e) { Log.e(TAG, "Unable to update focus", e); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 219530b46da4..029d073cb3d5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -163,6 +163,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, // and exit, since exit itself can trigger a number of changes that update the stages. private boolean mShouldUpdateRecents; private boolean mExitSplitScreenOnHide; + private boolean mIsDividerRemoteAnimating; /** The target stage to dismiss to when unlock after folded. */ @StageType @@ -389,6 +390,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, final IRemoteAnimationFinishedCallback finishedCallback) { + mIsDividerRemoteAnimating = true; RemoteAnimationTarget[] augmentedNonApps = new RemoteAnimationTarget[nonApps.length + 1]; for (int i = 0; i < nonApps.length; ++i) { @@ -400,6 +402,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, new IRemoteAnimationFinishedCallback.Stub() { @Override public void onAnimationFinished() throws RemoteException { + mIsDividerRemoteAnimating = false; mShouldUpdateRecents = true; mSyncQueue.queue(evictWct); mSyncQueue.runInSync(t -> setDividerVisibility(true, t)); @@ -423,6 +426,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onAnimationCancelled() { + mIsDividerRemoteAnimating = false; mShouldUpdateRecents = true; mSyncQueue.queue(evictWct); mSyncQueue.runInSync(t -> setDividerVisibility(true, t)); @@ -467,6 +471,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Using legacy transitions, so we can't use blast sync since it conflicts. mTaskOrganizer.applyTransaction(wct); + mSyncQueue.runInSync(t -> updateSurfaceBounds(mSplitLayout, t)); } /** Start an intent and a task ordered by {@code intentFirst}. */ @@ -1055,7 +1060,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void applyDividerVisibility(SurfaceControl.Transaction t) { final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash(); - if (dividerLeash == null) return; + if (mIsDividerRemoteAnimating || dividerLeash == null) return; if (mDividerVisible) { t.show(dividerLeash); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index ddf01a8c5ee9..5af15300d10b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -201,6 +201,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { "Display is changing, check if it should be seamless."); boolean checkedDisplayLayout = false; boolean hasTask = false; + boolean displayExplicitSeamless = false; for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); @@ -209,7 +210,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { // This container isn't rotating, so we can ignore it. if (change.getEndRotation() == change.getStartRotation()) continue; - if ((change.getFlags() & FLAG_IS_DISPLAY) != 0) { // In the presence of System Alert windows we can not seamlessly rotate. if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) { @@ -217,6 +217,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { " display has system alert windows, so not seamless."); return false; } + displayExplicitSeamless = + change.getRotationAnimation() == ROTATION_ANIMATION_SEAMLESS; } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, @@ -268,8 +270,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } - // ROTATION_ANIMATION_SEAMLESS can only be requested by task. - if (hasTask) { + // ROTATION_ANIMATION_SEAMLESS can only be requested by task or display. + if (hasTask || displayExplicitSeamless) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Rotation IS seamless."); return true; } @@ -417,8 +419,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { // hasRoundedCorners is currently only enabled for tasks final Context displayContext = mDisplayController.getDisplayContext(change.getTaskInfo().displayId); - cornerRadius = - ScreenDecorationsUtils.getWindowCornerRadius(displayContext); + cornerRadius = displayContext == null ? 0 + : ScreenDecorationsUtils.getWindowCornerRadius(displayContext); } else { cornerRadius = 0; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java index 7f8eaf1a9b55..7e95814c06c2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java @@ -16,10 +16,14 @@ package com.android.wm.shell.util; +import android.graphics.Point; +import android.util.RotationUtils; import android.view.SurfaceControl; /** - * Utility class that takes care of counter-rotating surfaces during a transition animation. + * Utility class that takes care of rotating unchanging child-surfaces to match the parent rotation + * during a transition animation. This gives the illusion that the child surfaces haven't rotated + * relative to the screen. */ public class CounterRotator { private SurfaceControl mSurface = null; @@ -33,29 +37,30 @@ public class CounterRotator { * Sets up this rotator. * * @param rotateDelta is the forward rotation change (the rotation the display is making). - * @param displayW (and H) Is the size of the rotating display. + * @param parentW (and H) Is the size of the rotating parent after the rotation. */ public void setup(SurfaceControl.Transaction t, SurfaceControl parent, int rotateDelta, - float displayW, float displayH) { + float parentW, float parentH) { if (rotateDelta == 0) return; - // We want to counter-rotate, so subtract from 4 - rotateDelta = 4 - (rotateDelta + 4) % 4; mSurface = new SurfaceControl.Builder() .setName("Transition Unrotate") .setContainerLayer() .setParent(parent) .build(); - // column-major - if (rotateDelta == 1) { - t.setMatrix(mSurface, 0, 1, -1, 0); - t.setPosition(mSurface, displayW, 0); - } else if (rotateDelta == 2) { - t.setMatrix(mSurface, -1, 0, 0, -1); - t.setPosition(mSurface, displayW, displayH); - } else if (rotateDelta == 3) { - t.setMatrix(mSurface, 0, -1, 1, 0); - t.setPosition(mSurface, 0, displayH); + // Rotate forward to match the new rotation (rotateDelta is the forward rotation the parent + // already took). Child surfaces will be in the old rotation relative to the new parent + // rotation, so we need to forward-rotate the child surfaces to match. + RotationUtils.rotateSurface(t, mSurface, rotateDelta); + final Point tmpPt = new Point(0, 0); + // parentW/H are the size in the END rotation, the rotation utilities expect the starting + // size. So swap them if necessary + if ((rotateDelta % 2) != 0) { + final float w = parentW; + parentW = parentH; + parentH = w; } + RotationUtils.rotatePoint(tmpPt, rotateDelta, (int) parentW, (int) parentH); + t.setPosition(mSurface, tmpPt.x, tmpPt.y); t.show(mSurface); } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt index 99f7e236ee3f..278ba9b0f4db 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt @@ -68,9 +68,11 @@ abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) { } teardown { - notifyManager.setBubblesAllowed(testApp.component.packageName, + test { + notifyManager.setBubblesAllowed(testApp.component.packageName, uid, NotificationManager.BUBBLE_PREFERENCE_NONE) - testApp.exit() + testApp.exit() + } } extraSpec(this) @@ -95,7 +97,7 @@ abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) { fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) + repetitions = 3) } const val FIND_OBJECT_TIMEOUT = 2000L diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt index a928bbdb288f..f6abc75037ed 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt @@ -28,6 +28,9 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before import org.junit.runner.RunWith import org.junit.Test import org.junit.runners.Parameterized @@ -44,11 +47,16 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @Group4 -class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { +open class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager private val displaySize = DisplayMetrics() + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } + override val transition: FlickerBuilder.() -> Unit get() = buildTransition { setup { @@ -68,7 +76,7 @@ class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(tes @Presubmit @Test - fun testAppIsAlwaysVisible() { + open fun testAppIsAlwaysVisible() { testSpec.assertLayers { this.isVisible(testApp.component) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenShellTransit.kt new file mode 100644 index 000000000000..dd744b3c45ab --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenShellTransit.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.bubble + +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice + +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before + +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 +@FlakyTest(bugId = 217777115) +class DismissBubbleScreenShellTransit( + testSpec: FlickerTestParameter +) : DismissBubbleScreen(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt index af629cc9f8ee..2ec743c10413 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt @@ -24,6 +24,9 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before import org.junit.runner.RunWith import org.junit.Test import org.junit.runners.Parameterized @@ -42,7 +45,12 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @Group4 -class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { +open class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { + + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } override val transition: FlickerBuilder.() -> Unit get() = buildTransition { @@ -61,7 +69,7 @@ class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(test @Presubmit @Test - fun testAppIsAlwaysVisible() { + open fun testAppIsAlwaysVisible() { testSpec.assertLayers { this.isVisible(testApp.component) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenShellTransit.kt new file mode 100644 index 000000000000..d92ec7781005 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenShellTransit.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.bubble + +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 +@FlakyTest(bugId = 217777115) +class ExpandBubbleScreenShellTransit( + testSpec: FlickerTestParameter +) : ExpandBubbleScreen(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt index 64636be1195f..a20a20125bf4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt @@ -22,6 +22,9 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before import org.junit.runner.RunWith import org.junit.Test import org.junit.runners.Parameterized @@ -39,7 +42,12 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @Group4 -class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { +open class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { + + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } override val transition: FlickerBuilder.() -> Unit get() = buildTransition { @@ -51,7 +59,7 @@ class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(test @Presubmit @Test - fun testAppIsAlwaysVisible() { + open fun testAppIsAlwaysVisible() { testSpec.assertLayers { this.isVisible(testApp.component) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenShellTransit.kt new file mode 100644 index 000000000000..9350868a99f4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenShellTransit.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.bubble + +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 +@FlakyTest(bugId = 217777115) +class LaunchBubbleScreenShellTransit( + testSpec: FlickerTestParameter +) : LaunchBubbleScreen(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt index add11c10d75f..8d1e315e2d5e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt @@ -25,6 +25,9 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before import org.junit.runner.RunWith import org.junit.Test import org.junit.runners.Parameterized @@ -41,7 +44,12 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @Group4 -class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { +open class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { + + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } override val transition: FlickerBuilder.() -> Unit get() = buildTransition { @@ -69,7 +77,7 @@ class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(test @Presubmit @Test - fun testAppIsAlwaysVisible() { + open fun testAppIsAlwaysVisible() { testSpec.assertLayers { this.isVisible(testApp.component) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt new file mode 100644 index 000000000000..ddebb6fed636 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.bubble + +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 +@FlakyTest(bugId = 217777115) +class MultiBubblesScreenShellTransit( + testSpec: FlickerTestParameter +) : MultiBubblesScreen(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index db94de238a5d..467cadc7e6e8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -71,24 +71,6 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { } } - @FlakyTest - @Test - fun runPresubmitAssertion() { - flickerRule.checkPresubmitAssertions() - } - - @FlakyTest - @Test - fun runPostsubmitAssertion() { - flickerRule.checkPostsubmitAssertions() - } - - @FlakyTest - @Test - fun runFlakyAssertion() { - flickerRule.checkFlakyAssertions() - } - /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test @@ -205,7 +187,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) + repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt index afe64e3d4abc..accb524d3de1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt @@ -224,7 +224,7 @@ class EnterPipToOtherOrientationTest( fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) + repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt index 3a9a0705908d..931d060be33d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt @@ -98,7 +98,7 @@ class ExitPipViaExpandButtonClickTest( @JvmStatic fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) + supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3) } } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt index 03c8929f9919..e00d7491839f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt @@ -78,24 +78,6 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit } } - @FlakyTest - @Test - fun runPresubmitAssertion() { - flickerRule.checkPresubmitAssertions() - } - - @FlakyTest - @Test - fun runPostsubmitAssertion() { - flickerRule.checkPostsubmitAssertions() - } - - @FlakyTest - @Test - fun runFlakyAssertion() { - flickerRule.checkFlakyAssertions() - } - /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test @@ -117,7 +99,7 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit @JvmStatic fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) + supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt index 976b7c6980a1..5214daa0ec44 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt @@ -67,24 +67,6 @@ class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTran } } - @FlakyTest - @Test - fun runPresubmitAssertion() { - flickerRule.checkPresubmitAssertions() - } - - @FlakyTest - @Test - fun runPostsubmitAssertion() { - flickerRule.checkPostsubmitAssertions() - } - - @FlakyTest - @Test - fun runFlakyAssertion() { - flickerRule.checkFlakyAssertions() - } - /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test @@ -107,7 +89,7 @@ class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTran fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) + repetitions = 3) } } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt index 906123914731..332bba6ad6ef 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt @@ -93,7 +93,7 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) + repetitions = 3) } } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index 8d14f70357b1..07c3b1524d36 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -173,7 +173,7 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) + repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt index e4150241d42c..e91bef10a9e9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt @@ -96,7 +96,7 @@ class MovePipDownShelfHeightChangeTest( @JvmStatic fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) + supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt index 4a4c46c596a2..7e66cd3718e4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt @@ -96,7 +96,7 @@ class MovePipUpShelfHeightChangeTest( @JvmStatic fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) + supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt index 1d61ab48d663..1e30f6b83874 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt @@ -31,7 +31,7 @@ import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.helpers.ImeAppHelper import org.junit.Assume.assumeFalse -import org.junit.Assume.assumeTrue +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -47,9 +47,15 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 -class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { +@FlakyTest(bugId = 218604389) +open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { private val imeApp = ImeAppHelper(instrumentation) + @Before + open fun before() { + assumeFalse(isShellTransitionsEnabled) + } + override val transition: FlickerBuilder.() -> Unit get() = buildTransition(eachRun = false) { setup { @@ -77,25 +83,14 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } - - @FlakyTest(bugId = 214452854) - @Test - fun statusBarLayerRotatesScales_shellTransit() { - assumeTrue(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() /** * Ensure the pip window remains visible throughout any keyboard interactions */ @Presubmit @Test - fun pipInVisibleBounds() { + open fun pipInVisibleBounds() { testSpec.assertWmVisibleRegion(pipApp.component) { val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) coversAtMost(displayBounds) @@ -107,7 +102,7 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) */ @Presubmit @Test - fun pipIsAboveAppWindow() { + open fun pipIsAboveAppWindow() { testSpec.assertWmTag(TAG_IME_VISIBLE) { isAboveWindow(FlickerComponentName.IME, pipApp.component) } @@ -121,7 +116,7 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) + repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt new file mode 100644 index 000000000000..1a21d32f568c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip + +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group4 +@FlakyTest(bugId = 217777115) +class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardTest(testSpec) { + + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } + + @FlakyTest(bugId = 214452854) + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt index b2b50ade78a9..c8ced1c9df12 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt @@ -27,10 +27,13 @@ import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.wm.shell.flicker.helpers.FixedAppHelper +import org.junit.Assume +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -61,11 +64,17 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 -class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { +@FlakyTest(bugId = 218604389) +open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { private val fixedApp = FixedAppHelper(instrumentation) private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation) private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation) + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } + override val transition: FlickerBuilder.() -> Unit get() = buildTransition(eachRun = false) { setup { @@ -182,7 +191,7 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance().getConfigRotationTests( supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90), - repetitions = 5) + repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt new file mode 100644 index 000000000000..a017f56af5bd --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip + +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group4 +@FlakyTest(bugId = 217777115) +class PipRotationTestShellTransit(testSpec: FlickerTestParameter) : PipRotationTest(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt index 8dd91048d29b..d65388bd8cc5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt @@ -26,9 +26,12 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP +import org.junit.Assume +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -44,12 +47,18 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 -class SetRequestedOrientationWhilePinnedTest( +@FlakyTest(bugId = 218604389) +open class SetRequestedOrientationWhilePinnedTest( testSpec: FlickerTestParameter ) : PipTransition(testSpec) { private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0) private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90) + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } + override val transition: FlickerBuilder.() -> Unit get() = { setupAndTeardown(this) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt new file mode 100644 index 000000000000..36e28049864f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip + +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group4 +@FlakyTest(bugId = 217777115) +class SetRequestedOrientationWhilePinnedTestShellTransit( + testSpec: FlickerTestParameter +) : SetRequestedOrientationWhilePinnedTest(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java new file mode 100644 index 000000000000..8278c67a9b4f --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.WindowManager; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.bubbles.BubbleController; +import com.android.wm.shell.bubbles.BubbleOverflow; +import com.android.wm.shell.bubbles.BubbleStackView; +import com.android.wm.shell.bubbles.TestableBubblePositioner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link com.android.wm.shell.bubbles.BubbleOverflow}. + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class BubbleOverflowTest extends ShellTestCase { + + private TestableBubblePositioner mPositioner; + private BubbleOverflow mOverflow; + + @Mock + private BubbleController mBubbleController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mPositioner = new TestableBubblePositioner(mContext, mock(WindowManager.class)); + when(mBubbleController.getPositioner()).thenReturn(mPositioner); + when(mBubbleController.getStackView()).thenReturn(mock(BubbleStackView.class)); + + mOverflow = new BubbleOverflow(mContext, mPositioner); + } + + @Test + public void test_initialize() { + assertThat(mOverflow.getExpandedView()).isNull(); + + mOverflow.initialize(mBubbleController); + + assertThat(mOverflow.getExpandedView()).isNotNull(); + assertThat(mOverflow.getExpandedView().getBubbleKey()).isEqualTo(BubbleOverflow.KEY); + } + + @Test + public void test_cleanUpExpandedState() { + mOverflow.createExpandedView(); + assertThat(mOverflow.getExpandedView()).isNotNull(); + + mOverflow.cleanUpExpandedState(); + assertThat(mOverflow.getExpandedView()).isNull(); + } + +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java index 6080f3ae78e8..403dbf9d9554 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java @@ -22,7 +22,7 @@ import android.content.Context; import android.hardware.display.DisplayManager; import android.testing.TestableContext; -import androidx.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.After; import org.junit.Before; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 21ced0dc5981..b11f9103e16c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -30,6 +30,7 @@ import android.os.RemoteException; import android.testing.AndroidTestingRunner; import android.view.MotionEvent; import android.view.SurfaceControl; +import android.window.BackEvent; import android.window.BackNavigationInfo; import androidx.test.filters.SmallTest; @@ -94,7 +95,9 @@ public class BackAnimationControllerTest { SurfaceControl screenshotSurface = new SurfaceControl(); HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer); - mController.onMotionEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0)); + mController.onMotionEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0), + BackEvent.EDGE_LEFT); verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer); verify(mTransaction).setVisibility(screenshotSurface, true); verify(mTransaction).apply(); @@ -106,8 +109,12 @@ public class BackAnimationControllerTest { SurfaceControl screenshotSurface = new SurfaceControl(); HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer); - mController.onMotionEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0)); - mController.onMotionEvent(MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0)); + mController.onMotionEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0), + BackEvent.EDGE_LEFT); + mController.onMotionEvent( + MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0), + BackEvent.EDGE_LEFT); verify(mTransaction).setPosition(topWindowLeash, 100, 100); verify(mTransaction, atLeastOnce()).apply(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java index 35e498262707..bb6026c36c97 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java @@ -23,6 +23,8 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY; import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; @@ -32,6 +34,7 @@ import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPL import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP; +import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; @@ -50,9 +53,11 @@ import android.app.ActivityTaskManager; import android.app.PendingIntent; import android.content.ClipData; import android.content.ClipDescription; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Insets; @@ -177,6 +182,12 @@ public class DragAndDropPolicyTest { info.configuration.windowConfiguration.setActivityType(actType); info.configuration.windowConfiguration.setWindowingMode(winMode); info.isResizeable = true; + info.baseActivity = new ComponentName(getInstrumentation().getContext().getPackageName(), + ".ActivityWithMode" + winMode); + ActivityInfo activityInfo = new ActivityInfo(); + activityInfo.packageName = info.baseActivity.getPackageName(); + activityInfo.name = info.baseActivity.getClassName(); + info.topActivityInfo = activityInfo; return info; } @@ -252,6 +263,62 @@ public class DragAndDropPolicyTest { } } + @Test + public void testLaunchMultipleTask_differentActivity() { + setRunningTask(mFullscreenAppTask); + mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); + Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0); + assertNull(fillInIntent); + } + + @Test + public void testLaunchMultipleTask_differentActivity_inSplitscreen() { + setRunningTask(mFullscreenAppTask); + doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible(); + doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt()); + mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); + Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0); + assertNull(fillInIntent); + } + + @Test + public void testLaunchMultipleTask_sameActivity() { + setRunningTask(mFullscreenAppTask); + + // Replace the mocked drag pending intent and ensure it resolves to the same activity + PendingIntent launchIntent = mock(PendingIntent.class); + ResolveInfo launchInfo = new ResolveInfo(); + launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo; + doReturn(Collections.singletonList(launchInfo)) + .when(launchIntent).queryIntentComponents(anyInt()); + mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT, + launchIntent); + + mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); + Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0); + assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0); + } + + @Test + public void testLaunchMultipleTask_sameActivity_inSplitScreen() { + setRunningTask(mFullscreenAppTask); + + // Replace the mocked drag pending intent and ensure it resolves to the same activity + PendingIntent launchIntent = mock(PendingIntent.class); + ResolveInfo launchInfo = new ResolveInfo(); + launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo; + doReturn(Collections.singletonList(launchInfo)) + .when(launchIntent).queryIntentComponents(anyInt()); + mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT, + launchIntent); + + doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible(); + doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt()); + mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); + Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0); + assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0); + } + private Target filterTargetByType(ArrayList<Target> targets, int type) { for (Target t : targets) { if (type == t.type) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java index f10dc16fae5c..b976c1287aca 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java @@ -24,8 +24,8 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.wm.shell.common.ShellExecutor; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java index 078e2b6cf574..16e92395c85e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java @@ -45,8 +45,8 @@ import android.window.DisplayAreaOrganizer; import android.window.IWindowContainerToken; import android.window.WindowContainerToken; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 0f4a06f22986..dbf93b4d7e0a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -591,6 +591,13 @@ public class ShellTransitionTests { .setRotate().build()) .build(); assertFalse(DefaultTransitionHandler.isRotationSeamless(noTask, displays)); + + // Seamless if display is explicitly seamless. + final TransitionInfo seamlessDisplay = new TransitionInfoBuilder(TRANSIT_CHANGE) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY) + .setRotate(ROTATION_ANIMATION_SEAMLESS).build()) + .build(); + assertTrue(DefaultTransitionHandler.isRotationSeamless(seamlessDisplay, displays)); } class TransitionInfoBuilder { diff --git a/media/Android.bp b/media/Android.bp index 5aedcfbc22e9..2f9c5203b462 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -103,6 +103,11 @@ aidl_interface { }, java: { sdk_version: "module_current", + min_sdk_version: "29", + apex_available: [ + "//apex_available:platform", + "com.android.car.framework", + ], }, ndk: { vndk: { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index cdc31631637e..3887372d00b5 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -91,6 +91,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * AudioManager provides access to volume and ringer mode control. @@ -8342,6 +8343,106 @@ public class AudioManager { } } + /** + * Add UID's that can be considered as assistant. + * + * @param assistantUids UID's of the services that can be considered as assistant. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void addAssistantServicesUids(@NonNull List<Integer> assistantUids) { + try { + getService().addAssistantServicesUids(assistantUids.stream() + .mapToInt(Integer::intValue).toArray()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove UID's that can be considered as assistant. + * + * @param assistantUids UID'S of the services that should be remove. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void removeAssistantServicesUids(@NonNull List<Integer> assistantUids) { + try { + getService().removeAssistantServicesUids(assistantUids.stream() + .mapToInt(Integer::intValue).toArray()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the list of assistants UIDs that been added with the + * {@link #addAssistantServicesUids(List)} (List)} and not yet removed with + * {@link #removeAssistantServicesUids(List)} + * + * @return list of assistants UID's + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public @NonNull List<Integer> getAssistantServicesUids() { + try { + int[] uids = getService().getAssistantServicesUids(); + return Arrays.stream(uids).boxed().collect(Collectors.toList()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets UID's that can be considered as active assistant. Calling the API with a new list will + * overwrite previous list. If the list of UIDs is empty then no UID will be considered active. + * In this manner calling the API with an empty list will remove all UID's previously set. + * + * @param assistantUids UID'S of the services that can be considered active assistant. Can be + * an empty list, for this no UID will be considered active. + * + * <p> Note that during audio service crash reset and after boot up the list of active assistant + * UID's will be reset to an empty list (i.e. no UID will be considered as an active assistant). + * Just after user switch the list of active assistant will also reset to empty. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void setActiveAssistantServiceUids(@NonNull List<Integer> assistantUids) { + try { + getService().setActiveAssistantServiceUids(assistantUids.stream() + .mapToInt(Integer::intValue).toArray()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the list of active assistant UIDs last set with the + * {@link #setActiveAssistantServiceUids(List)} + * + * @return list of active assistants UID's + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public @NonNull List<Integer> getActiveAssistantServicesUids() { + try { + int[] uids = getService().getActiveAssistantServiceUids(); + return Arrays.stream(uids).boxed().collect(Collectors.toList()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private final Object mMuteAwaitConnectionListenerLock = new Object(); @GuardedBy("mMuteAwaitConnectionListenerLock") diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java index cb887f2d523d..c2632458435a 100644 --- a/media/java/android/media/AudioManagerInternal.java +++ b/media/java/android/media/AudioManagerInternal.java @@ -38,18 +38,27 @@ public abstract class AudioManagerInternal { public abstract void updateRingerModeAffectedStreamsInternal(); + public abstract void setAccessibilityServiceUids(IntArray uids); + /** - * Notify the UID of the currently active {@link android.service.voice.HotwordDetectionService}. + * Add the UID for a new assistant service * - * <p>The caller is expected to take care of any performance implications, e.g. by using a - * background thread to call this method.</p> + * @param uid UID of the newly available assistants + */ + public abstract void addAssistantServiceUid(int uid); + + /** + * Remove the UID for an existing assistant service * - * @param uid UID of the currently active service or {@link android.os.Process#INVALID_UID} if - * none. + * @param uid UID of the currently available assistant */ - public abstract void setHotwordDetectionServiceUid(int uid); + public abstract void removeAssistantServiceUid(int uid); - public abstract void setAccessibilityServiceUids(IntArray uids); + /** + * Set the currently active assistant service UIDs + * @param activeUids active UIDs of the assistant service + */ + public abstract void setActiveAssistantServicesUids(IntArray activeUids); /** * Called by {@link com.android.server.inputmethod.InputMethodManagerService} to notify the UID diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 536b4ad71285..6cacebb433a6 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1856,16 +1856,15 @@ public class AudioSystem /** * @hide - * Communicate UID of active assistant to audio policy service. + * Communicate UIDs of the active assistant to audio policy service. */ - public static native int setAssistantUid(int uid); + public static native int setActiveAssistantServicesUids(int[] uids); /** - * Communicate UID of the current {@link android.service.voice.HotwordDetectionService} to audio - * policy service. * @hide + * Communicate UIDs of assistant to audio policy service. */ - public static native int setHotwordDetectionServiceUid(int uid); + public static native int setAssistantServicesUids(int[] uids); /** * @hide diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index fec14def618c..a6fdf6cedd55 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -402,6 +402,14 @@ interface IAudioService { boolean isSpatializerAvailable(); + boolean isSpatializerAvailableForDevice(in AudioDeviceAttributes device); + + boolean hasHeadTracker(in AudioDeviceAttributes device); + + void setHeadTrackerEnabled(boolean enabled, in AudioDeviceAttributes device); + + boolean isHeadTrackerEnabled(in AudioDeviceAttributes device); + void setSpatializerEnabled(boolean enabled); boolean canBeSpatialized(in AudioAttributes aa, in AudioFormat af); @@ -465,4 +473,19 @@ interface IAudioService { List<AudioFocusInfo> getFocusStack(); boolean sendFocusLoss(in AudioFocusInfo focusLoser, in IAudioPolicyCallback apcb); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)") + void addAssistantServicesUids(in int[] assistantUID); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)") + void removeAssistantServicesUids(in int[] assistantUID); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)") + void setActiveAssistantServiceUids(in int[] activeUids); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)") + int[] getAssistantServicesUids(); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)") + int[] getActiveAssistantServiceUids(); } diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 939b679676aa..4563259c31f2 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -16,9 +16,12 @@ package android.media; +import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.ImageFormat; import android.graphics.Rect; @@ -1934,12 +1937,41 @@ final public class MediaCodec { @NonNull public static MediaCodec createByCodecName(@NonNull String name) throws IOException { - return new MediaCodec( - name, false /* nameIsType */, false /* unused */); + return new MediaCodec(name, false /* nameIsType */, false /* encoder */); } - private MediaCodec( - @NonNull String name, boolean nameIsType, boolean encoder) { + /** + * This is the same as createByCodecName, but allows for instantiating a codec on behalf of a + * client process. This is used for system apps or system services that create MediaCodecs on + * behalf of other processes and will reclaim resources as necessary from processes with lower + * priority than the client process, rather than processes with lower priority than the system + * app or system service. Likely to be used with information obtained from + * {@link android.media.MediaCodecList}. + * @param name + * @param clientPid + * @param clientUid + * @throws IOException if the codec cannot be created. + * @throws IllegalArgumentException if name is not valid. + * @throws NullPointerException if name is null. + * @throws SecurityException if the MEDIA_RESOURCE_OVERRIDE_PID permission is not granted. + * + * @hide + */ + @NonNull + @SystemApi + @RequiresPermission(Manifest.permission.MEDIA_RESOURCE_OVERRIDE_PID) + public static MediaCodec createByCodecNameForClient(@NonNull String name, int clientPid, + int clientUid) throws IOException { + return new MediaCodec(name, false /* nameIsType */, false /* encoder */, clientPid, + clientUid); + } + + private MediaCodec(@NonNull String name, boolean nameIsType, boolean encoder) { + this(name, nameIsType, encoder, -1 /* pid */, -1 /* uid */); + } + + private MediaCodec(@NonNull String name, boolean nameIsType, boolean encoder, int pid, + int uid) { Looper looper; if ((looper = Looper.myLooper()) != null) { mEventHandler = new EventHandler(this, looper); @@ -1957,7 +1989,7 @@ final public class MediaCodec { // save name used at creation mNameAtCreation = nameIsType ? null : name; - native_setup(name, nameIsType, encoder); + native_setup(name, nameIsType, encoder, pid, uid); } private String mNameAtCreation; @@ -4991,7 +5023,7 @@ final public class MediaCodec { private static native final void native_init(); private native final void native_setup( - @NonNull String name, boolean nameIsType, boolean encoder); + @NonNull String name, boolean nameIsType, boolean encoder, int pid, int uid); private native final void native_finalize(); diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java index 030d212825ee..be0ef37345ed 100644 --- a/media/java/android/media/Spatializer.java +++ b/media/java/android/media/Spatializer.java @@ -108,6 +108,83 @@ public class Spatializer { } } + /** + * @hide + * Returns whether spatialization is available for a given audio device + * Reasons for spatialization being unavailable include situations where audio output is + * incompatible with sound spatialization, such as the device being a monophonic speaker, or + * the spatializer effect not supporting transaural processing when querying for speaker. + * @param device the audio device for which spatializer availability is queried + * @return {@code true} if the spatializer effect is available and capable + * of processing the audio over the given audio device, + * {@code false} otherwise. + * @see #isEnabled() + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public boolean isAvailableForDevice(@NonNull AudioDeviceAttributes device) { + Objects.requireNonNull(device); + try { + return mAm.getService().isSpatializerAvailableForDevice(device); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + + /** + * @hide + * Returns whether the given device has an associated headtracker + * @param device the audio device to query + * @return true if the device has a head tracker, false otherwise + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public boolean hasHeadTracker(@NonNull AudioDeviceAttributes device) { + Objects.requireNonNull(device); + try { + return mAm.getService().hasHeadTracker(device); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + + /** + * @hide + * Enables or disables the head tracker of the given device + * @param enabled true to enable, false to disable + * @param device the device whose head tracker state is changed + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public void setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes device) { + Objects.requireNonNull(device); + try { + mAm.getService().setHeadTrackerEnabled(enabled, device); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Returns whether the head tracker of the device is enabled + * @param device the device to query + * @return true if the head tracker is enabled, false if disabled or if there isn't one + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public boolean isHeadTrackerEnabled(@NonNull AudioDeviceAttributes device) { + Objects.requireNonNull(device); + try { + return mAm.getService().isHeadTrackerEnabled(device); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + /** @hide */ @IntDef(flag = false, value = { SPATIALIZER_IMMERSIVE_LEVEL_OTHER, diff --git a/media/java/android/media/metrics/BundleSession.java b/media/java/android/media/metrics/BundleSession.java new file mode 100644 index 000000000000..743d2338d69a --- /dev/null +++ b/media/java/android/media/metrics/BundleSession.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.metrics; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.PersistableBundle; + +import com.android.internal.util.AnnotationValidations; + +import java.util.Objects; + +/** + * An instances of this class represents a session with data stored in a bundle. + */ +public final class BundleSession implements AutoCloseable { + private final @NonNull String mId; + private final @NonNull MediaMetricsManager mManager; + private final @NonNull LogSessionId mLogSessionId; + + /** @hide */ + public BundleSession(@NonNull String id, @NonNull MediaMetricsManager manager) { + mId = id; + mManager = manager; + AnnotationValidations.validate(NonNull.class, null, mId); + AnnotationValidations.validate(NonNull.class, null, mManager); + mLogSessionId = new LogSessionId(mId); + } + + /** + * Reports metrics via bundle. + * + */ + public void reportBundleMetrics(@NonNull PersistableBundle metrics) { + mManager.reportBundleMetrics(mId, metrics); + } + + public @NonNull LogSessionId getSessionId() { + return mLogSessionId; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BundleSession that = (BundleSession) o; + return Objects.equals(mId, that.mId); + } + + @Override + public int hashCode() { + return Objects.hash(mId); + } + + @Override + public void close() { + } +} diff --git a/media/java/android/media/metrics/EditingSession.java b/media/java/android/media/metrics/EditingSession.java new file mode 100644 index 000000000000..2a48a728a2f0 --- /dev/null +++ b/media/java/android/media/metrics/EditingSession.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.metrics; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.util.AnnotationValidations; + +import java.util.Objects; + +/** + * An instances of this class represents a session of media editing. + */ +public final class EditingSession implements AutoCloseable { + private final @NonNull String mId; + private final @NonNull MediaMetricsManager mManager; + private final @NonNull LogSessionId mLogSessionId; + + /** @hide */ + public EditingSession(@NonNull String id, @NonNull MediaMetricsManager manager) { + mId = id; + mManager = manager; + AnnotationValidations.validate(NonNull.class, null, mId); + AnnotationValidations.validate(NonNull.class, null, mManager); + mLogSessionId = new LogSessionId(mId); + } + + public @NonNull LogSessionId getSessionId() { + return mLogSessionId; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EditingSession that = (EditingSession) o; + return Objects.equals(mId, that.mId); + } + + @Override + public int hashCode() { + return Objects.hash(mId); + } + + @Override + public void close() { + } +} diff --git a/media/java/android/media/metrics/IMediaMetricsManager.aidl b/media/java/android/media/metrics/IMediaMetricsManager.aidl index f2c0d44a00b1..a774403b4835 100644 --- a/media/java/android/media/metrics/IMediaMetricsManager.aidl +++ b/media/java/android/media/metrics/IMediaMetricsManager.aidl @@ -21,6 +21,7 @@ import android.media.metrics.PlaybackErrorEvent; import android.media.metrics.PlaybackMetrics; import android.media.metrics.PlaybackStateEvent; import android.media.metrics.TrackChangeEvent; +import android.os.PersistableBundle; /** * Interface to the playback manager service. @@ -28,10 +29,16 @@ import android.media.metrics.TrackChangeEvent; */ interface IMediaMetricsManager { void reportPlaybackMetrics(in String sessionId, in PlaybackMetrics metrics, int userId); + String getPlaybackSessionId(int userId); String getRecordingSessionId(int userId); void reportNetworkEvent(in String sessionId, in NetworkEvent event, int userId); void reportPlaybackErrorEvent(in String sessionId, in PlaybackErrorEvent event, int userId); void reportPlaybackStateEvent(in String sessionId, in PlaybackStateEvent event, int userId); void reportTrackChangeEvent(in String sessionId, in TrackChangeEvent event, int userId); -}
\ No newline at end of file + + String getTranscodingSessionId(int userId); + String getEditingSessionId(int userId); + String getBundleSessionId(int userId); + void reportBundleMetrics(in String sessionId, in PersistableBundle metrics, int userId); +} diff --git a/media/java/android/media/metrics/MediaMetricsManager.java b/media/java/android/media/metrics/MediaMetricsManager.java index 23b697ff7512..7229c471c3b6 100644 --- a/media/java/android/media/metrics/MediaMetricsManager.java +++ b/media/java/android/media/metrics/MediaMetricsManager.java @@ -19,6 +19,7 @@ package android.media.metrics; import android.annotation.NonNull; import android.annotation.SystemService; import android.content.Context; +import android.os.PersistableBundle; import android.os.RemoteException; /** @@ -53,6 +54,17 @@ public final class MediaMetricsManager { } } /** + * Reports bundle metrics. + * @hide + */ + public void reportBundleMetrics(@NonNull String sessionId, PersistableBundle metrics) { + try { + mService.reportBundleMetrics(sessionId, metrics, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Reports network event. * @hide */ @@ -117,6 +129,48 @@ public final class MediaMetricsManager { } /** + * Creates a transcoding session. + */ + @NonNull + public TranscodingSession createTranscodingSession() { + try { + String id = mService.getTranscodingSessionId(mUserId); + TranscodingSession session = new TranscodingSession(id, this); + return session; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Creates a editing session. + */ + @NonNull + public EditingSession createEditingSession() { + try { + String id = mService.getEditingSessionId(mUserId); + EditingSession session = new EditingSession(id, this); + return session; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Creates a generic bundle session. + */ + @NonNull + public BundleSession createBundleSession() { + try { + String id = mService.getBundleSessionId(mUserId); + BundleSession session = new BundleSession(id, this); + return session; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Reports error event. * @hide */ diff --git a/media/java/android/media/metrics/TranscodingSession.java b/media/java/android/media/metrics/TranscodingSession.java new file mode 100644 index 000000000000..e6d359aec35d --- /dev/null +++ b/media/java/android/media/metrics/TranscodingSession.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.metrics; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.util.AnnotationValidations; + +import java.util.Objects; + +/** + * An instances of this class represents a session of media Transcoding. + */ +public final class TranscodingSession implements AutoCloseable { + private final @NonNull String mId; + private final @NonNull MediaMetricsManager mManager; + private final @NonNull LogSessionId mLogSessionId; + + /** @hide */ + public TranscodingSession(@NonNull String id, @NonNull MediaMetricsManager manager) { + mId = id; + mManager = manager; + AnnotationValidations.validate(NonNull.class, null, mId); + AnnotationValidations.validate(NonNull.class, null, mManager); + mLogSessionId = new LogSessionId(mId); + } + + public @NonNull LogSessionId getSessionId() { + return mLogSessionId; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TranscodingSession that = (TranscodingSession) o; + return Objects.equals(mId, that.mId); + } + + @Override + public int hashCode() { + return Objects.hash(mId); + } + + @Override + public void close() { + } +} diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 73e96a2e6c3e..96809bda7c31 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -1877,6 +1877,7 @@ public final class TvInputManager { * @hide */ @SystemApi + @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) public int getClientPriority(@TvInputService.PriorityHintUseCaseType int useCase, @Nullable String sessionId) { return getClientPriorityInternal(useCase, sessionId); diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index f6944823feb5..c8d2d1ee621f 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -202,7 +202,7 @@ static const void *sRefBaseOwner; JMediaCodec::JMediaCodec( JNIEnv *env, jobject thiz, - const char *name, bool nameIsType, bool encoder) + const char *name, bool nameIsType, bool encoder, int pid, int uid) : mClass(NULL), mObject(NULL) { jclass clazz = env->GetObjectClass(thiz); @@ -220,12 +220,12 @@ JMediaCodec::JMediaCodec( ANDROID_PRIORITY_VIDEO); if (nameIsType) { - mCodec = MediaCodec::CreateByType(mLooper, name, encoder, &mInitStatus); + mCodec = MediaCodec::CreateByType(mLooper, name, encoder, &mInitStatus, pid, uid); if (mCodec == nullptr || mCodec->getName(&mNameAtCreation) != OK) { mNameAtCreation = "(null)"; } } else { - mCodec = MediaCodec::CreateByComponentName(mLooper, name, &mInitStatus); + mCodec = MediaCodec::CreateByComponentName(mLooper, name, &mInitStatus, pid, uid); mNameAtCreation = name; } CHECK((mCodec != NULL) != (mInitStatus != OK)); @@ -3136,7 +3136,7 @@ static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) { static void android_media_MediaCodec_native_setup( JNIEnv *env, jobject thiz, - jstring name, jboolean nameIsType, jboolean encoder) { + jstring name, jboolean nameIsType, jboolean encoder, int pid, int uid) { if (name == NULL) { jniThrowException(env, "java/lang/NullPointerException", NULL); return; @@ -3148,24 +3148,33 @@ static void android_media_MediaCodec_native_setup( return; } - sp<JMediaCodec> codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder); + sp<JMediaCodec> codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder, pid, uid); const status_t err = codec->initCheck(); if (err == NAME_NOT_FOUND) { // fail and do not try again. jniThrowException(env, "java/lang/IllegalArgumentException", - String8::format("Failed to initialize %s, error %#x", tmp, err)); + String8::format("Failed to initialize %s, error %#x (NAME_NOT_FOUND)", tmp, err)); env->ReleaseStringUTFChars(name, tmp); return; - } if (err == NO_MEMORY) { + } + if (err == NO_MEMORY) { throwCodecException(env, err, ACTION_CODE_TRANSIENT, - String8::format("Failed to initialize %s, error %#x", tmp, err)); + String8::format("Failed to initialize %s, error %#x (NO_MEMORY)", tmp, err)); + env->ReleaseStringUTFChars(name, tmp); + return; + } + if (err == PERMISSION_DENIED) { + jniThrowException(env, "java/lang/SecurityException", + String8::format("Failed to initialize %s, error %#x (PERMISSION_DENIED)", tmp, + err)); env->ReleaseStringUTFChars(name, tmp); return; - } else if (err != OK) { + } + if (err != OK) { // believed possible to try again jniThrowException(env, "java/io/IOException", - String8::format("Failed to find matching codec %s, error %#x", tmp, err)); + String8::format("Failed to find matching codec %s, error %#x (?)", tmp, err)); env->ReleaseStringUTFChars(name, tmp); return; } @@ -3174,7 +3183,7 @@ static void android_media_MediaCodec_native_setup( codec->registerSelf(); - setMediaCodec(env,thiz, codec); + setMediaCodec(env, thiz, codec); } static void android_media_MediaCodec_native_finalize( @@ -3478,7 +3487,7 @@ static const JNINativeMethod gMethods[] = { { "native_init", "()V", (void *)android_media_MediaCodec_native_init }, - { "native_setup", "(Ljava/lang/String;ZZ)V", + { "native_setup", "(Ljava/lang/String;ZZII)V", (void *)android_media_MediaCodec_native_setup }, { "native_finalize", "()V", diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index ee456c9ba82d..616c31b29157 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -55,7 +55,7 @@ using hardware::cas::native::V1_0::IDescrambler; struct JMediaCodec : public AHandler { JMediaCodec( JNIEnv *env, jobject thiz, - const char *name, bool nameIsType, bool encoder); + const char *name, bool nameIsType, bool encoder, int pid, int uid); status_t initCheck() const; diff --git a/native/android/OWNERS b/native/android/OWNERS index 02dfd393a0b7..cfe973400c98 100644 --- a/native/android/OWNERS +++ b/native/android/OWNERS @@ -1,14 +1,23 @@ -jreck@google.com +jreck@google.com #{LAST_RESORT_SUGGESTION} +# General NDK API reviewers +per-file libandroid.map.txt = danalbert@google.com, etalvala@google.com, michaelwr@google.com +per-file libandroid.map.txt = jreck@google.com, zyy@google.com + +# Networking per-file libandroid_net.map.txt, net.c = set noparent per-file libandroid_net.map.txt, net.c = codewiz@google.com, jchalard@google.com, junyulai@google.com per-file libandroid_net.map.txt, net.c = lorenzo@google.com, reminv@google.com, satk@google.com + +# Fonts per-file system_fonts.cpp = file:/graphics/java/android/graphics/fonts/OWNERS +# Window manager per-file native_window_jni.cpp = file:/services/core/java/com/android/server/wm/OWNERS per-file native_activity.cpp = file:/services/core/java/com/android/server/wm/OWNERS per-file surface_control.cpp = file:/services/core/java/com/android/server/wm/OWNERS +# Graphics per-file choreographer.cpp = file:/graphics/java/android/graphics/OWNERS per-file hardware_buffer_jni.cpp = file:/graphics/java/android/graphics/OWNERS per-file native_window_jni.cpp = file:/graphics/java/android/graphics/OWNERS diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index d01a30e52749..6eff629a03f4 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -141,7 +141,7 @@ void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) { } struct ASurfaceControlStats { - int64_t acquireTime; + std::variant<int64_t, sp<Fence>> acquireTimeOrFence; sp<Fence> previousReleaseFence; uint64_t frameNumber; }; @@ -153,7 +153,7 @@ void ASurfaceControl_registerSurfaceStatsListener(ASurfaceControl* control, int3 const SurfaceStats& surfaceStats) { ASurfaceControlStats aSurfaceControlStats; - aSurfaceControlStats.acquireTime = surfaceStats.acquireTime; + aSurfaceControlStats.acquireTimeOrFence = surfaceStats.acquireTimeOrFence; aSurfaceControlStats.previousReleaseFence = surfaceStats.previousReleaseFence; aSurfaceControlStats.frameNumber = surfaceStats.eventStats.frameNumber; @@ -171,7 +171,15 @@ void ASurfaceControl_unregisterSurfaceStatsListener(void* context, } int64_t ASurfaceControlStats_getAcquireTime(ASurfaceControlStats* stats) { - return stats->acquireTime; + if (const auto* fence = std::get_if<sp<Fence>>(&stats->acquireTimeOrFence)) { + // We got a fence instead of the acquire time due to latch unsignaled. + // Ideally the client could just get the acquire time dericly from + // the fence instead of calling this function which needs to block. + (*fence)->waitForever("ASurfaceControlStats_getAcquireTime"); + return (*fence)->getSignalTime(); + } + + return std::get<int64_t>(stats->acquireTimeOrFence); } uint64_t ASurfaceControlStats_getFrameNumber(ASurfaceControlStats* stats) { @@ -250,7 +258,7 @@ int64_t ASurfaceTransactionStats_getAcquireTime(ASurfaceTransactionStats* aSurfa aSurfaceControlStats == aSurfaceTransactionStats->aSurfaceControlStats.end(), "ASurfaceControl not found"); - return aSurfaceControlStats->second.acquireTime; + return ASurfaceControlStats_getAcquireTime(&aSurfaceControlStats->second); } int ASurfaceTransactionStats_getPreviousReleaseFenceFd( @@ -294,9 +302,10 @@ void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* aSurfaceTransaction, auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats; - for (const auto& [surfaceControl, latchTime, acquireTime, presentFence, previousReleaseFence, transformHint, frameEvents] : surfaceControlStats) { + for (const auto& [surfaceControl, latchTime, acquireTimeOrFence, presentFence, + previousReleaseFence, transformHint, frameEvents] : surfaceControlStats) { ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get()); - aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime; + aSurfaceControlStats[aSurfaceControl].acquireTimeOrFence = acquireTimeOrFence; aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence; } @@ -643,13 +652,12 @@ void ASurfaceTransaction_setOnCommit(ASurfaceTransaction* aSurfaceTransaction, v aSurfaceTransactionStats.transactionCompleted = false; auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats; - for (const auto& - [surfaceControl, latchTime, acquireTime, presentFence, - previousReleaseFence, transformHint, - frameEvents] : surfaceControlStats) { + for (const auto& [surfaceControl, latchTime, acquireTimeOrFence, presentFence, + previousReleaseFence, transformHint, frameEvents] : + surfaceControlStats) { ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get()); - aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime; + aSurfaceControlStats[aSurfaceControl].acquireTimeOrFence = acquireTimeOrFence; } (*func)(callback_context, &aSurfaceTransactionStats); diff --git a/omapi/aidl/vts/functional/config/Android.bp b/omapi/aidl/vts/functional/config/Android.bp new file mode 100644 index 000000000000..7c08257bf828 --- /dev/null +++ b/omapi/aidl/vts/functional/config/Android.bp @@ -0,0 +1,26 @@ +// +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +xsd_config { + name: "omapi_uuid_map_config", + srcs: ["omapi_uuid_map_config.xsd"], + api_dir: "schema", + package_name: "omapi.uuid.map.config", +} diff --git a/omapi/aidl/vts/functional/config/omapi_uuid_map_config.xsd b/omapi/aidl/vts/functional/config/omapi_uuid_map_config.xsd new file mode 100644 index 000000000000..ffeb7a032c38 --- /dev/null +++ b/omapi/aidl/vts/functional/config/omapi_uuid_map_config.xsd @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<xs:schema version="2.0" + attributeFormDefault="unqualified" + elementFormDefault="qualified" + xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:element name="ref_do"> + <xs:complexType> + <xs:sequence> + <xs:element name="uuid_ref_do" maxOccurs="unbounded" minOccurs="0"> + <xs:complexType> + <xs:sequence> + <xs:element name="uids"> + <xs:complexType> + <xs:sequence> + <xs:element type="xs:short" name="uid" maxOccurs="unbounded" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element type="xs:string" name="uuid"/> + </xs:sequence> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> +</xs:schema> diff --git a/omapi/aidl/vts/functional/config/schema/current.txt b/omapi/aidl/vts/functional/config/schema/current.txt new file mode 100644 index 000000000000..c2e930b949d9 --- /dev/null +++ b/omapi/aidl/vts/functional/config/schema/current.txt @@ -0,0 +1,30 @@ +// Signature format: 2.0 +package omapi.uuid.map.config { + + public class RefDo { + ctor public RefDo(); + method public java.util.List<omapi.uuid.map.config.RefDo.UuidRefDo> getUuid_ref_do(); + } + + public static class RefDo.UuidRefDo { + ctor public RefDo.UuidRefDo(); + method public omapi.uuid.map.config.RefDo.UuidRefDo.Uids getUids(); + method public String getUuid(); + method public void setUids(omapi.uuid.map.config.RefDo.UuidRefDo.Uids); + method public void setUuid(String); + } + + public static class RefDo.UuidRefDo.Uids { + ctor public RefDo.UuidRefDo.Uids(); + method public java.util.List<java.lang.Short> getUid(); + } + + public class XmlParser { + ctor public XmlParser(); + method public static omapi.uuid.map.config.RefDo read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + } + +} + diff --git a/omapi/aidl/vts/functional/config/schema/last_current.txt b/omapi/aidl/vts/functional/config/schema/last_current.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/omapi/aidl/vts/functional/config/schema/last_current.txt diff --git a/omapi/aidl/vts/functional/config/schema/last_removed.txt b/omapi/aidl/vts/functional/config/schema/last_removed.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/omapi/aidl/vts/functional/config/schema/last_removed.txt diff --git a/omapi/aidl/vts/functional/config/schema/removed.txt b/omapi/aidl/vts/functional/config/schema/removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/omapi/aidl/vts/functional/config/schema/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/omapi/aidl/vts/functional/omapi/Android.bp b/omapi/aidl/vts/functional/omapi/Android.bp index c3ab8d13920c..c41479f9e9cf 100644 --- a/omapi/aidl/vts/functional/omapi/Android.bp +++ b/omapi/aidl/vts/functional/omapi/Android.bp @@ -39,6 +39,11 @@ cc_test { static_libs: [ "VtsHalHidlTargetTestBase", "android.se.omapi-V1-ndk", + "android.hardware.audio.common.test.utility", + "libxml2", + ], + data: [ + ":omapi_uuid_map_config", ], cflags: [ "-O0", @@ -51,4 +56,5 @@ cc_test { "general-tests", "vts", ], + test_config: "VtsHalOmapiSeServiceV1_TargetTest.xml", } diff --git a/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp index 319cb7e70884..5303651b4b22 100644 --- a/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp +++ b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp @@ -32,6 +32,7 @@ #include <hidl/GtestPrinter.h> #include <hidl/ServiceManagement.h> #include <utils/String16.h> +#include "utility/ValidateXml.h" using namespace std; using namespace ::testing; @@ -176,6 +177,25 @@ class OMAPISEServiceHalTest : public TestWithParam<std::string> { return (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str())); } + std::optional<std::string> getUuidMappingFile() { + char value[PROPERTY_VALUE_MAX] = {0}; + int len = property_get("ro.boot.product.hardware.sku", value, "config"); + std::string uuidMappingConfigFile = UUID_MAPPING_CONFIG_PREFIX + + std::string(value, len) + + UUID_MAPPING_CONFIG_EXT; + std::string uuidMapConfigPath; + // Search in predefined folders + for (auto path : UUID_MAPPING_CONFIG_PATHS) { + uuidMapConfigPath = path + uuidMappingConfigFile; + auto confFile = fopen(uuidMapConfigPath.c_str(), "r"); + if (confFile) { + fclose(confFile); + return uuidMapConfigPath; + } + } + return std::optional<std::string>(); + } + void SetUp() override { LOG(INFO) << "get OMAPI service with name:" << GetParam(); ::ndk::SpAIBinder ks2Binder(AServiceManager_getService(GetParam().c_str())); @@ -300,6 +320,10 @@ class OMAPISEServiceHalTest : public TestWithParam<std::string> { std::map<std::string, std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>> mVSReaders = {}; + + std::string UUID_MAPPING_CONFIG_PREFIX = "hal_uuid_map_"; + std::string UUID_MAPPING_CONFIG_EXT = ".xml"; + std::string UUID_MAPPING_CONFIG_PATHS[3] = {"/odm/etc/", "/vendor/etc/", "/etc/"}; }; /** Tests getReaders API */ @@ -600,6 +624,14 @@ TEST_P(OMAPISEServiceHalTest, TestP2Value) { } } +TEST_P(OMAPISEServiceHalTest, TestUuidMappingConfig) { + constexpr const char* xsd = "/data/local/tmp/omapi_uuid_map_config.xsd"; + auto uuidMappingFile = getUuidMappingFile(); + ASSERT_TRUE(uuidMappingFile.has_value()) << "Unable to determine UUID mapping config file path"; + LOG(INFO) << "UUID Mapping config file: " << uuidMappingFile.value(); + EXPECT_VALID_XML(uuidMappingFile->c_str(), xsd); +} + INSTANTIATE_TEST_SUITE_P(PerInstance, OMAPISEServiceHalTest, testing::ValuesIn(::android::getAidlHalInstanceNames( aidl::android::se::omapi::ISecureElementService::descriptor)), diff --git a/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.xml b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.xml new file mode 100644 index 000000000000..3ee0414711f3 --- /dev/null +++ b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs VtsHalOmapiSeServiceV1_TargetTest."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-native" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" + value="omapi_uuid_map_config.xsd->/data/local/tmp/omapi_uuid_map_config.xsd" /> + <option name="push" + value="VtsHalOmapiSeServiceV1_TargetTest->/data/local/tmp/VtsHalOmapiSeServiceV1_TargetTest" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="VtsHalOmapiSeServiceV1_TargetTest" /> + </test> +</configuration> diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java index 221a848a5998..ca080ce4c64a 100644 --- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java +++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java @@ -699,7 +699,9 @@ public class NetworkStatsManager { * @hide */ @SystemApi - @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) @NonNull public android.net.NetworkStats getMobileUidStats() { try { return mService.getUidStatsForTransport(TRANSPORT_CELLULAR); @@ -723,7 +725,9 @@ public class NetworkStatsManager { * @hide */ @SystemApi - @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) @NonNull public android.net.NetworkStats getWifiUidStats() { try { return mService.getUidStatsForTransport(TRANSPORT_WIFI); diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java index 1f67f6d654e1..1a955c4c57d7 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java +++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java @@ -16,14 +16,16 @@ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.annotation.CallbackExecutor; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; -import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; @@ -33,13 +35,15 @@ import android.os.RemoteException; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.BackgroundThread; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.BiConsumer; /** - * A class representing the IP configuration of the Ethernet network. + * A class that manages and configures Ethernet interfaces. * * @hide */ @@ -54,11 +58,13 @@ public class EthernetManager { private final IEthernetServiceListener.Stub mServiceListener = new IEthernetServiceListener.Stub() { @Override - public void onAvailabilityChanged(String iface, boolean isAvailable) { + public void onInterfaceStateChanged(String iface, int state, int role, + IpConfiguration configuration) { synchronized (mListeners) { for (ListenerInfo li : mListeners) { li.executor.execute(() -> - li.listener.onAvailabilityChanged(iface, isAvailable)); + li.listener.onInterfaceStateChanged(iface, state, role, + configuration)); } } } @@ -68,19 +74,93 @@ public class EthernetManager { @NonNull public final Executor executor; @NonNull - public final Listener listener; + public final InterfaceStateListener listener; - private ListenerInfo(@NonNull Executor executor, @NonNull Listener listener) { + private ListenerInfo(@NonNull Executor executor, @NonNull InterfaceStateListener listener) { this.executor = executor; this.listener = listener; } } /** + * The interface is absent. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int STATE_ABSENT = 0; + + /** + * The interface is present but link is down. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int STATE_LINK_DOWN = 1; + + /** + * The interface is present and link is up. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int STATE_LINK_UP = 2; + + /** @hide */ + @IntDef(prefix = "STATE_", value = {STATE_ABSENT, STATE_LINK_DOWN, STATE_LINK_UP}) + @Retention(RetentionPolicy.SOURCE) + public @interface InterfaceState {} + + /** + * The interface currently does not have any specific role. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int ROLE_NONE = 0; + + /** + * The interface is in client mode (e.g., connected to the Internet). + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int ROLE_CLIENT = 1; + + /** + * Ethernet interface is in server mode (e.g., providing Internet access to tethered devices). + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int ROLE_SERVER = 2; + + /** @hide */ + @IntDef(prefix = "ROLE_", value = {ROLE_NONE, ROLE_CLIENT, ROLE_SERVER}) + @Retention(RetentionPolicy.SOURCE) + public @interface Role {} + + /** + * A listener that receives notifications about the state of Ethernet interfaces on the system. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public interface InterfaceStateListener { + /** + * Called when an Ethernet interface changes state. + * + * @param iface the name of the interface. + * @param state the current state of the interface, or {@link #STATE_ABSENT} if the + * interface was removed. + * @param role whether the interface is in the client mode or server mode. + * @param configuration the current IP configuration of the interface. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + void onInterfaceStateChanged(@NonNull String iface, @InterfaceState int state, + @Role int role, @Nullable IpConfiguration configuration); + } + + /** * A listener interface to receive notification on changes in Ethernet. + * This has never been a supported API. Use {@link InterfaceStateListener} instead. * @hide */ - public interface Listener { + public interface Listener extends InterfaceStateListener { /** * Called when Ethernet port's availability is changed. * @param iface Ethernet interface name @@ -89,6 +169,13 @@ public class EthernetManager { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) void onAvailabilityChanged(String iface, boolean isAvailable); + + /** Default implementation for backwards compatibility. Only calls the legacy listener. */ + default void onInterfaceStateChanged(@NonNull String iface, @InterfaceState int state, + @Role int role, @Nullable IpConfiguration configuration) { + onAvailabilityChanged(iface, (state >= STATE_LINK_UP)); + } + } /** @@ -121,7 +208,7 @@ public class EthernetManager { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void setConfiguration(String iface, IpConfiguration config) { + public void setConfiguration(@NonNull String iface, @NonNull IpConfiguration config) { try { mService.setConfiguration(iface, config); } catch (RemoteException e) { @@ -155,9 +242,8 @@ public class EthernetManager { /** * Adds a listener. + * This has never been a supported API. Use {@link #addInterfaceStateListener} instead. * - * Consider using {@link #addListener(Listener, Executor)} instead: this method uses a default - * executor that may have higher latency than a provided executor. * @param listener A {@link Listener} to add. * @throws IllegalArgumentException If the listener is null. * @hide @@ -169,6 +255,8 @@ public class EthernetManager { /** * Adds a listener. + * This has never been a supported API. Use {@link #addInterfaceStateListener} instead. + * * @param listener A {@link Listener} to add. * @param executor Executor to run callbacks on. * @throws IllegalArgumentException If the listener or executor is null. @@ -176,6 +264,28 @@ public class EthernetManager { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void addListener(@NonNull Listener listener, @NonNull Executor executor) { + addInterfaceStateListener(executor, listener); + } + + /** + * Listen to changes in the state of Ethernet interfaces. + * + * Adds a listener to receive notification for any state change of all existing Ethernet + * interfaces. + * <p>{@link Listener#onInterfaceStateChanged} will be triggered immediately for all + * existing interfaces upon adding a listener. The same method will be called on the + * listener every time any of the interface changes state. In particular, if an + * interface is removed, it will be called with state {@link #STATE_ABSENT}. + * <p>Use {@link #removeInterfaceStateListener} with the same object to stop listening. + * + * @param executor Executor to run callbacks on. + * @param listener A {@link Listener} to add. + * @hide + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @SystemApi(client = MODULE_LIBRARIES) + public void addInterfaceStateListener(@NonNull Executor executor, + @NonNull InterfaceStateListener listener) { if (listener == null || executor == null) { throw new NullPointerException("listener and executor must not be null"); } @@ -206,15 +316,13 @@ public class EthernetManager { /** * Removes a listener. + * * @param listener A {@link Listener} to remove. - * @throws IllegalArgumentException If the listener is null. * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void removeListener(@NonNull Listener listener) { - if (listener == null) { - throw new IllegalArgumentException("listener must not be null"); - } + @SystemApi(client = MODULE_LIBRARIES) + public void removeInterfaceStateListener(@NonNull InterfaceStateListener listener) { + Objects.requireNonNull(listener); synchronized (mListeners) { mListeners.removeIf(l -> l.listener == listener); if (mListeners.isEmpty()) { @@ -228,12 +336,26 @@ public class EthernetManager { } /** + * Removes a listener. + * This has never been a supported API. Use {@link #removeInterfaceStateListener} instead. + * @param listener A {@link Listener} to remove. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public void removeListener(@NonNull Listener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + removeInterfaceStateListener(listener); + } + + /** * Whether to treat interfaces created by {@link TestNetworkManager#createTapInterface} * as Ethernet interfaces. The effects of this method apply to any test interfaces that are * already present on the system. * @hide */ - @TestApi + @SystemApi(client = MODULE_LIBRARIES) public void setIncludeTestInterfaces(boolean include) { try { mService.setIncludeTestInterfaces(include); diff --git a/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl index 782fa19d9df7..6d2ba03f78d4 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl @@ -16,8 +16,11 @@ package android.net; +import android.net.IpConfiguration; + /** @hide */ oneway interface IEthernetServiceListener { - void onAvailabilityChanged(String iface, boolean isAvailable); + void onInterfaceStateChanged(String iface, int state, int role, + in IpConfiguration configuration); } diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java index 4ebaf2b8cdb8..a48f94b66def 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java @@ -20,6 +20,7 @@ import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkTemplate.NETWORK_TYPE_ALL; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import android.annotation.IntDef; import android.annotation.NonNull; @@ -86,6 +87,7 @@ public class NetworkIdentity { final int mType; final int mRatType; + final int mSubId; final String mSubscriberId; final String mWifiNetworkKey; final boolean mRoaming; @@ -96,7 +98,7 @@ public class NetworkIdentity { /** @hide */ public NetworkIdentity( int type, int ratType, @Nullable String subscriberId, @Nullable String wifiNetworkKey, - boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged) { + boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged, int subId) { mType = type; mRatType = ratType; mSubscriberId = subscriberId; @@ -105,12 +107,13 @@ public class NetworkIdentity { mMetered = metered; mDefaultNetwork = defaultNetwork; mOemManaged = oemManaged; + mSubId = subId; } @Override public int hashCode() { return Objects.hash(mType, mRatType, mSubscriberId, mWifiNetworkKey, mRoaming, mMetered, - mDefaultNetwork, mOemManaged); + mDefaultNetwork, mOemManaged, mSubId); } @Override @@ -122,7 +125,8 @@ public class NetworkIdentity { && Objects.equals(mWifiNetworkKey, ident.mWifiNetworkKey) && mMetered == ident.mMetered && mDefaultNetwork == ident.mDefaultNetwork - && mOemManaged == ident.mOemManaged; + && mOemManaged == ident.mOemManaged + && mSubId == ident.mSubId; } return false; } @@ -150,6 +154,7 @@ public class NetworkIdentity { builder.append(", metered=").append(mMetered); builder.append(", defaultNetwork=").append(mDefaultNetwork); builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged)); + builder.append(", subId=").append(mSubId); return builder.append("}").toString(); } @@ -256,6 +261,11 @@ public class NetworkIdentity { return mOemManaged; } + /** Get the SubId of this instance. */ + public int getSubId() { + return mSubId; + } + /** * Assemble a {@link NetworkIdentity} from the passed arguments. * @@ -276,7 +286,8 @@ public class NetworkIdentity { public static NetworkIdentity buildNetworkIdentity(Context context, @NonNull NetworkStateSnapshot snapshot, boolean defaultNetwork, int ratType) { final NetworkIdentity.Builder builder = new NetworkIdentity.Builder() - .setNetworkStateSnapshot(snapshot).setDefaultNetwork(defaultNetwork); + .setNetworkStateSnapshot(snapshot).setDefaultNetwork(defaultNetwork) + .setSubId(snapshot.getSubId()); if (snapshot.getLegacyType() == TYPE_MOBILE && ratType != NETWORK_TYPE_ALL) { builder.setRatType(ratType); } @@ -325,6 +336,9 @@ public class NetworkIdentity { if (res == 0) { res = Integer.compare(left.mOemManaged, right.mOemManaged); } + if (res == 0) { + res = Integer.compare(left.mSubId, right.mSubId); + } return res; } @@ -345,6 +359,7 @@ public class NetworkIdentity { private boolean mMetered; private boolean mDefaultNetwork; private int mOemManaged; + private int mSubId; /** * Creates a new Builder. @@ -359,6 +374,7 @@ public class NetworkIdentity { mMetered = false; mDefaultNetwork = false; mOemManaged = NetworkTemplate.OEM_MANAGED_NO; + mSubId = INVALID_SUBSCRIPTION_ID; } /** @@ -537,6 +553,19 @@ public class NetworkIdentity { return this; } + /** + * Set the Subscription Id. + * + * @param subId the Subscription Id of the network. Or INVALID_SUBSCRIPTION_ID if not + * applicable. + * @return this builder. + */ + @NonNull + public Builder setSubId(int subId) { + mSubId = subId; + return this; + } + private void ensureValidParameters() { // Assert non-mobile network cannot have a ratType. if (mType != TYPE_MOBILE && mRatType != NetworkTemplate.NETWORK_TYPE_ALL) { @@ -559,7 +588,7 @@ public class NetworkIdentity { public NetworkIdentity build() { ensureValidParameters(); return new NetworkIdentity(mType, mRatType, mSubscriberId, mWifiNetworkKey, - mRoaming, mMetered, mDefaultNetwork, mOemManaged); + mRoaming, mMetered, mDefaultNetwork, mOemManaged, mSubId); } } } diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java index 2236d70c3502..56461babfe49 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java @@ -17,6 +17,7 @@ package android.net; import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import android.annotation.NonNull; import android.service.NetworkIdentitySetProto; @@ -42,6 +43,7 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> { private static final int VERSION_ADD_METERED = 4; private static final int VERSION_ADD_DEFAULT_NETWORK = 5; private static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6; + private static final int VERSION_ADD_SUB_ID = 7; /** * Construct a {@link NetworkIdentitySet} object. @@ -103,8 +105,15 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> { oemNetCapabilities = NetworkIdentity.OEM_NONE; } + final int subId; + if (version >= VERSION_ADD_SUB_ID) { + subId = in.readInt(); + } else { + subId = INVALID_SUBSCRIPTION_ID; + } + add(new NetworkIdentity(type, ratType, subscriberId, networkId, roaming, metered, - defaultNetwork, oemNetCapabilities)); + defaultNetwork, oemNetCapabilities, subId)); } } @@ -113,7 +122,7 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> { * @hide */ public void writeToStream(DataOutput out) throws IOException { - out.writeInt(VERSION_ADD_OEM_MANAGED_NETWORK); + out.writeInt(VERSION_ADD_SUB_ID); out.writeInt(size()); for (NetworkIdentity ident : this) { out.writeInt(ident.getType()); @@ -124,6 +133,7 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> { out.writeBoolean(ident.isMetered()); out.writeBoolean(ident.isDefaultNetwork()); out.writeInt(ident.getOemManaged()); + out.writeInt(ident.getSubId()); } } diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java index d577aa8fba54..c018e916551e 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java @@ -17,6 +17,8 @@ package android.net; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import android.annotation.NonNull; import android.annotation.Nullable; @@ -98,12 +100,29 @@ public final class NetworkStateSnapshot implements Parcelable { return mLinkProperties; } - /** Get the Subscriber Id of the network associated with this snapshot. */ + /** + * Get the Subscriber Id of the network associated with this snapshot. + * @deprecated Please use #getSubId, which doesn't return personally identifiable + * information. + */ + @Deprecated @Nullable public String getSubscriberId() { return mSubscriberId; } + /** Get the subId of the network associated with this snapshot. */ + public int getSubId() { + if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { + final NetworkSpecifier spec = mNetworkCapabilities.getNetworkSpecifier(); + if (spec instanceof TelephonyNetworkSpecifier) { + return ((TelephonyNetworkSpecifier) spec).getSubscriptionId(); + } + } + return INVALID_SUBSCRIPTION_ID; + } + + /** * Get the legacy type of the network associated with this snapshot. * @return the legacy network type. See {@code ConnectivityManager#TYPE_*}. diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java index e366ca13df79..e8b3d4cd1310 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java @@ -1067,7 +1067,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStats getUidStatsForTransport(int transport) { - enforceAnyPermissionOf(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); + PermissionUtils.enforceNetworkStackPermission(mContext); try { final String[] relevantIfaces = transport == TRANSPORT_WIFI ? mWifiIfaces : mMobileIfaces; @@ -1484,10 +1484,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.isMetered()) { // Copy the identify from IMS one but mark it as metered. - NetworkIdentity vtIdent = new NetworkIdentity(ident.getType(), - ident.getRatType(), ident.getSubscriberId(), ident.getWifiNetworkKey(), - ident.isRoaming(), true /* metered */, - true /* onDefaultNetwork */, ident.getOemManaged()); + NetworkIdentity vtIdent = new NetworkIdentity.Builder() + .setType(ident.getType()) + .setRatType(ident.getRatType()) + .setSubscriberId(ident.getSubscriberId()) + .setWifiNetworkKey(ident.getWifiNetworkKey()) + .setRoaming(ident.isRoaming()).setMetered(true) + .setDefaultNetwork(true) + .setOemManaged(ident.getOemManaged()) + .setSubId(ident.getSubId()).build(); final String ifaceVt = IFACE_VT + getSubIdForMobile(snapshot); findOrCreateNetworkIdentitySet(mActiveIfaces, ifaceVt).add(vtIdent); findOrCreateNetworkIdentitySet(mActiveUidIfaces, ifaceVt).add(vtIdent); diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index f7b297461f15..17db45dcaeb3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -138,8 +138,6 @@ public class RestrictedPreferenceHelper { return true; } if (mDisabledByAppOps) { - Preconditions.checkState(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU, - "Build SDK version needs >= T"); RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, packageName, uid); return true; diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index 5c05a1bd6722..342189d8aa4c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -61,6 +61,7 @@ public class RestrictedSwitchPreference extends SwitchPreference { final TypedValue restrictedSwitchSummary = attributes.peekValue( R.styleable.RestrictedSwitchPreference_restrictedSwitchSummary); + attributes.recycle(); if (restrictedSwitchSummary != null && restrictedSwitchSummary.type == TypedValue.TYPE_STRING) { if (restrictedSwitchSummary.resourceId != 0) { diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index d179b828d4ab..bd3deae8c01a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -26,8 +26,6 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.os.RemoteException; import android.os.ServiceManager; @@ -35,21 +33,14 @@ import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.text.TextUtils; -import android.util.AttributeSet; import android.util.Log; -import android.util.Xml; import com.android.settingslib.R; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; @@ -185,15 +176,18 @@ public class DreamBackend { dreamInfo.componentName = componentName; dreamInfo.isActive = dreamInfo.componentName.equals(activeDream); - final DreamMetadata dreamMetadata = getDreamMetadata(pm, resolveInfo); - dreamInfo.settingsComponentName = dreamMetadata.mSettingsActivity; - dreamInfo.previewImage = dreamMetadata.mPreviewImage; + final DreamService.DreamMetadata dreamMetadata = DreamService.getDreamMetadata(mContext, + resolveInfo.serviceInfo); + if (dreamMetadata != null) { + dreamInfo.settingsComponentName = dreamMetadata.settingsActivity; + dreamInfo.previewImage = dreamMetadata.previewImage; + } if (dreamInfo.previewImage == null) { dreamInfo.previewImage = mDreamPreviewDefault; } dreamInfos.add(dreamInfo); } - Collections.sort(dreamInfos, mComparator); + dreamInfos.sort(mComparator); return dreamInfos; } @@ -450,25 +444,31 @@ public class DreamBackend { } final Intent intent = new Intent() .setComponent(dreamInfo.settingsComponentName) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + .addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); uiContext.startActivity(intent); } - public void preview(DreamInfo dreamInfo) { - logd("preview(%s)", dreamInfo); - if (mDreamManager == null || dreamInfo == null || dreamInfo.componentName == null) + /** + * Preview a dream, given the component name. + */ + public void preview(ComponentName componentName) { + logd("preview(%s)", componentName); + if (mDreamManager == null || componentName == null) { return; + } try { - mDreamManager.testDream(mContext.getUserId(), dreamInfo.componentName); + mDreamManager.testDream(mContext.getUserId(), componentName); } catch (RemoteException e) { - Log.w(TAG, "Failed to preview " + dreamInfo, e); + Log.w(TAG, "Failed to preview " + componentName, e); } } public void startDreaming() { logd("startDreaming()"); - if (mDreamManager == null) + if (mDreamManager == null) { return; + } try { mDreamManager.dream(); } catch (RemoteException e) { @@ -483,78 +483,6 @@ public class DreamBackend { return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); } - private static final class DreamMetadata { - @Nullable - Drawable mPreviewImage; - @Nullable - ComponentName mSettingsActivity; - } - - @Nullable - private static TypedArray readMetadata(PackageManager pm, ServiceInfo serviceInfo) { - if (serviceInfo == null || serviceInfo.metaData == null) { - return null; - } - try (XmlResourceParser parser = - serviceInfo.loadXmlMetaData(pm, DreamService.DREAM_META_DATA)) { - if (parser == null) { - Log.w(TAG, "No " + DreamService.DREAM_META_DATA + " meta-data"); - return null; - } - Resources res = pm.getResourcesForApplication(serviceInfo.applicationInfo); - AttributeSet attrs = Xml.asAttributeSet(parser); - while (true) { - final int type = parser.next(); - if (type == XmlPullParser.END_DOCUMENT || type == XmlPullParser.START_TAG) { - break; - } - } - String nodeName = parser.getName(); - if (!"dream".equals(nodeName)) { - Log.w(TAG, "Meta-data does not start with dream tag"); - return null; - } - return res.obtainAttributes(attrs, com.android.internal.R.styleable.Dream); - } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) { - Log.w(TAG, "Error parsing : " + serviceInfo.packageName, e); - return null; - } - } - - private static ComponentName convertToComponentName(String flattenedString, - ServiceInfo serviceInfo) { - if (flattenedString == null) return null; - - if (flattenedString.indexOf('/') < 0) { - flattenedString = serviceInfo.packageName + "/" + flattenedString; - } - - ComponentName cn = ComponentName.unflattenFromString(flattenedString); - - if (cn == null) return null; - if (!cn.getPackageName().equals(serviceInfo.packageName)) { - Log.w(TAG, - "Inconsistent package name in component: " + cn.getPackageName() - + ", should be: " + serviceInfo.packageName); - return null; - } - - return cn; - } - - private static DreamMetadata getDreamMetadata(PackageManager pm, ResolveInfo resolveInfo) { - DreamMetadata result = new DreamMetadata(); - if (resolveInfo == null) return result; - TypedArray rawMetadata = readMetadata(pm, resolveInfo.serviceInfo); - if (rawMetadata == null) return result; - result.mSettingsActivity = convertToComponentName(rawMetadata.getString( - com.android.internal.R.styleable.Dream_settingsActivity), resolveInfo.serviceInfo); - result.mPreviewImage = rawMetadata.getDrawable( - com.android.internal.R.styleable.Dream_previewImage); - rawMetadata.recycle(); - return result; - } - private static void logd(String msg, Object... args) { if (DEBUG) { Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args)); diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java index 26e3e0432fd6..1f4cafce835f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java @@ -32,7 +32,6 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.Switch; import android.widget.Toast; @@ -156,14 +155,10 @@ public class InputMethodPreference extends PrimarySwitchPreference final int iconSize = getContext().getResources().getDimensionPixelSize( R.dimen.secondary_app_icon_size); if (icon != null && iconSize > 0) { - if (isTv()) { - ViewGroup.LayoutParams params = icon.getLayoutParams(); - params.height = iconSize; - params.width = iconSize; - icon.setLayoutParams(params); - } else { - icon.setLayoutParams(new LinearLayout.LayoutParams(iconSize, iconSize)); - } + ViewGroup.LayoutParams params = icon.getLayoutParams(); + params.height = iconSize; + params.width = iconSize; + icon.setLayoutParams(params); } } @@ -226,8 +221,8 @@ public class InputMethodPreference extends PrimarySwitchPreference setSwitchEnabled(false); } else if (!mIsAllowedByOrganization) { EnforcedAdmin admin = - RestrictedLockUtilsInternal.checkIfInputMethodDisallowed(getContext(), - mImi.getPackageName(), UserHandle.myUserId()); + RestrictedLockUtilsInternal.checkIfInputMethodDisallowed( + getContext(), mImi.getPackageName(), mUserId); setDisabledByAdmin(admin); } else { setEnabled(true); diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java index b4c95e6ee2df..2c2be0394b6e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java +++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java @@ -39,6 +39,8 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; import java.util.zip.GZIPInputStream; /** @@ -54,6 +56,7 @@ class LicenseHtmlGeneratorFromXml { private static final String TAG_FILE_NAME = "file-name"; private static final String TAG_FILE_CONTENT = "file-content"; private static final String ATTR_CONTENT_ID = "contentId"; + private static final String ATTR_LIBRARY_NAME = "lib"; private static final String HTML_HEAD_STRING = "<html><head>\n" @@ -67,8 +70,12 @@ class LicenseHtmlGeneratorFromXml { + "</style>\n" + "</head>" + "<body topmargin=\"0\" leftmargin=\"0\" rightmargin=\"0\" bottommargin=\"0\">\n" - + "<div class=\"toc\">\n" - + "<ul>"; + + "<div class=\"toc\">\n"; + private static final String LIBRARY_HEAD_STRING = + "<strong>Libraries</strong>\n<ul class=\"libraries\">"; + private static final String LIBRARY_TAIL_STRING = "</ul>\n<strong>Files</strong>"; + + private static final String FILES_HEAD_STRING = "<ul class=\"files\">"; private static final String HTML_MIDDLE_STRING = "</ul>\n" @@ -81,12 +88,14 @@ class LicenseHtmlGeneratorFromXml { private final List<File> mXmlFiles; /* - * A map from a file name to a content id (MD5 sum of file content) for its license. - * For example, "/system/priv-app/TeleService/TeleService.apk" maps to + * A map from a file name to a library name (may be empty) to a content id (MD5 sum of file + * content) for its license. + * For example, "/system/priv-app/TeleService/TeleService.apk" maps to "service/Telephony" to * "9645f39e9db895a4aa6e02cb57294595". Here "9645f39e9db895a4aa6e02cb57294595" is a MD5 sum * of the content of packages/services/Telephony/MODULE_LICENSE_APACHE2. */ - private final Map<String, Set<String>> mFileNameToContentIdMap = new HashMap(); + private final Map<String, Map<String, Set<String>>> mFileNameToLibraryToContentIdMap = + new HashMap(); /* * A map from a content id (MD5 sum of file content) to a license file content. @@ -98,7 +107,7 @@ class LicenseHtmlGeneratorFromXml { static class ContentIdAndFileNames { final String mContentId; - final List<String> mFileNameList = new ArrayList(); + final Map<String, List<String>> mLibraryToFileNameMap = new TreeMap(); ContentIdAndFileNames(String contentId) { mContentId = contentId; @@ -120,7 +129,7 @@ class LicenseHtmlGeneratorFromXml { parse(xmlFile); } - if (mFileNameToContentIdMap.isEmpty() || mContentIdToFileContentMap.isEmpty()) { + if (mFileNameToLibraryToContentIdMap.isEmpty() || mContentIdToFileContentMap.isEmpty()) { return false; } @@ -128,7 +137,7 @@ class LicenseHtmlGeneratorFromXml { try { writer = new PrintWriter(outputFile); - generateHtml(mFileNameToContentIdMap, mContentIdToFileContentMap, writer, + generateHtml(mFileNameToLibraryToContentIdMap, mContentIdToFileContentMap, writer, noticeHeader); writer.flush(); @@ -157,7 +166,7 @@ class LicenseHtmlGeneratorFromXml { in = new FileReader(xmlFile); } - parse(in, mFileNameToContentIdMap, mContentIdToFileContentMap); + parse(in, mFileNameToLibraryToContentIdMap, mContentIdToFileContentMap); in.close(); } catch (XmlPullParserException | IOException e) { @@ -180,7 +189,8 @@ class LicenseHtmlGeneratorFromXml { * * <licenses> * <file-name contentId="content_id_of_license1">file1</file-name> - * <file-name contentId="content_id_of_license2">file2</file-name> + * <file-name contentId="content_id_of_license2" lib="name of library">file2</file-name> + * <file-name contentId="content_id_of_license2" lib="another library">file2</file-name> * ... * <file-content contentId="content_id_of_license1">license1 file contents</file-content> * <file-content contentId="content_id_of_license2">license2 file contents</file-content> @@ -188,10 +198,12 @@ class LicenseHtmlGeneratorFromXml { * </licenses> */ @VisibleForTesting - static void parse(InputStreamReader in, Map<String, Set<String>> outFileNameToContentIdMap, + static void parse(InputStreamReader in, + Map<String, Map<String, Set<String>>> outFileNameToLibraryToContentIdMap, Map<String, String> outContentIdToFileContentMap) throws XmlPullParserException, IOException { - Map<String, Set<String>> fileNameToContentIdMap = new HashMap<String, Set<String>>(); + Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = + new HashMap<String, Map<String, Set<String>>>(); Map<String, String> contentIdToFileContentMap = new HashMap<String, String>(); XmlPullParser parser = Xml.newPullParser(); @@ -205,12 +217,15 @@ class LicenseHtmlGeneratorFromXml { if (state == XmlPullParser.START_TAG) { if (TAG_FILE_NAME.equals(parser.getName())) { String contentId = parser.getAttributeValue("", ATTR_CONTENT_ID); + String libraryName = parser.getAttributeValue("", ATTR_LIBRARY_NAME); if (!TextUtils.isEmpty(contentId)) { String fileName = readText(parser).trim(); if (!TextUtils.isEmpty(fileName)) { - Set<String> contentIds = - fileNameToContentIdMap.computeIfAbsent( - fileName, k -> new HashSet<>()); + Map<String, Set<String>> libs = + fileNameToLibraryToContentIdMap.computeIfAbsent( + fileName, k -> new HashMap<>()); + Set<String> contentIds = libs.computeIfAbsent( + libraryName, k -> new HashSet<>()); contentIds.add(contentId); } } @@ -229,11 +244,17 @@ class LicenseHtmlGeneratorFromXml { state = parser.next(); } - for (Map.Entry<String, Set<String>> entry : fileNameToContentIdMap.entrySet()) { - outFileNameToContentIdMap.merge( - entry.getKey(), entry.getValue(), (s1, s2) -> { - s1.addAll(s2); - return s1; + for (Map.Entry<String, Map<String, Set<String>>> mapEntry : + fileNameToLibraryToContentIdMap.entrySet()) { + outFileNameToLibraryToContentIdMap.merge( + mapEntry.getKey(), mapEntry.getValue(), (m1, m2) -> { + for (Map.Entry<String, Set<String>> entry : m2.entrySet()) { + m1.merge(entry.getKey(), entry.getValue(), (s1, s2) -> { + s1.addAll(s2); + return s1; + }); + } + return m1; }); } outContentIdToFileContentMap.putAll(contentIdToFileContentMap); @@ -251,13 +272,28 @@ class LicenseHtmlGeneratorFromXml { } @VisibleForTesting - static void generateHtml(Map<String, Set<String>> fileNameToContentIdMap, + static void generateHtml(Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap, Map<String, String> contentIdToFileContentMap, PrintWriter writer, String noticeHeader) { List<String> fileNameList = new ArrayList(); - fileNameList.addAll(fileNameToContentIdMap.keySet()); + fileNameList.addAll(fileNameToLibraryToContentIdMap.keySet()); Collections.sort(fileNameList); + SortedMap<String, Set<String>> libraryToContentIdMap = new TreeMap(); + for (Map<String, Set<String>> libraryToContentValue : + fileNameToLibraryToContentIdMap.values()) { + for (Map.Entry<String, Set<String>> entry : libraryToContentValue.entrySet()) { + if (TextUtils.isEmpty(entry.getKey())) { + continue; + } + libraryToContentIdMap.merge( + entry.getKey(), entry.getValue(), (s1, s2) -> { + s1.addAll(s2); + return s1; + }); + } + } + writer.println(HTML_HEAD_STRING); if (!TextUtils.isEmpty(noticeHeader)) { @@ -268,21 +304,56 @@ class LicenseHtmlGeneratorFromXml { Map<String, Integer> contentIdToOrderMap = new HashMap(); List<ContentIdAndFileNames> contentIdAndFileNamesList = new ArrayList(); + if (!libraryToContentIdMap.isEmpty()) { + writer.println(LIBRARY_HEAD_STRING); + for (Map.Entry<String, Set<String>> entry: libraryToContentIdMap.entrySet()) { + String libraryName = entry.getKey(); + for (String contentId : entry.getValue()) { + // Assigns an id to a newly referred license file content. + if (!contentIdToOrderMap.containsKey(contentId)) { + contentIdToOrderMap.put(contentId, count); + + // An index in contentIdAndFileNamesList is the order of each element. + contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId)); + count++; + } + int id = contentIdToOrderMap.get(contentId); + writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, libraryName); + } + } + writer.println(LIBRARY_TAIL_STRING); + } + // Prints all the file list with a link to its license file content. for (String fileName : fileNameList) { - for (String contentId : fileNameToContentIdMap.get(fileName)) { - // Assigns an id to a newly referred license file content. - if (!contentIdToOrderMap.containsKey(contentId)) { - contentIdToOrderMap.put(contentId, count); - - // An index in contentIdAndFileNamesList is the order of each element. - contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId)); - count++; + for (Map.Entry<String, Set<String>> libToContentId : + fileNameToLibraryToContentIdMap.get(fileName).entrySet()) { + String libraryName = libToContentId.getKey(); + if (libraryName == null) { + libraryName = ""; } + for (String contentId : libToContentId.getValue()) { + // Assigns an id to a newly referred license file content. + if (!contentIdToOrderMap.containsKey(contentId)) { + contentIdToOrderMap.put(contentId, count); + + // An index in contentIdAndFileNamesList is the order of each element. + contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId)); + count++; + } - int id = contentIdToOrderMap.get(contentId); - contentIdAndFileNamesList.get(id).mFileNameList.add(fileName); - writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName); + int id = contentIdToOrderMap.get(contentId); + ContentIdAndFileNames elem = contentIdAndFileNamesList.get(id); + List<String> files = elem.mLibraryToFileNameMap.computeIfAbsent( + libraryName, k -> new ArrayList()); + files.add(fileName); + if (TextUtils.isEmpty(libraryName)) { + writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName); + } else { + writer.format("<li><a href=\"#id%d\">%s - %s</a></li>\n", + id, fileName, libraryName); + } + } } } @@ -292,19 +363,27 @@ class LicenseHtmlGeneratorFromXml { // Prints all contents of the license files in order of id. for (ContentIdAndFileNames contentIdAndFileNames : contentIdAndFileNamesList) { writer.format("<tr id=\"id%d\"><td class=\"same-license\">\n", count); - writer.println("<div class=\"label\">Notices for file(s):</div>"); - writer.println("<div class=\"file-list\">"); - for (String fileName : contentIdAndFileNames.mFileNameList) { - writer.format("%s <br/>\n", fileName); + for (Map.Entry<String, List<String>> libraryFiles : + contentIdAndFileNames.mLibraryToFileNameMap.entrySet()) { + String libraryName = libraryFiles.getKey(); + if (TextUtils.isEmpty(libraryName)) { + writer.println("<div class=\"label\">Notices for file(s):</div>"); + } else { + writer.format("<div class=\"label\"><strong>%s</strong> used by:</div>\n", + libraryName); + } + writer.println("<div class=\"file-list\">"); + for (String fileName : libraryFiles.getValue()) { + writer.format("%s <br/>\n", fileName); + } + writer.println("</div><!-- file-list -->"); + count++; } - writer.println("</div><!-- file-list -->"); writer.println("<pre class=\"license-text\">"); writer.println(contentIdToFileContentMap.get( contentIdAndFileNames.mContentId)); writer.println("</pre><!-- license-text -->"); writer.println("</td></tr><!-- same-license -->"); - - count++; } writer.println(HTML_REAR_STRING); diff --git a/packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java b/packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java index 1805f1a90d10..42e3af059c3f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java +++ b/packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java @@ -46,12 +46,12 @@ public class InjectedSetting { public final String className; /** - * The {@link android.support.v7.preference.Preference#getTitle()} value. + * The {@link androidx.preference.Preference#getTitle()} value. */ public final String title; /** - * The {@link android.support.v7.preference.Preference#getIcon()} value. + * The {@link androidx.preference.Preference#getIcon()} value. */ public final int iconId; diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java index 360361ba0104..dd7db21f765c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java @@ -19,6 +19,7 @@ import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.graphics.drawable.Drawable; +import android.media.AudioManager; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; @@ -34,11 +35,13 @@ public class BluetoothMediaDevice extends MediaDevice { private static final String TAG = "BluetoothMediaDevice"; private CachedBluetoothDevice mCachedDevice; + private final AudioManager mAudioManager; BluetoothMediaDevice(Context context, CachedBluetoothDevice device, MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) { super(context, routerManager, info, packageName); mCachedDevice = device; + mAudioManager = context.getSystemService(AudioManager.class); initDeviceRecord(); } @@ -98,6 +101,12 @@ public class BluetoothMediaDevice extends MediaDevice { } @Override + public boolean isMutingExpectedDevice() { + return mAudioManager.getMutingExpectedDevice() != null && mCachedDevice.getAddress().equals( + mAudioManager.getMutingExpectedDevice().getAddress()); + } + + @Override public boolean isConnected() { return mCachedDevice.getBondState() == BluetoothDevice.BOND_BONDED && mCachedDevice.isConnected(); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java new file mode 100644 index 000000000000..6c0eab3fb016 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.media; + +import android.annotation.DrawableRes; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.media.AudioDeviceInfo; +import android.media.MediaRoute2Info; + +import com.android.settingslib.R; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** A util class to get the appropriate icon for different device types. */ +public class DeviceIconUtil { + // A map from a @AudioDeviceInfo.AudioDeviceType to full device information. + private final Map<Integer, Device> mAudioDeviceTypeToIconMap = new HashMap<>(); + // A map from a @MediaRoute2Info.Type to full device information. + private final Map<Integer, Device> mMediaRouteTypeToIconMap = new HashMap<>(); + // A default icon to use if the type is not present in the map. + @DrawableRes private static final int DEFAULT_ICON = R.drawable.ic_smartphone; + + public DeviceIconUtil() { + List<Device> deviceList = Arrays.asList( + new Device( + AudioDeviceInfo.TYPE_USB_DEVICE, + MediaRoute2Info.TYPE_USB_DEVICE, + R.drawable.ic_headphone), + new Device( + AudioDeviceInfo.TYPE_USB_HEADSET, + MediaRoute2Info.TYPE_USB_HEADSET, + R.drawable.ic_headphone), + new Device( + AudioDeviceInfo.TYPE_USB_ACCESSORY, + MediaRoute2Info.TYPE_USB_ACCESSORY, + R.drawable.ic_headphone), + new Device( + AudioDeviceInfo.TYPE_DOCK, + MediaRoute2Info.TYPE_DOCK, + R.drawable.ic_headphone), + new Device( + AudioDeviceInfo.TYPE_HDMI, + MediaRoute2Info.TYPE_HDMI, + R.drawable.ic_headphone), + new Device( + AudioDeviceInfo.TYPE_WIRED_HEADSET, + MediaRoute2Info.TYPE_WIRED_HEADSET, + R.drawable.ic_headphone), + new Device( + AudioDeviceInfo.TYPE_WIRED_HEADPHONES, + MediaRoute2Info.TYPE_WIRED_HEADPHONES, + R.drawable.ic_headphone), + new Device( + AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, + MediaRoute2Info.TYPE_BUILTIN_SPEAKER, + R.drawable.ic_smartphone)); + for (int i = 0; i < deviceList.size(); i++) { + Device device = deviceList.get(i); + mAudioDeviceTypeToIconMap.put(device.mAudioDeviceType, device); + mMediaRouteTypeToIconMap.put(device.mMediaRouteType, device); + } + } + + /** Returns a drawable for an icon representing the given audioDeviceType. */ + public Drawable getIconFromAudioDeviceType( + @AudioDeviceInfo.AudioDeviceType int audioDeviceType, Context context) { + return context.getDrawable(getIconResIdFromAudioDeviceType(audioDeviceType)); + } + + /** Returns a drawable res ID for an icon representing the given audioDeviceType. */ + @DrawableRes + public int getIconResIdFromAudioDeviceType( + @AudioDeviceInfo.AudioDeviceType int audioDeviceType) { + if (mAudioDeviceTypeToIconMap.containsKey(audioDeviceType)) { + return mAudioDeviceTypeToIconMap.get(audioDeviceType).mIconDrawableRes; + } + return DEFAULT_ICON; + } + + /** Returns a drawable res ID for an icon representing the given mediaRouteType. */ + @DrawableRes + public int getIconResIdFromMediaRouteType( + @MediaRoute2Info.Type int mediaRouteType) { + if (mMediaRouteTypeToIconMap.containsKey(mediaRouteType)) { + return mMediaRouteTypeToIconMap.get(mediaRouteType).mIconDrawableRes; + } + return DEFAULT_ICON; + } + + private static class Device { + @AudioDeviceInfo.AudioDeviceType + private final int mAudioDeviceType; + + @MediaRoute2Info.Type + private final int mMediaRouteType; + + @DrawableRes + private final int mIconDrawableRes; + + Device(@AudioDeviceInfo.AudioDeviceType int audioDeviceType, + @MediaRoute2Info.Type int mediaRouteType, + @DrawableRes int iconDrawableRes) { + mAudioDeviceType = audioDeviceType; + mMediaRouteType = mediaRouteType; + mIconDrawableRes = iconDrawableRes; + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index b5facf34d1e7..31d5921a8673 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -21,6 +21,7 @@ import android.app.Notification; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.Context; +import android.graphics.drawable.Drawable; import android.media.RoutingSessionInfo; import android.os.Build; import android.text.TextUtils; @@ -227,6 +228,18 @@ public class LocalMediaManager implements BluetoothCallback { } /** + * Dispatch a change in the about-to-connect device. See + * {@link DeviceCallback#onAboutToConnectDeviceChanged} for more information. + */ + public void dispatchAboutToConnectDeviceChanged( + @Nullable String deviceName, + @Nullable Drawable deviceIcon) { + for (DeviceCallback callback : getCallbacks()) { + callback.onAboutToConnectDeviceChanged(deviceName, deviceIcon); + } + } + + /** * Stop scan MediaDevice */ public void stopScan() { @@ -674,6 +687,21 @@ public class LocalMediaManager implements BluetoothCallback { * {@link android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND}, */ default void onRequestFailed(int reason){}; + + /** + * Callback for notifying that we have a new about-to-connect device. + * + * An about-to-connect device is a device that is not yet connected but is expected to + * connect imminently and should be displayed as the current device in the media player. + * See [AudioManager.muteAwaitConnection] for more details. + * + * @param deviceName the name of the device (displayed to the user). + * @param deviceIcon the icon that should be used with the device. + */ + default void onAboutToConnectDeviceChanged( + @Nullable String deviceName, + @Nullable Drawable deviceIcon + ) {} } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index 970abff3054b..c7599623c465 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -320,6 +320,13 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { } if (mType == another.mType) { + // Check device is muting expected device + if (isMutingExpectedDevice()) { + return -1; + } else if (another.isMutingExpectedDevice()) { + return 1; + } + // Check fast pair device if (isFastPairDevice()) { return -1; @@ -392,6 +399,14 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { return false; } + /** + * Check if it is muting expected device + * @return {@code true} if it is muting expected device, otherwise return {@code false} + */ + protected boolean isMutingExpectedDevice() { + return false; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof MediaDevice)) { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index c16ecb558712..921c24526445 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -47,10 +47,12 @@ public class PhoneMediaDevice extends MediaDevice { private String mSummary = ""; + private final DeviceIconUtil mDeviceIconUtil; + PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) { super(context, routerManager, info, packageName); - + mDeviceIconUtil = new DeviceIconUtil(); initDeviceRecord(); } @@ -94,23 +96,7 @@ public class PhoneMediaDevice extends MediaDevice { @VisibleForTesting int getDrawableResId() { - int resId; - switch (mRouteInfo.getType()) { - case TYPE_USB_DEVICE: - case TYPE_USB_HEADSET: - case TYPE_USB_ACCESSORY: - case TYPE_DOCK: - case TYPE_HDMI: - case TYPE_WIRED_HEADSET: - case TYPE_WIRED_HEADPHONES: - resId = R.drawable.ic_headphone; - break; - case TYPE_BUILTIN_SPEAKER: - default: - resId = R.drawable.ic_smartphone; - break; - } - return resId; + return mDeviceIconUtil.getIconResIdFromMediaRouteType(mRouteInfo.getType()); } @Override diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java index e87461f85762..e348865019ec 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java @@ -36,7 +36,7 @@ import java.util.Set; @RunWith(RobolectricTestRunner.class) public class LicenseHtmlGeneratorFromXmlTest { - private static final String VALILD_XML_STRING = + private static final String VALID_OLD_XML_STRING = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<licenses>\n" + "<file-name contentId=\"0\">/file0</file-name>\n" @@ -44,7 +44,15 @@ public class LicenseHtmlGeneratorFromXmlTest { + "<file-content contentId=\"0\"><![CDATA[license content #0]]></file-content>\n" + "</licenses>"; - private static final String INVALILD_XML_STRING = + private static final String VALID_NEW_XML_STRING = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<licenses>\n" + + "<file-name contentId=\"0\" lib=\"libA\">/file0</file-name>\n" + + "<file-name contentId=\"0\" lib=\"libB\">/file1</file-name>\n" + + "<file-content contentId=\"0\"><![CDATA[license content #0]]></file-content>\n" + + "</licenses>"; + + private static final String INVALID_XML_STRING = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<licenses2>\n" + "<file-name contentId=\"0\">/file0</file-name>\n" @@ -64,13 +72,13 @@ public class LicenseHtmlGeneratorFromXmlTest { + "</style>\n" + "</head>" + "<body topmargin=\"0\" leftmargin=\"0\" rightmargin=\"0\" bottommargin=\"0\">\n" - + "<div class=\"toc\">\n" - + "<ul>\n"; + + "<div class=\"toc\">\n"; private static final String HTML_CUSTOM_HEADING = "Custom heading"; - private static final String HTML_BODY_STRING = - "<li><a href=\"#id0\">/file0</a></li>\n" + private static final String HTML_OLD_BODY_STRING = + "<ul class=\"files\">\n" + + "<li><a href=\"#id0\">/file0</a></li>\n" + "<li><a href=\"#id1\">/file0</a></li>\n" + "<li><a href=\"#id0\">/file1</a></li>\n" + "</ul>\n" @@ -97,66 +105,181 @@ public class LicenseHtmlGeneratorFromXmlTest { + "</td></tr><!-- same-license -->\n" + "</table></body></html>\n"; - private static final String EXPECTED_HTML_STRING = HTML_HEAD_STRING + HTML_BODY_STRING; + private static final String HTML_NEW_BODY_STRING = + "<strong>Libraries</strong>\n" + + "<ul class=\"libraries\">\n" + + "<li><a href=\"#id0\">libA</a></li>\n" + + "<li><a href=\"#id1\">libB</a></li>\n" + + "</ul>\n" + + "<strong>Files</strong>\n" + + "<ul class=\"files\">\n" + + "<li><a href=\"#id0\">/file0 - libA</a></li>\n" + + "<li><a href=\"#id1\">/file0 - libB</a></li>\n" + + "<li><a href=\"#id0\">/file1 - libA</a></li>\n" + + "</ul>\n" + + "</div><!-- table of contents -->\n" + + "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n" + + "<tr id=\"id0\"><td class=\"same-license\">\n" + + "<div class=\"label\">Notices for file(s):</div>\n" + + "<div class=\"file-list\">\n" + + "/file0 <br/>\n" + + "/file1 <br/>\n" + + "</div><!-- file-list -->\n" + + "<pre class=\"license-text\">\n" + + "license content #0\n" + + "</pre><!-- license-text -->\n" + + "</td></tr><!-- same-license -->\n" + + "<tr id=\"id1\"><td class=\"same-license\">\n" + + "<div class=\"label\">Notices for file(s):</div>\n" + + "<div class=\"file-list\">\n" + + "/file0 <br/>\n" + + "</div><!-- file-list -->\n" + + "<pre class=\"license-text\">\n" + + "license content #1\n" + + "</pre><!-- license-text -->\n" + + "</td></tr><!-- same-license -->\n" + + "</table></body></html>\n"; + + private static final String EXPECTED_OLD_HTML_STRING = HTML_HEAD_STRING + HTML_OLD_BODY_STRING; + + private static final String EXPECTED_NEW_HTML_STRING = HTML_HEAD_STRING + HTML_NEW_BODY_STRING; + + private static final String EXPECTED_OLD_HTML_STRING_WITH_CUSTOM_HEADING = + HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n" + HTML_OLD_BODY_STRING; - private static final String EXPECTED_HTML_STRING_WITH_CUSTOM_HEADING = - HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n" + HTML_BODY_STRING; + private static final String EXPECTED_NEW_HTML_STRING_WITH_CUSTOM_HEADING = + HTML_HEAD_STRING + HTML_CUSTOM_HEADING + "\n" + HTML_NEW_BODY_STRING; @Test public void testParseValidXmlStream() throws XmlPullParserException, IOException { - Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>(); + Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>(); Map<String, String> contentIdToFileContentMap = new HashMap<>(); LicenseHtmlGeneratorFromXml.parse( - new InputStreamReader(new ByteArrayInputStream(VALILD_XML_STRING.getBytes())), - fileNameToContentIdMap, contentIdToFileContentMap); - assertThat(fileNameToContentIdMap.size()).isEqualTo(2); - assertThat(fileNameToContentIdMap.get("/file0")).containsExactly("0"); - assertThat(fileNameToContentIdMap.get("/file1")).containsExactly("0"); + new InputStreamReader(new ByteArrayInputStream(VALID_OLD_XML_STRING.getBytes())), + fileNameToLibraryToContentIdMap, contentIdToFileContentMap); + assertThat(fileNameToLibraryToContentIdMap.size()).isEqualTo(1); + assertThat(fileNameToLibraryToContentIdMap.get("").size()).isEqualTo(2); + assertThat(fileNameToLibraryToContentIdMap.get("").get("/file0")).containsExactly("0"); + assertThat(fileNameToLibraryToContentIdMap.get("").get("/file1")).containsExactly("0"); + assertThat(contentIdToFileContentMap.size()).isEqualTo(1); + assertThat(contentIdToFileContentMap.get("0")).isEqualTo("license content #0"); + } + + @Test + public void testParseNewValidXmlStream() throws XmlPullParserException, IOException { + Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>(); + Map<String, String> contentIdToFileContentMap = new HashMap<>(); + + LicenseHtmlGeneratorFromXml.parse( + new InputStreamReader(new ByteArrayInputStream(VALID_NEW_XML_STRING.getBytes())), + fileNameToLibraryToContentIdMap, contentIdToFileContentMap); + assertThat(fileNameToLibraryToContentIdMap.size()).isEqualTo(2); + assertThat(fileNameToLibraryToContentIdMap.get("libA").size()).isEqualTo(1); + assertThat(fileNameToLibraryToContentIdMap.get("libB").size()).isEqualTo(1); + assertThat(fileNameToLibraryToContentIdMap.get("libA").get("/file0")).containsExactly("0"); + assertThat(fileNameToLibraryToContentIdMap.get("libB").get("/file1")).containsExactly("0"); assertThat(contentIdToFileContentMap.size()).isEqualTo(1); assertThat(contentIdToFileContentMap.get("0")).isEqualTo("license content #0"); } @Test(expected = XmlPullParserException.class) public void testParseInvalidXmlStream() throws XmlPullParserException, IOException { - Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>(); + Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>(); Map<String, String> contentIdToFileContentMap = new HashMap<>(); LicenseHtmlGeneratorFromXml.parse( - new InputStreamReader(new ByteArrayInputStream(INVALILD_XML_STRING.getBytes())), - fileNameToContentIdMap, contentIdToFileContentMap); + new InputStreamReader(new ByteArrayInputStream(INVALID_XML_STRING.getBytes())), + fileNameToLibraryToContentIdMap, contentIdToFileContentMap); } @Test public void testGenerateHtml() { - Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>(); + Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>(); + Map<String, String> contentIdToFileContentMap = new HashMap<>(); + Map<String, Set<String>> toBoth = new HashMap<>(); + Map<String, Set<String>> toOne = new HashMap<>(); + + toBoth.put("", new HashSet<String>(Arrays.asList("0", "1"))); + toOne.put("", new HashSet<String>(Arrays.asList("0"))); + + fileNameToLibraryToContentIdMap.put("/file0", toBoth); + fileNameToLibraryToContentIdMap.put("/file1", toOne); + contentIdToFileContentMap.put("0", "license content #0"); + contentIdToFileContentMap.put("1", "license content #1"); + + StringWriter output = new StringWriter(); + LicenseHtmlGeneratorFromXml.generateHtml( + fileNameToLibraryToContentIdMap, contentIdToFileContentMap, + new PrintWriter(output), ""); + assertThat(output.toString()).isEqualTo(EXPECTED_OLD_HTML_STRING); + } + + @Test + public void testGenerateNewHtml() { + Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>(); Map<String, String> contentIdToFileContentMap = new HashMap<>(); + Map<String, Set<String>> toBoth = new HashMap<>(); + Map<String, Set<String>> toOne = new HashMap<>(); + + toBoth.put("libA", new HashSet<String>(Arrays.asList("0"))); + toBoth.put("libB", new HashSet<String>(Arrays.asList("1"))); + toOne.put("libA", new HashSet<String>(Arrays.asList("0"))); - fileNameToContentIdMap.put("/file0", new HashSet<String>(Arrays.asList("0", "1"))); - fileNameToContentIdMap.put("/file1", new HashSet<String>(Arrays.asList("0"))); + fileNameToLibraryToContentIdMap.put("/file0", toBoth); + fileNameToLibraryToContentIdMap.put("/file1", toOne); contentIdToFileContentMap.put("0", "license content #0"); contentIdToFileContentMap.put("1", "license content #1"); StringWriter output = new StringWriter(); LicenseHtmlGeneratorFromXml.generateHtml( - fileNameToContentIdMap, contentIdToFileContentMap, new PrintWriter(output), ""); - assertThat(output.toString()).isEqualTo(EXPECTED_HTML_STRING); + fileNameToLibraryToContentIdMap, contentIdToFileContentMap, + new PrintWriter(output), ""); + assertThat(output.toString()).isEqualTo(EXPECTED_NEW_HTML_STRING); } @Test public void testGenerateHtmlWithCustomHeading() { - Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>(); + Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>(); + Map<String, String> contentIdToFileContentMap = new HashMap<>(); + Map<String, Set<String>> toBoth = new HashMap<>(); + Map<String, Set<String>> toOne = new HashMap<>(); + + toBoth.put("", new HashSet<String>(Arrays.asList("0", "1"))); + toOne.put("", new HashSet<String>(Arrays.asList("0"))); + + fileNameToLibraryToContentIdMap.put("/file0", toBoth); + fileNameToLibraryToContentIdMap.put("/file1", toOne); + contentIdToFileContentMap.put("0", "license content #0"); + contentIdToFileContentMap.put("1", "license content #1"); + + StringWriter output = new StringWriter(); + LicenseHtmlGeneratorFromXml.generateHtml( + fileNameToLibraryToContentIdMap, contentIdToFileContentMap, + new PrintWriter(output), HTML_CUSTOM_HEADING); + assertThat(output.toString()).isEqualTo(EXPECTED_OLD_HTML_STRING_WITH_CUSTOM_HEADING); + } + + @Test + public void testGenerateNewHtmlWithCustomHeading() { + Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>(); Map<String, String> contentIdToFileContentMap = new HashMap<>(); + Map<String, Set<String>> toBoth = new HashMap<>(); + Map<String, Set<String>> toOne = new HashMap<>(); + + toBoth.put("libA", new HashSet<String>(Arrays.asList("0"))); + toBoth.put("libB", new HashSet<String>(Arrays.asList("1"))); + toOne.put("libA", new HashSet<String>(Arrays.asList("0"))); - fileNameToContentIdMap.put("/file0", new HashSet<String>(Arrays.asList("0", "1"))); - fileNameToContentIdMap.put("/file1", new HashSet<String>(Arrays.asList("0"))); + fileNameToLibraryToContentIdMap.put("/file0", toBoth); + fileNameToLibraryToContentIdMap.put("/file1", toOne); contentIdToFileContentMap.put("0", "license content #0"); contentIdToFileContentMap.put("1", "license content #1"); StringWriter output = new StringWriter(); LicenseHtmlGeneratorFromXml.generateHtml( - fileNameToContentIdMap, contentIdToFileContentMap, new PrintWriter(output), - HTML_CUSTOM_HEADING); - assertThat(output.toString()).isEqualTo(EXPECTED_HTML_STRING_WITH_CUSTOM_HEADING); + fileNameToLibraryToContentIdMap, contentIdToFileContentMap, + new PrintWriter(output), HTML_CUSTOM_HEADING); + assertThat(output.toString()).isEqualTo(EXPECTED_NEW_HTML_STRING_WITH_CUSTOM_HEADING); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java new file mode 100644 index 000000000000..72dfc1733275 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java @@ -0,0 +1,150 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.media; + +import static com.google.common.truth.Truth.assertThat; + +import android.media.AudioDeviceInfo; +import android.media.MediaRoute2Info; + +import com.android.settingslib.R; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class DeviceIconUtilTest { + private final DeviceIconUtil mDeviceIconUtil = new DeviceIconUtil(); + + @Test + public void getIconResIdFromMediaRouteType_usbDevice_isHeadphone() { + assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_DEVICE)) + .isEqualTo(R.drawable.ic_headphone); + } + + @Test + public void getIconResIdFromMediaRouteType_usbHeadset_isHeadphone() { + assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_HEADSET)) + .isEqualTo(R.drawable.ic_headphone); + } + + @Test + public void getIconResIdFromMediaRouteType_usbAccessory_isHeadphone() { + assertThat( + mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_ACCESSORY)) + .isEqualTo(R.drawable.ic_headphone); + } + + @Test + public void getIconResIdFromMediaRouteType_dock_isHeadphone() { + assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_DOCK)) + .isEqualTo(R.drawable.ic_headphone); + } + + @Test + public void getIconResIdFromMediaRouteType_hdmi_isHeadphone() { + assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_HDMI)) + .isEqualTo(R.drawable.ic_headphone); + } + + @Test + public void getIconResIdFromMediaRouteType_wiredHeadset_isHeadphone() { + assertThat( + mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_WIRED_HEADSET)) + .isEqualTo(R.drawable.ic_headphone); + } + + @Test + public void getIconResIdFromMediaRouteType_wiredHeadphones_isHeadphone() { + assertThat( + mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_WIRED_HEADPHONES)) + .isEqualTo(R.drawable.ic_headphone); + } + + @Test + public void getIconResIdFromMediaRouteType_builtinSpeaker_isSmartphone() { + assertThat( + mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER)) + .isEqualTo(R.drawable.ic_smartphone); + } + + @Test + public void getIconResIdFromMediaRouteType_unsupportedType_isSmartphone() { + assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_UNKNOWN)) + .isEqualTo(R.drawable.ic_smartphone); + } + + @Test + public void getIconResIdFromAudioDeviceType_usbDevice_isHeadphone() { + assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_DEVICE)) + .isEqualTo(R.drawable.ic_headphone); + } + + @Test + public void getIconResIdFromAudioDeviceType_usbHeadset_isHeadphone() { + assertThat( + mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_HEADSET)) + .isEqualTo(R.drawable.ic_headphone); + } + + @Test + public void getIconResIdFromAudioDeviceType_usbAccessory_isHeadphone() { + assertThat( + mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_ACCESSORY)) + .isEqualTo(R.drawable.ic_headphone); + } + + @Test + public void getIconResIdFromAudioDeviceType_dock_isHeadphone() { + assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_DOCK)) + .isEqualTo(R.drawable.ic_headphone); + } + + @Test + public void getIconResIdFromAudioDeviceType_hdmi_isHeadphone() { + assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_HDMI)) + .isEqualTo(R.drawable.ic_headphone); + } + + @Test + public void getIconResIdFromAudioDeviceType_wiredHeadset_isHeadphone() { + assertThat( + mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_WIRED_HEADSET)) + .isEqualTo(R.drawable.ic_headphone); + } + + @Test + public void getIconResIdFromAudioDeviceType_wiredHeadphones_isHeadphone() { + assertThat( + mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_WIRED_HEADPHONES)) + .isEqualTo(R.drawable.ic_headphone); + } + + @Test + public void getIconResIdFromAudioDeviceType_builtinSpeaker_isSmartphone() { + assertThat( + mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)) + .isEqualTo(R.drawable.ic_smartphone); + } + + @Test + public void getIconResIdFromAudioDeviceType_unsupportedType_isSmartphone() { + assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_UNKNOWN)) + .isEqualTo(R.drawable.ic_smartphone); + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 86ee3b3ddcfb..0c6d40aa9910 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -652,29 +652,31 @@ public class SettingsBackupAgent extends BackupAgentHelper { return Collections.emptySet(); } - Cursor cursor = getContentResolver().query(settingsUri, new String[] { - Settings.NameValueTable.NAME, Settings.NameValueTable.IS_PRESERVED_IN_RESTORE }, - /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null); - - if (!cursor.moveToFirst()) { - Slog.i(TAG, "No settings to be preserved in restore"); - return Collections.emptySet(); - } + try (Cursor cursor = getContentResolver().query(settingsUri, new String[]{ + Settings.NameValueTable.NAME, + Settings.NameValueTable.IS_PRESERVED_IN_RESTORE}, + /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null)) { + + if (!cursor.moveToFirst()) { + Slog.i(TAG, "No settings to be preserved in restore"); + return Collections.emptySet(); + } - int nameIndex = cursor.getColumnIndex(Settings.NameValueTable.NAME); - int isPreservedIndex = cursor.getColumnIndex( - Settings.NameValueTable.IS_PRESERVED_IN_RESTORE); + int nameIndex = cursor.getColumnIndex(Settings.NameValueTable.NAME); + int isPreservedIndex = cursor.getColumnIndex( + Settings.NameValueTable.IS_PRESERVED_IN_RESTORE); - Set<String> preservedSettings = new HashSet<>(); - while (!cursor.isAfterLast()) { - if (Boolean.parseBoolean(cursor.getString(isPreservedIndex))) { - preservedSettings.add(getQualifiedKeyForSetting(cursor.getString(nameIndex), - settingsUri)); + Set<String> preservedSettings = new HashSet<>(); + while (!cursor.isAfterLast()) { + if (Boolean.parseBoolean(cursor.getString(isPreservedIndex))) { + preservedSettings.add(getQualifiedKeyForSetting(cursor.getString(nameIndex), + settingsUri)); + } + cursor.moveToNext(); } - cursor.moveToNext(); - } - return preservedSettings; + return preservedSettings; + } } /** diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index e9f940a65e83..741fe4f22e74 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -603,6 +603,9 @@ <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" /> + <!-- Permission required for CTS test - CtsAmbientContextDetectionServiceDeviceTest --> + <uses-permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT" /> + <!-- Permission required for CTS test - CallAudioInterceptionTest --> <uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION" /> diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt index 7ef0901a27c9..2e9a16fffe9a 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt @@ -24,6 +24,7 @@ import android.os.IBinder import android.os.RemoteException import android.util.ArrayMap import android.util.Log +import android.util.RotationUtils import android.view.IRemoteAnimationFinishedCallback import android.view.IRemoteAnimationRunner import android.view.RemoteAnimationAdapter @@ -345,39 +346,33 @@ class RemoteTransitionAdapter { * Sets up this rotator. * * @param rotateDelta is the forward rotation change (the rotation the display is making). - * @param displayW (and H) Is the size of the rotating display. + * @param parentW (and H) Is the size of the rotating parent. */ fun setup( t: SurfaceControl.Transaction, parent: SurfaceControl, rotateDelta: Int, - displayW: Float, - displayH: Float + parentW: Float, + parentH: Float ) { - var rotateDelta = rotateDelta if (rotateDelta == 0) return - // We want to counter-rotate, so subtract from 4 - rotateDelta = 4 - (rotateDelta + 4) % 4 - surface = SurfaceControl.Builder() + val surface = SurfaceControl.Builder() .setName("Transition Unrotate") .setContainerLayer() .setParent(parent) .build() - // column-major - when (rotateDelta) { - 1 -> { - t.setMatrix(surface, 0f, 1f, -1f, 0f) - t.setPosition(surface!!, displayW, 0f) - } - 2 -> { - t.setMatrix(surface, -1f, 0f, 0f, -1f) - t.setPosition(surface!!, displayW, displayH) - } - 3 -> { - t.setMatrix(surface, 0f, -1f, 1f, 0f) - t.setPosition(surface!!, 0f, displayH) - } - } + // Rotate forward to match the new rotation (rotateDelta is the forward rotation the + // parent already took). Child surfaces will be in the old rotation relative to the new + // parent rotation, so we need to forward-rotate the child surfaces to match. + RotationUtils.rotateSurface(t, surface, rotateDelta) + val tmpPt = Point(0, 0) + // parentW/H are the size in the END rotation, the rotation utilities expect the + // starting size. So swap them if necessary + val flipped = rotateDelta % 2 != 0 + val pw = if (flipped) parentH else parentW + val ph = if (flipped) parentW else parentH + RotationUtils.rotatePoint(tmpPt, rotateDelta, pw.toInt(), ph.toInt()) + t.setPosition(surface, tmpPt.x.toFloat(), tmpPt.y.toFloat()) t.show(surface) } diff --git a/packages/SystemUI/docs/keyguard/aod.md b/packages/SystemUI/docs/keyguard/aod.md index 6d76ed55174a..7f89984abdb3 100644 --- a/packages/SystemUI/docs/keyguard/aod.md +++ b/packages/SystemUI/docs/keyguard/aod.md @@ -1 +1,77 @@ # Always-on Display (AOD) + +AOD provides an alternatative 'screen-off' experience. Instead, of completely turning the display off, it provides a distraction-free, glanceable experience for the phone in a low-powered mode. In this low-powered mode, the display will have a lower refresh rate and the UI should frequently shift its displayed contents in order to prevent burn-in. + +The default doze component is specified by `config_dozeComponent` in the [framework config][1]. SystemUI provides a default Doze Component: [DozeService][2]. [DozeService][2] builds a [DozeMachine][3] with dependencies specified in [DozeModule][4] and configurations in [AmbientDisplayConfiguration][13] and [DozeParameters][14]. + +[DozeMachine][3] handles the following main states: +* AOD - persistently showing UI when the device is in a low-powered state +* Pulsing - waking up the screen to show notifications (from AOD and screen off) +* Docked UI - UI to show when the device is docked +* Wake-up gestures - including lift to wake and tap to wake (from AOD and screen off) + +## Doze States ([see DozeMachine.State][3]) +### DOZE +Device is asleep and listening for enabled pulsing and wake-up gesture triggers. In this state, no UI shows. + +### DOZE_AOD +Device is asleep, showing UI, and listening for enabled pulsing and wake-up triggers. In this state, screen brightness is handled by [DozeScreenBrightness][5] which uses the brightness sensor specified by `doze_brightness_sensor_type` in the [SystemUI config][6]. To save power, this should be a low-powered sensor that shouldn't trigger as often as the light sensor used for on-screen adaptive brightness. + +### DOZE_AOD_PAUSED +Device is asleep and would normally be in state `DOZE_AOD`; however, instead the display is temporarily off since the proximity sensor reported near for a minimum abount of time. [DozePauser][7] handles transitioning from `DOZE_AOD_PAUSING` after the minimum timeout after the NEAR is reported by the proximity sensor from [DozeTriggers][8]). + +### DOZE_PULSING +Device is awake and showing UI. This is state typically occurs in response to incoming notification, but may also be from other pulse triggers specified in [DozeTriggers][8]. + +### DOZE_AOD_DOCKED +Device is awake, showing docking UI and listening for enabled pulsing and wake-up triggers. The default DockManager is provided by an empty interface at [DockManagerImpl][9]. SystemUI should override the DockManager for the DozeService to handle docking events. + +[DozeDockHandler][11] listens for Dock state changes from [DockManager][10] and updates the doze docking state. + +## Wake-up gestures +Doze sensors are registered in [DozeTriggers][8] via [DozeSensors][12]. Sensors can be configured per posture for foldable devices. + +Relevant sensors include: +* Proximity sensor +* Brightness sensor +* Wake-up gestures + * tap to wake + * double tap to wake + * lift to wake + * significant motion + +And are configured in the [AmbientDisplayConfiguration][13] with some related configurations specified in [DozeParameters][14]. + +## Debugging Tips +Enable DozeLog to print directly to logcat: +``` +adb shell settings put global systemui/buffer/DozeLog v +``` + +Enable all DozeService logs to print directly to logcat: +``` +adb shell setprop log.tag.DozeService DEBUG +``` + +Other helpful dumpsys commands (`adb shell dumpsys <service>`): +* activity service com.android.systemui/.doze.DozeService +* activity service com.android.systemui/.SystemUIService +* display +* power +* dreams +* sensorservice + +[1]: /frameworks/base/core/res/res/values/config.xml +[2]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeService.java +[3]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +[4]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java +[5]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +[6]: /frameworks/base/packages/SystemUI/res/values/config.xml +[7]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java +[8]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +[9]: /frameworks/base/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java +[10]: /frameworks/base/packages/SystemUI/src/com/android/systemui/dock/DockManager.java +[11]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java +[12]: /frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +[13]: /frameworks/base/core/java/android/hardware/display/AmbientDisplayConfiguration.java +[14]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java index c7bc858c8266..757ed76eff36 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java @@ -25,6 +25,8 @@ import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.plugins.annotations.DependsOn; import com.android.systemui.plugins.annotations.ProvidesInterface; +import java.util.ArrayList; + /** * Dispatches events to {@link DarkReceiver}s about changes in darkness, tint area and dark * intensity. Accessible through {@link PluginDependency} @@ -32,15 +34,15 @@ import com.android.systemui.plugins.annotations.ProvidesInterface; @ProvidesInterface(version = DarkIconDispatcher.VERSION) @DependsOn(target = DarkReceiver.class) public interface DarkIconDispatcher { - int VERSION = 1; + int VERSION = 2; /** * Sets the dark area so {@link #applyDark} only affects the icons in the specified area. * - * @param r the area in which icons should change its tint, in logical screen + * @param r the areas in which icons should change its tint, in logical screen * coordinates */ - void setIconsDarkArea(Rect r); + void setIconsDarkArea(ArrayList<Rect> r); /** * Adds a receiver to receive callbacks onDarkChanged @@ -76,8 +78,8 @@ public interface DarkIconDispatcher { * @return the tint to apply to view depending on the desired tint color and * the screen tintArea in which to apply that tint */ - static int getTint(Rect tintArea, View view, int color) { - if (isInArea(tintArea, view)) { + static int getTint(ArrayList<Rect> tintAreas, View view, int color) { + if (isInAreas(tintAreas, view)) { return color; } else { return DEFAULT_ICON_TINT; @@ -85,15 +87,16 @@ public interface DarkIconDispatcher { } /** - * @return the dark intensity to apply to view depending on the desired dark - * intensity and the screen tintArea in which to apply that intensity + * @return true if more than half of the view area are in any of the given + * areas, false otherwise */ - static float getDarkIntensity(Rect tintArea, View view, float intensity) { - if (isInArea(tintArea, view)) { - return intensity; - } else { - return 0f; + static boolean isInAreas(ArrayList<Rect> areas, View view) { + for (Rect area : areas) { + if (isInArea(area, view)) { + return true; + } } + return false; } /** @@ -122,7 +125,7 @@ public interface DarkIconDispatcher { */ @ProvidesInterface(version = DarkReceiver.VERSION) interface DarkReceiver { - int VERSION = 1; - void onDarkChanged(Rect area, float darkIntensity, int tint); + int VERSION = 2; + void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint); } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java deleted file mode 100644 index 6d1408d5d212..000000000000 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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.systemui.plugins.qs; - -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.view.View; -import android.view.ViewGroup; - -import com.android.internal.logging.UiEventLogger; -import com.android.systemui.plugins.annotations.ProvidesInterface; - -@ProvidesInterface(version = DetailAdapter.VERSION) -public interface DetailAdapter { - public static final int VERSION = 1; - - CharSequence getTitle(); - Boolean getToggleState(); - - default boolean getToggleEnabled() { - return true; - } - - View createDetailView(Context context, View convertView, ViewGroup parent); - - /** - * @return intent for opening more settings related to this detail panel. If null, the more - * settings button will not be shown - */ - Intent getSettingsIntent(); - - /** - * @return resource id of the string to use for opening the settings intent. If - * {@code Resources.ID_NULL}, then use the default string: - * {@code com.android.systemui.R.string.quick_settings_more_settings} - */ - default int getSettingsText() { - return Resources.ID_NULL; - } - - /** - * @return resource id of the string to use for closing the detail panel. If - * {@code Resources.ID_NULL}, then use the default string: - * {@code com.android.systemui.R.string.quick_settings_done} - */ - default int getDoneText() { - return Resources.ID_NULL; - } - - void setToggleState(boolean state); - int getMetricsCategory(); - - /** - * Indicates whether the detail view wants to have its header (back button, title and - * toggle) shown. - */ - default boolean hasHeader() { - return true; - } - - /** - * Indicates whether the detail view wants to animate when shown. This has no affect over the - * closing animation. Detail panels will always animate when closed. - */ - default boolean shouldAnimate() { - return true; - } - - /** - * @return true if the callback handled the event and wants to keep the detail panel open, false - * otherwise. Returning false will close the panel. - */ - default boolean onDoneButtonClicked() { - return false; - } - - default UiEventLogger.UiEventEnum openDetailEvent() { - return INVALID; - } - - default UiEventLogger.UiEventEnum closeDetailEvent() { - return INVALID; - } - - default UiEventLogger.UiEventEnum moreSettingsEvent() { - return INVALID; - } - - UiEventLogger.UiEventEnum INVALID = () -> 0; -} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index 1ef532407761..669d6a3835ac 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -35,14 +35,12 @@ import java.util.function.Supplier; @ProvidesInterface(version = QSTile.VERSION) @DependsOn(target = QSIconView.class) -@DependsOn(target = DetailAdapter.class) @DependsOn(target = Callback.class) @DependsOn(target = Icon.class) @DependsOn(target = State.class) public interface QSTile { - int VERSION = 2; + int VERSION = 3; - DetailAdapter getDetailAdapter(); String getTileSpec(); boolean isAvailable(); @@ -117,12 +115,9 @@ public interface QSTile { } @ProvidesInterface(version = Callback.VERSION) - public interface Callback { - public static final int VERSION = 1; + interface Callback { + static final int VERSION = 2; void onStateChanged(State state); - void onShowDetail(boolean show); - void onToggleStateChanged(boolean state); - void onScanStateChanged(boolean state); } @ProvidesInterface(version = Icon.VERSION) diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index ce63b446efb7..6352f81b4474 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -1,3 +1,6 @@ +# Preserve line number information for debugging stack traces. +-keepattributes SourceFile,LineNumberTable + -keep class com.android.systemui.recents.OverviewProxyRecentsImpl -keep class com.android.systemui.statusbar.car.CarStatusBar -keep class com.android.systemui.statusbar.phone.StatusBar diff --git a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml index 5343411e4c95..59f87da79a11 100644 --- a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml +++ b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml @@ -16,9 +16,8 @@ --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="0dp" + android:layout_width="match_parent" android:layout_height="@dimen/qs_security_footer_single_line_height" - android:layout_weight="1" android:gravity="center" android:clickable="true" android:visibility="gone"> diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml index e8244433260d..a25ab5109fa8 100644 --- a/packages/SystemUI/res-keyguard/values/config.xml +++ b/packages/SystemUI/res-keyguard/values/config.xml @@ -28,5 +28,6 @@ <!-- Will display the bouncer on one side of the display, and the current user icon and user switcher on the other side --> <bool name="config_enableBouncerUserSwitcher">false</bool> - + <!-- Time to be considered a consecutive fingerprint failure in ms --> + <integer name="fp_consecutive_failure_time_ms">3500</integer> </resources> diff --git a/packages/SystemUI/res/color-night/qs_detail_progress_track.xml b/packages/SystemUI/res/color-night/qs_detail_progress_track.xml deleted file mode 100644 index c56382e6047f..000000000000 --- a/packages/SystemUI/res/color-night/qs_detail_progress_track.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <!-- I really don't want to define this, but the View that uses this asset uses both the - light and dark accent colors. --> - <item android:alpha="0.6" android:drawable="@*android:color/accent_device_default_light" /> -</selector> diff --git a/packages/SystemUI/res/color/qs_detail_progress_track.xml b/packages/SystemUI/res/color/qs_detail_progress_track.xml deleted file mode 100644 index d86119fc01fe..000000000000 --- a/packages/SystemUI/res/color/qs_detail_progress_track.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <!-- I really don't want to define this, but the View that uses this asset uses both the - light and dark accent colors. --> - <item android:alpha="0.6" android:drawable="@*android:color/accent_device_default_dark" /> -</selector> diff --git a/packages/SystemUI/res/drawable/ic_account_circle.xml b/packages/SystemUI/res/drawable/ic_account_circle.xml new file mode 100644 index 000000000000..5ca99f32771b --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_account_circle.xml @@ -0,0 +1,24 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@android:color/white" + android:pathData="M5.85,17.1Q7.125,16.125 8.7,15.562Q10.275,15 12,15Q13.725,15 15.3,15.562Q16.875,16.125 18.15,17.1Q19.025,16.075 19.513,14.775Q20,13.475 20,12Q20,8.675 17.663,6.337Q15.325,4 12,4Q8.675,4 6.338,6.337Q4,8.675 4,12Q4,13.475 4.488,14.775Q4.975,16.075 5.85,17.1ZM12,13Q10.525,13 9.512,11.988Q8.5,10.975 8.5,9.5Q8.5,8.025 9.512,7.012Q10.525,6 12,6Q13.475,6 14.488,7.012Q15.5,8.025 15.5,9.5Q15.5,10.975 14.488,11.988Q13.475,13 12,13ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM12,20Q13.325,20 14.5,19.613Q15.675,19.225 16.65,18.5Q15.675,17.775 14.5,17.387Q13.325,17 12,17Q10.675,17 9.5,17.387Q8.325,17.775 7.35,18.5Q8.325,19.225 9.5,19.613Q10.675,20 12,20ZM12,11Q12.65,11 13.075,10.575Q13.5,10.15 13.5,9.5Q13.5,8.85 13.075,8.425Q12.65,8 12,8Q11.35,8 10.925,8.425Q10.5,8.85 10.5,9.5Q10.5,10.15 10.925,10.575Q11.35,11 12,11ZM12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5ZM12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_account_circle_filled.xml b/packages/SystemUI/res/drawable/ic_account_circle_filled.xml new file mode 100644 index 000000000000..47c553b52123 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_account_circle_filled.xml @@ -0,0 +1,27 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#FFFFFF" + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM18.36,16.83c-1.43,-1.74 -4.9,-2.33 -6.36,-2.33s-4.93,0.59 -6.36,2.33A7.95,7.95 0,0 1,4 12c0,-4.41 3.59,-8 8,-8s8,3.59 8,8c0,1.82 -0.62,3.49 -1.64,4.83z"/> + <path + android:fillColor="#FFFFFF" + android:pathData="M12,6c-1.94,0 -3.5,1.56 -3.5,3.5S10.06,13 12,13s3.5,-1.56 3.5,-3.5S13.94,6 12,6z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_add_supervised_user.xml b/packages/SystemUI/res/drawable/ic_add_supervised_user.xml new file mode 100644 index 000000000000..627743ed1669 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_add_supervised_user.xml @@ -0,0 +1,19 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<layer-list + xmlns:android="http://schemas.android.com/apk/res/android" > + <item android:drawable="@*android:drawable/ic_add_supervised_user" /> +</layer-list> diff --git a/packages/SystemUI/res/drawable/ic_manage_users.xml b/packages/SystemUI/res/drawable/ic_manage_users.xml new file mode 100644 index 000000000000..3a0805de1230 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_manage_users.xml @@ -0,0 +1,23 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path android:fillColor="@android:color/white" + android:pathData="M10,12Q8.35,12 7.175,10.825Q6,9.65 6,8Q6,6.35 7.175,5.175Q8.35,4 10,4Q11.65,4 12.825,5.175Q14,6.35 14,8Q14,9.65 12.825,10.825Q11.65,12 10,12ZM2,20V17.2Q2,16.375 2.425,15.65Q2.85,14.925 3.6,14.55Q4.875,13.9 6.475,13.45Q8.075,13 10,13Q10.2,13 10.35,13Q10.5,13 10.65,13.05Q10.45,13.5 10.312,13.988Q10.175,14.475 10.1,15H10Q8.225,15 6.812,15.45Q5.4,15.9 4.5,16.35Q4.275,16.475 4.138,16.7Q4,16.925 4,17.2V18H10.3Q10.45,18.525 10.7,19.038Q10.95,19.55 11.25,20ZM16,21L15.7,19.5Q15.4,19.375 15.137,19.238Q14.875,19.1 14.6,18.9L13.15,19.35L12.15,17.65L13.3,16.65Q13.25,16.3 13.25,16Q13.25,15.7 13.3,15.35L12.15,14.35L13.15,12.65L14.6,13.1Q14.875,12.9 15.137,12.762Q15.4,12.625 15.7,12.5L16,11H18L18.3,12.5Q18.6,12.625 18.863,12.775Q19.125,12.925 19.4,13.15L20.85,12.65L21.85,14.4L20.7,15.4Q20.75,15.7 20.75,16.025Q20.75,16.35 20.7,16.65L21.85,17.65L20.85,19.35L19.4,18.9Q19.125,19.1 18.863,19.238Q18.6,19.375 18.3,19.5L18,21ZM17,18Q17.825,18 18.413,17.413Q19,16.825 19,16Q19,15.175 18.413,14.587Q17.825,14 17,14Q16.175,14 15.588,14.587Q15,15.175 15,16Q15,16.825 15.588,17.413Q16.175,18 17,18ZM10,10Q10.825,10 11.413,9.412Q12,8.825 12,8Q12,7.175 11.413,6.588Q10.825,6 10,6Q9.175,6 8.588,6.588Q8,7.175 8,8Q8,8.825 8.588,9.412Q9.175,10 10,10ZM10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8ZM10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/qs_detail_background.xml b/packages/SystemUI/res/drawable/qs_detail_background.xml deleted file mode 100644 index c23649d629bd..000000000000 --- a/packages/SystemUI/res/drawable/qs_detail_background.xml +++ /dev/null @@ -1,33 +0,0 @@ -<!-- -Copyright (C) 2014 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<transition xmlns:android="http://schemas.android.com/apk/res/android"> - <item> - <inset> - <shape> - <solid android:color="@android:color/transparent"/> - <corners android:radius="@dimen/qs_footer_action_corner_radius" /> - </shape> - </inset> - </item> - <item> - <inset> - <shape> - <solid android:color="?android:attr/colorBackgroundFloating"/> - <corners android:radius="@dimen/qs_footer_action_corner_radius" /> - </shape> - </inset> - </item> -</transition>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml deleted file mode 100644 index 78655c03dd73..000000000000 --- a/packages/SystemUI/res/layout/qs_detail.xml +++ /dev/null @@ -1,62 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2014 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<!-- Extends LinearLayout --> -<com.android.systemui.qs.QSDetail - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@drawable/qs_detail_background" - android:layout_marginTop="@dimen/qs_detail_margin_top" - android:clickable="true" - android:orientation="vertical" - android:paddingBottom="8dp" - android:visibility="invisible" - android:elevation="4dp" - android:importantForAccessibility="no" > - - <include - android:id="@+id/qs_detail_header" - layout="@layout/qs_detail_header" - android:layout_width="match_parent" - android:layout_height="wrap_content" - /> - - <com.android.systemui.statusbar.AlphaOptimizedImageView - android:id="@+id/qs_detail_header_progress" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:alpha="0" - android:background="@color/qs_detail_progress_track" - android:src="@drawable/indeterminate_anim" - android:scaleType="fitXY" - /> - - <ScrollView - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" - android:fillViewport="true"> - - <FrameLayout - android:id="@android:id/content" - android:layout_width="match_parent" - android:layout_height="match_parent"/> - </ScrollView> - - <include layout="@layout/qs_detail_buttons" /> - -</com.android.systemui.qs.QSDetail> diff --git a/packages/SystemUI/res/layout/qs_detail_buttons.xml b/packages/SystemUI/res/layout/qs_detail_buttons.xml deleted file mode 100644 index 75f43f9a5808..000000000000 --- a/packages/SystemUI/res/layout/qs_detail_buttons.xml +++ /dev/null @@ -1,44 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - 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. ---> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingEnd="8dp" - android:gravity="end"> - - <TextView - android:id="@android:id/button2" - style="@style/QSBorderlessButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="8dp" - android:minHeight="48dp" - android:minWidth="132dp" - android:textAppearance="@style/TextAppearance.QS.DetailButton" - android:focusable="true" /> - - <TextView - android:id="@android:id/button1" - style="@style/QSBorderlessButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:minHeight="48dp" - android:minWidth="88dp" - android:textAppearance="@style/TextAppearance.QS.DetailButton" - android:focusable="true"/> - -</LinearLayout> diff --git a/packages/SystemUI/res/layout/qs_detail_header.xml b/packages/SystemUI/res/layout/qs_detail_header.xml deleted file mode 100644 index d1ab054dd335..000000000000 --- a/packages/SystemUI/res/layout/qs_detail_header.xml +++ /dev/null @@ -1,65 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2014 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<com.android.keyguard.AlphaOptimizedLinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingLeft="@dimen/qs_detail_header_padding" - android:paddingTop="@dimen/qs_detail_header_padding" - android:paddingBottom="@dimen/qs_detail_items_padding_top" - android:paddingEnd="@dimen/qs_panel_padding" - android:background="@drawable/btn_borderless_rect" - android:orientation="vertical" - android:gravity="center"> - - <com.android.systemui.ResizingSpace - android:layout_width="match_parent" - android:layout_height="@dimen/qs_detail_header_margin_top" /> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - - <TextView - android:id="@android:id/title" - android:paddingStart="@dimen/qs_detail_header_text_padding" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:textDirection="locale" - android:textAppearance="@style/TextAppearance.QS.DetailHeader" /> - - <ImageView - android:id="@+id/settings" - android:layout_width="@dimen/qs_detail_image_width" - android:layout_height="@dimen/qs_detail_image_height" - android:background="?android:attr/selectableItemBackground" - android:padding="@dimen/qs_detail_image_padding" - android:src="@drawable/ic_settings" - android:visibility="gone"/> - - <ViewStub - android:id="@+id/toggle_stub" - android:inflatedId="@+id/toggle" - android:layout="@layout/qs_detail_switch" - android:layout_width="wrap_content" - android:layout_height="wrap_content"/> - - </LinearLayout> - -</com.android.keyguard.AlphaOptimizedLinearLayout> diff --git a/packages/SystemUI/res/layout/qs_detail_item.xml b/packages/SystemUI/res/layout/qs_detail_item.xml deleted file mode 100644 index 0844bb46d53f..000000000000 --- a/packages/SystemUI/res/layout/qs_detail_item.xml +++ /dev/null @@ -1,70 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2014 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:minHeight="@dimen/qs_detail_item_height" - android:background="@drawable/btn_borderless_rect" - android:clickable="true" - android:focusable="true" - android:gravity="center_vertical" - android:orientation="horizontal" > - - <ImageView - android:id="@android:id/icon" - android:layout_width="@dimen/qs_detail_item_icon_width" - android:layout_height="@dimen/qs_detail_item_icon_size" - android:layout_marginStart="@dimen/qs_detail_item_icon_marginStart" - android:layout_marginEnd="@dimen/qs_detail_item_icon_marginEnd" - android:tint="?android:attr/textColorPrimary"/> - - <LinearLayout - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="12dp" - android:layout_weight="1" - android:orientation="vertical" > - - <TextView - android:id="@android:id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textDirection="locale" - android:ellipsize="end" - android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary" /> - - <TextView - android:id="@android:id/summary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textDirection="locale" - android:layout_marginTop="2dp" - android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary" /> - </LinearLayout> - - <ImageView - android:id="@android:id/icon2" - style="@style/QSBorderlessButton" - android:layout_width="48dp" - android:layout_height="48dp" - android:clickable="true" - android:focusable="true" - android:scaleType="center" - android:contentDescription="@*android:string/media_route_controller_disconnect" - android:tint="?android:attr/textColorPrimary" /> - -</LinearLayout> diff --git a/packages/SystemUI/res/layout/qs_detail_items.xml b/packages/SystemUI/res/layout/qs_detail_items.xml deleted file mode 100644 index 60cba67cf3d7..000000000000 --- a/packages/SystemUI/res/layout/qs_detail_items.xml +++ /dev/null @@ -1,55 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2014 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<!-- extends FrameLayout --> -<com.android.systemui.qs.QSDetailItems - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:sysui="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingStart="@dimen/qs_detail_padding_start" - android:paddingEnd="16dp"> - - <com.android.systemui.qs.AutoSizingList - android:id="@android:id/list" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - sysui:itemHeight="@dimen/qs_detail_item_height" - style="@style/AutoSizingList"/> - - <LinearLayout - android:id="@android:id/empty" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="center" - android:gravity="center" - android:orientation="vertical"> - - <ImageView - android:id="@android:id/icon" - android:layout_width="56dp" - android:layout_height="56dp" - android:tint="?android:attr/textColorSecondary" /> - - <TextView - android:id="@android:id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="20dp" - android:textAppearance="@style/TextAppearance.QS.DetailEmpty"/> - </LinearLayout> -</com.android.systemui.qs.QSDetailItems> diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index 85b33cc4cc25..20400510a31a 100644 --- a/packages/SystemUI/res/layout/qs_panel.xml +++ b/packages/SystemUI/res/layout/qs_panel.xml @@ -47,10 +47,6 @@ <include layout="@layout/quick_status_bar_expanded_header" /> - <include - android:id="@+id/qs_detail" - layout="@layout/qs_detail" /> - <ViewStub android:id="@+id/container_stub" android:inflatedId="@+id/qs_footer_actions" diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml index 3a0df2863819..0c847ed588e8 100644 --- a/packages/SystemUI/res/layout/qs_user_detail_item.xml +++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml @@ -52,7 +52,7 @@ android:gravity="center_horizontal" /> <ImageView android:id="@+id/restricted_padlock" - android:layout_width="@dimen/qs_detail_item_secondary_text_size" + android:layout_width="@dimen/qs_tile_text_size" android:layout_height="match_parent" android:gravity="center_vertical" android:src="@drawable/ic_info" diff --git a/packages/SystemUI/res/layout/rounded_corners_bottom.xml b/packages/SystemUI/res/layout/rounded_corners_bottom.xml index b2857abc19d2..bb6d4bddf25a 100644 --- a/packages/SystemUI/res/layout/rounded_corners_bottom.xml +++ b/packages/SystemUI/res/layout/rounded_corners_bottom.xml @@ -25,6 +25,7 @@ android:layout_height="12dp" android:layout_gravity="left|bottom" android:tint="#ff000000" + android:visibility="gone" android:src="@drawable/rounded_corner_bottom"/> <ImageView @@ -32,6 +33,7 @@ android:layout_width="12dp" android:layout_height="12dp" android:tint="#ff000000" + android:visibility="gone" android:layout_gravity="right|bottom" android:src="@drawable/rounded_corner_bottom"/> diff --git a/packages/SystemUI/res/layout/rounded_corners_top.xml b/packages/SystemUI/res/layout/rounded_corners_top.xml index 9937c215e71c..46648c88d921 100644 --- a/packages/SystemUI/res/layout/rounded_corners_top.xml +++ b/packages/SystemUI/res/layout/rounded_corners_top.xml @@ -25,6 +25,7 @@ android:layout_height="12dp" android:layout_gravity="left|top" android:tint="#ff000000" + android:visibility="gone" android:src="@drawable/rounded_corner_top"/> <ImageView @@ -32,6 +33,7 @@ android:layout_width="12dp" android:layout_height="12dp" android:tint="#ff000000" + android:visibility="gone" android:layout_gravity="right|top" android:src="@drawable/rounded_corner_top"/> diff --git a/packages/SystemUI/res/layout/qs_detail_switch.xml b/packages/SystemUI/res/layout/screen_decor_hwc_layer.xml index abb2497fb7be..1c17e7512491 100644 --- a/packages/SystemUI/res/layout/qs_detail_switch.xml +++ b/packages/SystemUI/res/layout/screen_decor_hwc_layer.xml @@ -1,5 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2019 The Android Open Source Project + ~ Copyright (C) 2022 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. @@ -13,11 +14,8 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<Switch +<com.android.systemui.RegionInterceptingFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@android:id/toggle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:clickable="false" - android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
\ No newline at end of file + android:id="@+id/screen_decor_hwc_container" + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml index 4f4bae49b275..816dfd35f96d 100644 --- a/packages/SystemUI/res/layout/system_icons.xml +++ b/packages/SystemUI/res/layout/system_icons.xml @@ -19,6 +19,7 @@ android:id="@+id/system_icons" android:layout_width="wrap_content" android:layout_height="match_parent" + android:layout_gravity="center_vertical|end" android:gravity="center_vertical"> <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons" diff --git a/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml b/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml deleted file mode 100644 index efe63d74b97e..000000000000 --- a/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - 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. ---> -<!-- extends LinearLayout --> -<com.android.systemui.tuner.TunerZenModePanel - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/tuner_zen_mode_panel" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:clipChildren="false" - android:visibility="gone" - android:orientation="vertical" > - - <View - android:id="@+id/zen_embedded_divider" - android:layout_width="match_parent" - android:layout_height="1dp" - android:layout_marginBottom="12dp" - android:layout_marginTop="8dp" - android:background="@color/qs_tile_divider" /> - - <include - android:layout_width="match_parent" - android:layout_height="48dp" - android:layout_marginStart="16dp" - android:id="@+id/tuner_zen_switch" - layout="@layout/qs_detail_header" /> - - <include layout="@layout/zen_mode_panel" /> - - <include - android:id="@+id/tuner_zen_buttons" - layout="@layout/qs_detail_buttons" /> - -</com.android.systemui.tuner.TunerZenModePanel> diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml index 7b95cf3cfa34..2d883bc1477f 100644 --- a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml +++ b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml @@ -21,6 +21,7 @@ android:id="@+id/user_switcher_root" android:layout_width="match_parent" android:layout_height="match_parent" + android:layout_marginBottom="40dp" android:layout_marginEnd="60dp" android:layout_marginStart="60dp"> diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml index 8d02429150f0..401c4bdd55b6 100644 --- a/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml +++ b/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml @@ -29,17 +29,19 @@ <ImageView android:id="@+id/icon" + android:scaleType="centerInside" android:layout_gravity="center" android:layout_width="20dp" android:layout_height="20dp" android:contentDescription="@null" + android:tint="@color/user_switcher_fullscreen_popup_item_tint" android:layout_marginEnd="10dp" /> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="@*android:color/text_color_primary_device_default_dark" + android:textColor="@color/user_switcher_fullscreen_popup_item_tint" android:textSize="14sp" android:layout_gravity="start" /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml deleted file mode 100644 index 58624135b85f..000000000000 --- a/packages/SystemUI/res/layout/zen_mode_panel.xml +++ /dev/null @@ -1,170 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2014 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<!-- extends LinearLayout --> -<com.android.systemui.volume.ZenModePanel xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/zen_mode_panel" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:clipChildren="false" > - - <LinearLayout - android:id="@+id/edit_container" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:attr/colorPrimary" - android:clipChildren="false" - android:orientation="vertical"> - - <com.android.systemui.volume.SegmentedButtons - android:id="@+id/zen_buttons" - android:background="@drawable/segmented_buttons_background" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginEnd="16dp" - android:layout_marginBottom="8dp" /> - - <RelativeLayout - android:id="@+id/zen_introduction" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginEnd="16dp" - android:paddingTop="8dp" - android:paddingBottom="8dp" - android:background="@drawable/zen_introduction_message_background" - android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent.Light"> - - <ImageView - android:id="@+id/zen_introduction_confirm" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_marginEnd="8dp" - android:layout_alignParentEnd="true" - android:background="@drawable/btn_borderless_rect" - android:clickable="true" - android:contentDescription="@string/accessibility_desc_close" - android:scaleType="center" - android:src="@drawable/ic_close_white_rounded" /> - - <TextView - android:id="@+id/zen_introduction_message" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="12dp" - android:layout_marginStart="24dp" - android:textDirection="locale" - android:lineSpacingMultiplier="1.20029" - android:layout_toStartOf="@id/zen_introduction_confirm" - android:textAppearance="@style/TextAppearance.QS.Introduction" /> - - <TextView - android:id="@+id/zen_introduction_customize" - style="@style/QSBorderlessButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentEnd="true" - android:layout_marginEnd="12dp" - android:layout_below="@id/zen_introduction_message" - android:clickable="true" - android:focusable="true" - android:text="@string/zen_priority_customize_button" - android:textAppearance="@style/TextAppearance.QS.DetailButton.White" /> - - <View - android:layout_width="0dp" - android:layout_height="16dp" - android:layout_below="@id/zen_introduction_message" - android:layout_alignParentEnd="true" /> - - </RelativeLayout> - - <com.android.settingslib.notification.ZenRadioLayout - android:id="@+id/zen_conditions" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:layout_marginEnd="4dp" - android:layout_marginStart="4dp" - android:paddingBottom="@dimen/zen_mode_condition_detail_bottom_padding" - android:orientation="horizontal" > - <RadioGroup - android:id="@+id/zen_radio_buttons" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> - <LinearLayout - android:id="@+id/zen_radio_buttons_content" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:orientation="vertical"/> - </com.android.settingslib.notification.ZenRadioLayout> - - <TextView - android:id="@+id/zen_alarm_warning" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginStart="18dp" - android:layout_marginEnd="16dp" - android:textDirection="locale" - android:lineSpacingMultiplier="1.20029" - android:textAppearance="@style/TextAppearance.QS.Warning" /> - </LinearLayout> - - <LinearLayout - android:id="@android:id/empty" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="center" - android:background="?android:attr/colorPrimary" - android:gravity="center" - android:orientation="vertical"> - - <ImageView - android:id="@android:id/icon" - android:layout_width="56dp" - android:layout_height="56dp" - android:alpha="?android:attr/disabledAlpha" - android:tint="?android:attr/colorForeground" /> - - <TextView - android:id="@android:id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="20dp" - android:textAppearance="@style/TextAppearance.QS.DetailEmpty"/> - </LinearLayout> - - <LinearLayout - android:id="@+id/auto_rule" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:attr/colorPrimary" - android:layout_marginStart="16dp" - android:layout_marginEnd="16dp" - android:layout_marginTop="16dp" - android:layout_marginBottom="8dp" - android:orientation="vertical"> - - <TextView - android:id="@android:id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary"/> - - </LinearLayout> - -</com.android.systemui.volume.ZenModePanel> diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml index 8c5006de577e..062e33ce7e30 100644 --- a/packages/SystemUI/res/values-land/config.xml +++ b/packages/SystemUI/res/values-land/config.xml @@ -33,4 +33,10 @@ <!-- Max number of columns for power menu --> <integer name="power_menu_max_columns">4</integer> + + <!-- Max number of columns for power menu lite --> + <integer name="power_menu_lite_max_columns">4</integer> + <!-- Max number of rows for power menu lite --> + <integer name="power_menu_lite_max_rows">2</integer> + </resources> diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index 9d24e9b97da3..01eb09b659f2 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -38,11 +38,6 @@ <dimen name="qs_footer_padding">14dp</dimen> <dimen name="qs_security_footer_background_inset">12dp</dimen> - <dimen name="battery_detail_graph_space_top">9dp</dimen> - <dimen name="battery_detail_graph_space_bottom">9dp</dimen> - - <dimen name="qs_detail_header_margin_top">14dp</dimen> - <dimen name="volume_tool_tip_top_margin">12dp</dimen> <dimen name="volume_row_slider_height">128dp</dimen> diff --git a/packages/SystemUI/res/values-sw410dp/dimens.xml b/packages/SystemUI/res/values-sw410dp/dimens.xml index d33ee9970f80..7da47e5089be 100644 --- a/packages/SystemUI/res/values-sw410dp/dimens.xml +++ b/packages/SystemUI/res/values-sw410dp/dimens.xml @@ -20,8 +20,6 @@ <!-- These resources are around just to allow their values to be customized for different hardware and product builds. --> <resources> - <dimen name="qs_detail_items_padding_top">16dp</dimen> - <!-- Global actions grid --> <dimen name="global_actions_grid_vertical_padding">8dp</dimen> <dimen name="global_actions_grid_horizontal_padding">4dp</dimen> diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index fe546f65bb13..588638f3dea5 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -33,4 +33,7 @@ <!-- Notifications are sized to match the width of two (of 4) qs tiles in landscape. --> <bool name="config_skinnyNotifsInLandscape">false</bool> + <integer name="power_menu_lite_max_columns">3</integer> + <integer name="power_menu_lite_max_rows">2</integer> + </resources> diff --git a/packages/SystemUI/res/values-sw600dp-port/config.xml b/packages/SystemUI/res/values-sw600dp-port/config.xml index 3c6a81e7c617..857e162d4f14 100644 --- a/packages/SystemUI/res/values-sw600dp-port/config.xml +++ b/packages/SystemUI/res/values-sw600dp-port/config.xml @@ -23,4 +23,8 @@ <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">3</integer> + + <integer name="power_menu_lite_max_columns">2</integer> + <integer name="power_menu_lite_max_rows">3</integer> + </resources> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index a66ed15c9d84..8f6bde56d521 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -65,9 +65,6 @@ <dimen name="qs_security_footer_single_line_height">48dp</dimen> <dimen name="qs_security_footer_background_inset">0dp</dimen> - <!-- When split shade is used, this panel should be aligned to the top --> - <dimen name="qs_detail_margin_top">0dp</dimen> - <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) --> <dimen name="large_dialog_width">472dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml index 2eff692301b1..a9e6d22461b6 100644 --- a/packages/SystemUI/res/values-television/config.xml +++ b/packages/SystemUI/res/values-television/config.xml @@ -24,29 +24,6 @@ <string name="config_systemUIFactoryComponent" translatable="false"> com.android.systemui.tv.TvSystemUIFactory </string> - <!-- SystemUI Services: The classes of the stuff to start. --> - <string-array name="config_systemUIServiceComponents" translatable="false"> - <item>com.android.systemui.util.NotificationChannels</item> - <item>com.android.systemui.volume.VolumeUI</item> - <item>com.android.systemui.privacy.television.TvOngoingPrivacyChip</item> - <item>com.android.systemui.statusbar.tv.TvStatusBar</item> - <item>com.android.systemui.statusbar.tv.notifications.TvNotificationPanel</item> - <item>com.android.systemui.statusbar.tv.notifications.TvNotificationHandler</item> - <item>com.android.systemui.statusbar.tv.VpnStatusObserver</item> - <item>com.android.systemui.globalactions.GlobalActionsComponent</item> - <item>com.android.systemui.usb.StorageNotification</item> - <item>com.android.systemui.power.PowerUI</item> - <item>com.android.systemui.media.RingtonePlayer</item> - <item>com.android.systemui.keyboard.KeyboardUI</item> - <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item> - <item>@string/config_systemUIVendorServiceComponent</item> - <item>com.android.systemui.SliceBroadcastRelayHandler</item> - <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item> - <item>com.android.systemui.accessibility.WindowMagnification</item> - <item>com.android.systemui.toast.ToastUI</item> - <item>com.android.systemui.wmshell.WMShell</item> - <item>com.android.systemui.media.systemsounds.HomeSoundEffectController</item> - </string-array> <!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. --> <integer name="recents_svelte_level">3</integer> diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index e6ab0ff9bd73..2992859b1ce7 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -114,13 +114,6 @@ <attr name="hybridNotificationTextStyle" format="reference" /> </declare-styleable> - <declare-styleable name="AutoSizingList"> - <!-- Whether AutoSizingList will show only as many items as fit on screen and - remove extra items instead of scrolling. --> - <attr name="enableAutoSizing" format="boolean" /> - <attr name="itemHeight" format="dimension" /> - </declare-styleable> - <declare-styleable name="PluginInflateContainer"> <attr name="viewType" format="string" /> </declare-styleable> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 15147786e557..faf518e73e6d 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -88,6 +88,7 @@ <color name="keyguard_user_switcher_background_gradient_color">#77000000</color> <color name="user_switcher_fullscreen_bg">@android:color/system_neutral1_900</color> + <color name="user_switcher_fullscreen_popup_item_tint">@*android:color/text_color_primary_device_default_dark</color> <!-- The color of the navigation bar icons. Need to be in sync with ic_sysbar_* --> <color name="navigation_bar_icon_color">#E5FFFFFF</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 47822b77a93f..bb1ffa8fec38 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -292,43 +292,6 @@ <!-- SystemUIFactory component --> <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string> - <!-- SystemUI Services: The classes of base stuff to start by default for all - configurations. --> - <string-array name="config_systemUIServiceComponents" translatable="false"> - <item>com.android.systemui.util.NotificationChannels</item> - <item>com.android.systemui.keyguard.KeyguardViewMediator</item> - <item>com.android.keyguard.KeyguardBiometricLockoutLogger</item> - <item>com.android.systemui.recents.Recents</item> - <item>com.android.systemui.volume.VolumeUI</item> - <item>com.android.systemui.statusbar.phone.StatusBar</item> - <item>com.android.systemui.usb.StorageNotification</item> - <item>com.android.systemui.power.PowerUI</item> - <item>com.android.systemui.media.RingtonePlayer</item> - <item>com.android.systemui.keyboard.KeyboardUI</item> - <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item> - <item>@string/config_systemUIVendorServiceComponent</item> - <item>com.android.systemui.util.leak.GarbageMonitor$Service</item> - <item>com.android.systemui.LatencyTester</item> - <item>com.android.systemui.globalactions.GlobalActionsComponent</item> - <item>com.android.systemui.ScreenDecorations</item> - <item>com.android.systemui.biometrics.AuthController</item> - <item>com.android.systemui.log.SessionTracker</item> - <item>com.android.systemui.SliceBroadcastRelayHandler</item> - <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item> - <item>com.android.systemui.theme.ThemeOverlayController</item> - <item>com.android.systemui.accessibility.WindowMagnification</item> - <item>com.android.systemui.accessibility.SystemActions</item> - <item>com.android.systemui.toast.ToastUI</item> - <item>com.android.systemui.wmshell.WMShell</item> - <item>com.android.systemui.clipboardoverlay.ClipboardListener</item> - </string-array> - - <!-- SystemUI Services: The classes of the additional stuff to start. Services here are - specified as an overlay to provide configuration-specific services that - supplement those listed in config_systemUIServiceComponents. --> - <string-array name="config_additionalSystemUIServiceComponents" translatable="false"> - </string-array> - <!-- QS tile shape store width. negative implies fill configuration instead of stroke--> <dimen name="config_qsTileStrokeWidthActive">-1dp</dimen> <dimen name="config_qsTileStrokeWidthInactive">-1dp</dimen> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index fe79f271d0b6..d1f4f1906f33 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -500,26 +500,10 @@ <dimen name="qs_panel_elevation">4dp</dimen> <dimen name="qs_panel_padding_bottom">@dimen/new_footer_height</dimen> <dimen name="qs_panel_padding_top">48dp</dimen> - <dimen name="qs_detail_header_padding">0dp</dimen> - <dimen name="qs_detail_image_width">56dp</dimen> - <dimen name="qs_detail_image_height">56dp</dimen> - <dimen name="qs_detail_image_padding">16dp</dimen> - <dimen name="qs_detail_item_height">48dp</dimen> - <dimen name="qs_detail_header_text_size">20sp</dimen> - <dimen name="qs_detail_button_text_size">14sp</dimen> - <dimen name="qs_detail_item_primary_text_size">16sp</dimen> - <dimen name="qs_detail_item_secondary_text_size">14sp</dimen> - <dimen name="qs_detail_empty_text_size">14sp</dimen> - <dimen name="qs_detail_header_margin_top">28dp</dimen> - <dimen name="qs_detail_header_text_padding">16dp</dimen> + <dimen name="qs_data_usage_text_size">14sp</dimen> <dimen name="qs_data_usage_usage_text_size">36sp</dimen> - <dimen name="qs_detail_padding_start">16dp</dimen> - <dimen name="qs_detail_items_padding_top">4dp</dimen> - <dimen name="qs_detail_item_icon_size">24dp</dimen> - <dimen name="qs_detail_item_icon_width">32dp</dimen> - <dimen name="qs_detail_item_icon_marginStart">0dp</dimen> - <dimen name="qs_detail_item_icon_marginEnd">20dp</dimen> + <dimen name="qs_header_mobile_icon_size">@dimen/status_bar_icon_drawing_size</dimen> <dimen name="qs_header_carrier_separator_width">6dp</dimen> <dimen name="qs_carrier_margin_width">4dp</dimen> @@ -533,9 +517,6 @@ <dimen name="qs_security_footer_background_inset">0dp</dimen> <dimen name="qs_security_footer_corner_radius">28dp</dimen> - <!-- Desired qs icon overlay size. --> - <dimen name="qs_detail_icon_overlay_size">24dp</dimen> - <dimen name="segmented_button_spacing">0dp</dimen> <dimen name="borderless_button_radius">2dp</dimen> @@ -547,8 +528,6 @@ <!-- Padding between subtitles and the following text in the QSFooter dialog --> <dimen name="qs_footer_dialog_subtitle_padding">20dp</dimen> - <dimen name="qs_detail_margin_top">@*android:dimen/quick_qs_offset_height</dimen> - <!-- Zen mode panel: spacing between two-line condition upper and lower lines --> <dimen name="zen_mode_condition_detail_item_interline_spacing">4dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index d39e29568986..3b7e9d46c953 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -609,8 +609,6 @@ <string name="quick_settings_inversion_label">Color inversion</string> <!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] --> <string name="quick_settings_color_correction_label">Color correction</string> - <!-- QuickSettings: Control panel: Label for button that navigates to settings. [CHAR LIMIT=NONE] --> - <string name="quick_settings_more_settings">More settings</string> <!-- QuickSettings: Control panel: Label for button that navigates to user settings. [CHAR LIMIT=NONE] --> <string name="quick_settings_more_user_settings">User settings</string> <!-- QuickSettings: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] --> @@ -1213,9 +1211,6 @@ <!-- Alarm template for far alarms [CHAR LIMIT=25] --> <string name="alarm_template_far">on <xliff:g id="when" example="Fri 7:00 AM">%1$s</xliff:g></string> - <!-- Accessibility label for Quick Settings detail screens [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_detail">Quick Settings, <xliff:g id="title" example="Wi-Fi">%s</xliff:g>.</string> - <!-- Accessibility label for hotspot icon [CHAR LIMIT=NONE] --> <string name="accessibility_status_bar_hotspot">Hotspot</string> @@ -1731,11 +1726,6 @@ <string name="data_connection_no_internet">No internet</string> <!-- accessibility label for quick settings items that open a details page [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_open_details">Open details.</string> - - <!-- accessibility label for quick settings items that are currently disabled. Must have a reason [CHAR LIMIT=NONE] --> - - <!-- accessibility label for quick settings items that open a details page [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_open_settings">Open <xliff:g name="page" example="Bluetooth">%s</xliff:g> settings.</string> <!-- accessibility label for button to edit quick settings [CHAR LIMIT=NONE] --> @@ -2402,4 +2392,8 @@ <!-- Generic "add" string [CHAR LIMIT=NONE] --> <string name="add">Add</string> + <!-- Add supervised user --> + <string name="add_user_supervised" translatable="false">@*android:string/supervised_user_creation_label</string> + <!-- Manage users - For system user management [CHAR LIMIT=40] --> + <string name="manage_users">Manage users</string> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 9448d3f342b9..b98354523e94 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -96,17 +96,12 @@ <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> </style> - <style name="TextAppearance.QS.DetailHeader"> - <item name="android:textSize">@dimen/qs_detail_header_text_size</item> - <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> - </style> - <style name="TextAppearance.QS.DetailItemPrimary"> - <item name="android:textSize">@dimen/qs_detail_item_primary_text_size</item> + <item name="android:textSize">@dimen/qs_tile_text_size</item> </style> <style name="TextAppearance.QS.DetailItemSecondary"> - <item name="android:textSize">@dimen/qs_detail_item_secondary_text_size</item> + <item name="android:textSize">@dimen/qs_tile_text_size</item> <item name="android:textColor">?android:attr/colorAccent</item> </style> @@ -120,23 +115,6 @@ <item name="android:textColor">?android:attr/colorError</item> </style> - <style name="TextAppearance.QS.DetailButton"> - <item name="android:textSize">@dimen/qs_detail_button_text_size</item> - <item name="android:textColor">?android:attr/textColorSecondary</item> - <item name="android:textAllCaps">true</item> - <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> - <item name="android:gravity">center</item> - </style> - - <style name="TextAppearance.QS.DetailButton.White"> - <item name="android:textColor">@color/zen_introduction</item> - </style> - - <style name="TextAppearance.QS.DetailEmpty"> - <item name="android:textSize">@dimen/qs_detail_empty_text_size</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> - </style> - <style name="TextAppearance.QS.SegmentedButton"> <item name="android:textSize">16sp</item> <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> @@ -167,7 +145,7 @@ </style> <style name="TextAppearance.QS.UserSwitcher"> - <item name="android:textSize">@dimen/qs_detail_item_secondary_text_size</item> + <item name="android:textSize">@dimen/qs_tile_text_size</item> <item name="android:textColor">?android:attr/textColorSecondary</item> </style> @@ -433,9 +411,6 @@ <item name="numColumns">3</item> </style> - <style name="AutoSizingList"> - <item name="enableAutoSizing">true</item> - </style> <style name="Theme.SystemUI.MediaProjectionAlertDialog"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> @@ -779,11 +754,10 @@ <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item> </style> - <style name="Theme.CreateUser" parent="@style/Theme.SystemUI"> + <style name="Theme.CreateUser" parent="@android:style/Theme.DeviceDefault.NoActionBar"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">#33000000</item> - <item name="android:windowActionBar">false</item> - <item name="android:windowNoTitle">true</item> + <item name="android:windowFullscreen">true</item> </style> <style name="Theme.PeopleTileConfigActivity" parent="@style/Theme.SystemUI"> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 2ef8d6d4d234..99e0ec13ff4a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -86,8 +86,6 @@ import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; -import androidx.lifecycle.Observer; - import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.util.LatencyTracker; @@ -109,7 +107,6 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.Assert; -import com.android.systemui.util.RingerModeTracker; import com.google.android.collect.Lists; @@ -146,7 +143,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final boolean DEBUG_ACTIVE_UNLOCK = Build.IS_DEBUGGABLE; private static final boolean DEBUG_SPEW = false; private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600; - private int mNumActiveUnlockTriggers = 0; private static final String ACTION_FACE_UNLOCK_STARTED = "com.android.facelock.FACE_UNLOCK_STARTED"; @@ -157,7 +153,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int MSG_TIME_UPDATE = 301; private static final int MSG_BATTERY_UPDATE = 302; private static final int MSG_SIM_STATE_CHANGE = 304; - private static final int MSG_RINGER_MODE_CHANGED = 305; private static final int MSG_PHONE_STATE_CHANGED = 306; private static final int MSG_DEVICE_PROVISIONED = 308; private static final int MSG_DPM_STATE_CHANGED = 309; @@ -313,7 +308,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private TrustManager mTrustManager; private UserManager mUserManager; private KeyguardBypassController mKeyguardBypassController; - private RingerModeTracker mRingerModeTracker; private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; private int mFaceRunningState = BIOMETRIC_STATE_STOPPED; private boolean mIsFaceAuthUserRequested; @@ -325,8 +319,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final InteractionJankMonitor mInteractionJankMonitor; private final LatencyTracker mLatencyTracker; private boolean mLogoutEnabled; - // cached value to avoid IPCs - private boolean mIsUdfpsEnrolled; private boolean mIsFaceEnrolled; // If the user long pressed the lock icon, disabling face auth for the current session. private boolean mLockIconPressed; @@ -362,13 +354,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final Handler mHandler; - private final Observer<Integer> mRingerModeObserver = new Observer<Integer>() { - @Override - public void onChanged(Integer ringer) { - mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED, ringer, 0).sendToTarget(); - } - }; - private SparseBooleanArray mBiometricEnabledForUser = new SparseBooleanArray(); private BiometricManager mBiometricManager; private IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback = @@ -1808,10 +1793,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFaceRunningState = BIOMETRIC_STATE_STOPPED; } - private void registerRingerTracker() { - mRingerModeTracker.getRingerMode().observeForever(mRingerModeObserver); - } - @VisibleForTesting @Inject protected KeyguardUpdateMonitor( @@ -1819,7 +1800,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Main Looper mainLooper, BroadcastDispatcher broadcastDispatcher, DumpManager dumpManager, - RingerModeTracker ringerModeTracker, @Background Executor backgroundExecutor, @Main Executor mainExecutor, StatusBarStateController statusBarStateController, @@ -1837,7 +1817,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mBroadcastDispatcher = broadcastDispatcher; mInteractionJankMonitor = interactionJankMonitor; mLatencyTracker = latencyTracker; - mRingerModeTracker = ringerModeTracker; mStatusBarStateController = statusBarStateController; mStatusBarStateController.addCallback(mStatusBarStateControllerListener); mStatusBarState = mStatusBarStateController.getState(); @@ -1862,9 +1841,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab case MSG_SIM_STATE_CHANGE: handleSimStateChange(msg.arg1, msg.arg2, (int) msg.obj); break; - case MSG_RINGER_MODE_CHANGED: - handleRingerModeChange(msg.arg1); - break; case MSG_PHONE_STATE_CHANGED: handlePhoneStateChanged((String) msg.obj); break; @@ -2006,8 +1982,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } }); - mHandler.post(this::registerRingerTracker); - final IntentFilter allUserFilter = new IntentFilter(); allUserFilter.addAction(Intent.ACTION_USER_INFO_CHANGED); allUserFilter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); @@ -2117,10 +2091,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab false, mTimeFormatChangeObserver, UserHandle.USER_ALL); } - private void updateUdfpsEnrolled(int userId) { - mIsUdfpsEnrolled = mAuthController.isUdfpsEnrolled(userId); - } - private void updateFaceEnrolled(int userId) { mIsFaceEnrolled = whitelistIpcs( () -> mFaceManager != null && mFaceManager.isHardwareDetected() @@ -2132,7 +2102,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @return true if there's at least one udfps enrolled for the current user. */ public boolean isUdfpsEnrolled() { - return mIsUdfpsEnrolled; + return mAuthController.isUdfpsEnrolled(getCurrentUser()); } /** @@ -2186,7 +2156,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return; } - updateUdfpsEnrolled(getCurrentUser()); final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported()); final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING || mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING; @@ -2750,12 +2719,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Assert.isMainThread(); updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); updateSecondaryLockscreenRequirement(userId); - for (int i = 0; i < mCallbacks.size(); i++) { - KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); - if (cb != null) { - cb.onDevicePolicyManagerStateChanged(); - } - } } /** @@ -2834,21 +2797,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** - * Handle {@link #MSG_RINGER_MODE_CHANGED} - */ - private void handleRingerModeChange(int mode) { - Assert.isMainThread(); - if (DEBUG) Log.d(TAG, "handleRingerModeChange(" + mode + ")"); - mRingMode = mode; - for (int i = 0; i < mCallbacks.size(); i++) { - KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); - if (cb != null) { - cb.onRingerModeChanged(mode); - } - } - } - - /** * Handle {@link #MSG_TIME_UPDATE} */ private void handleTimeUpdate() { @@ -3235,7 +3183,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // Notify listener of the current state callback.onRefreshBatteryInfo(mBatteryStatus); callback.onTimeChanged(); - callback.onRingerModeChanged(mRingMode); callback.onPhoneStateChanged(mPhoneState); callback.onRefreshCarrierInfo(); callback.onClockVisibilityChanged(); @@ -3546,7 +3493,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); mBroadcastDispatcher.unregisterReceiver(mBroadcastAllReceiver); - mRingerModeTracker.getRingerMode().removeObserver(mRingerModeObserver); mLockPatternUtils.unregisterStrongAuthTracker(mStrongAuthTracker); mTrustManager.unregisterTrustListener(this); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 47e1035fbfef..8d5603dc1563 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -15,10 +15,7 @@ */ package com.android.keyguard; -import android.app.admin.DevicePolicyManager; -import android.graphics.Bitmap; import android.hardware.biometrics.BiometricSourceType; -import android.media.AudioManager; import android.os.SystemClock; import android.telephony.TelephonyManager; import android.view.WindowManagerPolicyConstants; @@ -70,13 +67,6 @@ public class KeyguardUpdateMonitorCallback { public void onRefreshCarrierInfo() { } /** - * Called when the ringer mode changes. - * @param state the current ringer state, as defined in - * {@link AudioManager#RINGER_MODE_CHANGED_ACTION} - */ - public void onRingerModeChanged(int state) { } - - /** * Called when the phone state changes. String will be one of: * {@link TelephonyManager#EXTRA_STATE_IDLE} * {@link TelephonyManager@EXTRA_STATE_RINGING} @@ -124,12 +114,6 @@ public class KeyguardUpdateMonitorCallback { public void onDeviceProvisioned() { } /** - * Called when the device policy changes. - * See {@link DevicePolicyManager#ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED} - */ - public void onDevicePolicyManagerStateChanged() { } - - /** * Called when the user change begins. */ public void onUserSwitching(int userId) { } @@ -168,14 +152,7 @@ public class KeyguardUpdateMonitorCallback { public void onEmergencyCallAction() { } /** - * Called when the transport background changes. - * @param bitmap - */ - public void onSetBackground(Bitmap bitmap) { - } - - /** - * Called when the device has started waking up. + * Called when the device has started waking up and after biometric states are updated. * * @deprecated use {@link com.android.systemui.keyguard.WakefulnessLifecycle}. */ @@ -183,7 +160,8 @@ public class KeyguardUpdateMonitorCallback { public void onStartedWakingUp() { } /** - * Called when the device has started going to sleep. + * Called when the device has started going to sleep and after biometric recognized + * states are reset. * @param why see {@link #onFinishedGoingToSleep(int)} * * @deprecated use {@link com.android.systemui.keyguard.WakefulnessLifecycle}. @@ -304,7 +282,6 @@ public class KeyguardUpdateMonitorCallback { */ public void onTrustAgentErrorMessage(CharSequence message) { } - /** * Called when a value of logout enabled is change. */ diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 80a3a0ebb250..4ad51835687f 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -433,7 +433,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme public void onDozingChanged(boolean isDozing) { mIsDozing = isDozing; updateBurnInOffsets(); - updateIsUdfpsEnrolled(); updateVisibility(); } @@ -513,7 +512,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mKeyguardUpdateMonitor.getUserUnlockedWithBiometric( KeyguardUpdateMonitor.getCurrentUser()); } - updateIsUdfpsEnrolled(); updateVisibility(); } diff --git a/packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt b/packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt index 42d38cb3463c..13d96e44be9e 100644 --- a/packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt @@ -32,11 +32,11 @@ class DarkReceiverImpl @JvmOverloads constructor( private val dualToneHandler = DualToneHandler(context) init { - onDarkChanged(Rect(), 1f, DarkIconDispatcher.DEFAULT_ICON_TINT) + onDarkChanged(ArrayList<Rect>(), 1f, DarkIconDispatcher.DEFAULT_ICON_TINT) } - override fun onDarkChanged(area: Rect?, darkIntensity: Float, tint: Int) { - val intensity = if (DarkIconDispatcher.isInArea(area, this)) darkIntensity else 0f + override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) { + val intensity = if (DarkIconDispatcher.isInAreas(areas, this)) darkIntensity else 0f setBackgroundColor(dualToneHandler.getSingleColor(intensity)) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt new file mode 100644 index 000000000000..6bec8aacd55f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.annotation.Dimension +import android.content.Context +import android.graphics.Canvas +import android.graphics.Matrix +import android.graphics.Paint +import android.graphics.Path +import android.graphics.Rect +import android.graphics.RectF +import android.graphics.Region +import android.util.AttributeSet +import android.view.Display +import android.view.DisplayCutout +import android.view.DisplayInfo +import android.view.Surface +import android.view.View +import androidx.annotation.VisibleForTesting +import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView +import com.android.systemui.animation.Interpolators + +/** + * A class that handles common actions of display cutout view. + * - Draws cutouts. + * - Handles camera protection. + * - Intercepts touches on cutout areas. + */ +open class DisplayCutoutBaseView : View, RegionInterceptableView { + + private val shouldDrawCutout: Boolean = DisplayCutout.getFillBuiltInDisplayCutout( + context.resources, context.display?.uniqueId) + private var displayMode: Display.Mode? = null + private val location = IntArray(2) + protected var displayRotation = 0 + + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) + @JvmField val displayInfo = DisplayInfo() + @JvmField protected var pendingRotationChange = false + @JvmField protected val paint = Paint() + @JvmField protected val cutoutPath = Path() + + @JvmField protected var showProtection = false + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) + @JvmField val protectionRect: RectF = RectF() + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) + @JvmField val protectionPath: Path = Path() + private val protectionRectOrig: RectF = RectF() + private val protectionPathOrig: Path = Path() + private var cameraProtectionProgress: Float = HIDDEN_CAMERA_PROTECTION_SCALE + private var cameraProtectionAnimator: ValueAnimator? = null + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) + : super(context, attrs, defStyleAttr) + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + updateCutout() + } + + fun onDisplayChanged(displayId: Int) { + val oldMode: Display.Mode? = displayMode + displayMode = display.mode + + // Skip if display mode or cutout hasn't changed. + if (!displayModeChanged(oldMode, displayMode) && + display.cutout == displayInfo.displayCutout) { + return + } + if (displayId == display.displayId) { + updateCutout() + updateProtectionBoundingPath() + } + } + + open fun updateRotation(rotation: Int) { + displayRotation = rotation + updateCutout() + updateProtectionBoundingPath() + } + + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) + public override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + if (!shouldDrawCutout) { + return + } + canvas.save() + getLocationOnScreen(location) + canvas.translate(-location[0].toFloat(), -location[1].toFloat()) + + drawCutouts(canvas) + drawCutoutProtection(canvas) + canvas.restore() + } + + override fun shouldInterceptTouch(): Boolean { + return displayInfo.displayCutout != null && visibility == VISIBLE && shouldDrawCutout + } + + override fun getInterceptRegion(): Region? { + displayInfo.displayCutout ?: return null + + val cutoutBounds: Region = rectsToRegion(displayInfo.displayCutout?.boundingRects) + // Transform to window's coordinate space + rootView.getLocationOnScreen(location) + cutoutBounds.translate(-location[0], -location[1]) + + // Intersect with window's frame + cutoutBounds.op(rootView.left, rootView.top, rootView.right, rootView.bottom, + Region.Op.INTERSECT) + return cutoutBounds + } + + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) + open fun updateCutout() { + if (pendingRotationChange) { + return + } + cutoutPath.reset() + display.getDisplayInfo(displayInfo) + displayInfo.displayCutout?.cutoutPath?.let { path -> cutoutPath.set(path) } + invalidate() + } + + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) + open fun drawCutouts(canvas: Canvas) { + displayInfo.displayCutout?.cutoutPath ?: return + canvas.drawPath(cutoutPath, paint) + } + + protected open fun drawCutoutProtection(canvas: Canvas) { + if (cameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE && + !protectionRect.isEmpty) { + canvas.scale(cameraProtectionProgress, cameraProtectionProgress, + protectionRect.centerX(), protectionRect.centerY()) + canvas.drawPath(protectionPath, paint) + } + } + + /** + * Converts a set of [Rect]s into a [Region] + */ + fun rectsToRegion(rects: List<Rect?>?): Region { + val result = Region.obtain() + if (rects != null) { + for (r in rects) { + if (r != null && !r.isEmpty) { + result.op(r, Region.Op.UNION) + } + } + } + return result + } + + open fun enableShowProtection(show: Boolean) { + if (showProtection == show) { + return + } + showProtection = show + updateProtectionBoundingPath() + // Delay the relayout until the end of the animation when hiding the cutout, + // otherwise we'd clip it. + if (showProtection) { + requestLayout() + } + cameraProtectionAnimator?.cancel() + cameraProtectionAnimator = ValueAnimator.ofFloat(cameraProtectionProgress, + if (showProtection) 1.0f else HIDDEN_CAMERA_PROTECTION_SCALE).setDuration(750) + cameraProtectionAnimator?.interpolator = Interpolators.DECELERATE_QUINT + cameraProtectionAnimator?.addUpdateListener(ValueAnimator.AnimatorUpdateListener { + animation: ValueAnimator -> + cameraProtectionProgress = animation.animatedValue as Float + invalidate() + }) + cameraProtectionAnimator?.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + cameraProtectionAnimator = null + if (!showProtection) { + requestLayout() + } + } + }) + cameraProtectionAnimator?.start() + } + + open fun setProtection(path: Path, pathBounds: Rect) { + protectionPathOrig.reset() + protectionPathOrig.set(path) + protectionPath.reset() + protectionRectOrig.setEmpty() + protectionRectOrig.set(pathBounds) + protectionRect.setEmpty() + } + + protected open fun updateProtectionBoundingPath() { + if (pendingRotationChange) { + return + } + val lw: Int = displayInfo.logicalWidth + val lh: Int = displayInfo.logicalHeight + val flipped = (displayInfo.rotation == Surface.ROTATION_90 || + displayInfo.rotation == Surface.ROTATION_270) + val dw = if (flipped) lh else lw + val dh = if (flipped) lw else lh + val m = Matrix() + transformPhysicalToLogicalCoordinates(displayInfo.rotation, dw, dh, m) + if (!protectionPathOrig.isEmpty) { + // Reset the protection path so we don't aggregate rotations + protectionPath.set(protectionPathOrig) + protectionPath.transform(m) + m.mapRect(protectionRect, protectionRectOrig) + } + } + + private fun displayModeChanged(oldMode: Display.Mode?, newMode: Display.Mode?): Boolean { + if (oldMode == null) { + return true + } + + // We purposely ignore refresh rate and id changes here, because we don't need to + // invalidate for those, and they can trigger the refresh rate to increase + return oldMode?.physicalHeight != newMode?.physicalHeight || + oldMode?.physicalWidth != newMode?.physicalWidth + } + + companion object { + private const val HIDDEN_CAMERA_PROTECTION_SCALE = 0.5f + + @JvmStatic protected fun transformPhysicalToLogicalCoordinates( + @Surface.Rotation rotation: Int, + @Dimension physicalWidth: Int, + @Dimension physicalHeight: Int, + out: Matrix + ) { + when (rotation) { + Surface.ROTATION_0 -> out.reset() + Surface.ROTATION_90 -> { + out.setRotate(270f) + out.postTranslate(0f, physicalWidth.toFloat()) + } + Surface.ROTATION_180 -> { + out.setRotate(180f) + out.postTranslate(physicalWidth.toFloat(), physicalHeight.toFloat()) + } + Surface.ROTATION_270 -> { + out.setRotate(90f) + out.postTranslate(physicalHeight.toFloat(), 0f) + } + else -> throw IllegalArgumentException("Unknown rotation: $rotation") + } + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java index c7f1006a4042..95f666c5d0eb 100644 --- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java +++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java @@ -60,6 +60,7 @@ public class PluginInflateContainer extends AutoReinflateContainer super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PluginInflateContainer); String viewType = a.getString(R.styleable.PluginInflateContainer_viewType); + a.recycle(); try { mClass = (Class<ViewProvider>) Class.forName(viewType); } catch (Exception e) { diff --git a/packages/SystemUI/src/com/android/systemui/ResizingSpace.java b/packages/SystemUI/src/com/android/systemui/ResizingSpace.java index c2bc53e7d553..bb4176d64802 100644 --- a/packages/SystemUI/src/com/android/systemui/ResizingSpace.java +++ b/packages/SystemUI/src/com/android/systemui/ResizingSpace.java @@ -35,6 +35,7 @@ public class ResizingSpace extends View { TypedArray a = context.obtainStyledAttributes(attrs, android.R.styleable.ViewGroup_Layout); mWidth = a.getResourceId(android.R.styleable.ViewGroup_Layout_layout_width, 0); mHeight = a.getResourceId(android.R.styleable.ViewGroup_Layout_layout_height, 0); + a.recycle(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt new file mode 100644 index 000000000000..ee1d9a3aa5de --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui + +import android.content.Context +import android.content.pm.ActivityInfo +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.ColorFilter +import android.graphics.Paint +import android.graphics.PixelFormat +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.graphics.PorterDuffXfermode +import android.graphics.drawable.Drawable +import android.hardware.graphics.common.AlphaInterpretation +import android.hardware.graphics.common.DisplayDecorationSupport +import android.view.RoundedCorner +import android.view.RoundedCorners + +/** + * When the HWC of the device supports Composition.DISPLAY_DECORATON, we use this layer to draw + * screen decorations. + */ +class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDecorationSupport) + : DisplayCutoutBaseView(context) { + public val colorMode: Int + private val useInvertedAlphaColor: Boolean + private val color: Int + private val bgColor: Int + private val cornerFilter: ColorFilter + private val cornerBgFilter: ColorFilter + private val clearPaint: Paint + + private var roundedCornerTopSize = 0 + private var roundedCornerBottomSize = 0 + private var roundedCornerDrawableTop: Drawable? = null + private var roundedCornerDrawableBottom: Drawable? = null + + init { + if (displayDecorationSupport.format != PixelFormat.R_8) { + throw IllegalArgumentException("Attempting to use unsupported mode " + + "${PixelFormat.formatToString(displayDecorationSupport.format)}") + } + if (DEBUG_COLOR) { + color = Color.GREEN + bgColor = Color.TRANSPARENT + colorMode = ActivityInfo.COLOR_MODE_DEFAULT + useInvertedAlphaColor = false + } else { + colorMode = ActivityInfo.COLOR_MODE_A8 + useInvertedAlphaColor = displayDecorationSupport.alphaInterpretation == + AlphaInterpretation.COVERAGE + if (useInvertedAlphaColor) { + color = Color.TRANSPARENT + bgColor = Color.BLACK + } else { + color = Color.BLACK + bgColor = Color.TRANSPARENT + } + } + cornerFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) + cornerBgFilter = PorterDuffColorFilter(bgColor, PorterDuff.Mode.SRC_OUT) + + clearPaint = Paint() + clearPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + viewRootImpl.setDisplayDecoration(true) + + if (useInvertedAlphaColor) { + paint.set(clearPaint) + } else { + paint.color = color + paint.style = Paint.Style.FILL + } + } + + override fun onDraw(canvas: Canvas) { + if (useInvertedAlphaColor) { + canvas.drawColor(bgColor) + } + // Cutouts are drawn in DisplayCutoutBaseView.onDraw() + super.onDraw(canvas) + drawRoundedCorners(canvas) + } + + private fun drawRoundedCorners(canvas: Canvas) { + if (roundedCornerTopSize == 0 && roundedCornerBottomSize == 0) { + return + } + var degree: Int + for (i in RoundedCorner.POSITION_TOP_LEFT + until RoundedCorners.ROUNDED_CORNER_POSITION_LENGTH) { + canvas.save() + degree = getRoundedCornerRotationDegree(90 * i) + canvas.rotate(degree.toFloat()) + canvas.translate( + getRoundedCornerTranslationX(degree).toFloat(), + getRoundedCornerTranslationY(degree).toFloat()) + if (i == RoundedCorner.POSITION_TOP_LEFT || i == RoundedCorner.POSITION_TOP_RIGHT) { + drawRoundedCorner(canvas, roundedCornerDrawableTop, roundedCornerTopSize) + } else { + drawRoundedCorner(canvas, roundedCornerDrawableBottom, roundedCornerBottomSize) + } + canvas.restore() + } + } + + private fun drawRoundedCorner(canvas: Canvas, drawable: Drawable?, size: Int) { + if (useInvertedAlphaColor) { + canvas.drawRect(0f, 0f, size.toFloat(), size.toFloat(), clearPaint) + drawable?.colorFilter = cornerBgFilter + } else { + drawable?.colorFilter = cornerFilter + } + drawable?.draw(canvas) + // Clear color filter when we are done with drawing. + drawable?.clearColorFilter() + } + + private fun getRoundedCornerRotationDegree(defaultDegree: Int): Int { + return (defaultDegree - 90 * displayRotation + 360) % 360 + } + + private fun getRoundedCornerTranslationX(degree: Int): Int { + return when (degree) { + 0, 90 -> 0 + 180 -> -width + 270 -> -height + else -> throw IllegalArgumentException("Incorrect degree: $degree") + } + } + + private fun getRoundedCornerTranslationY(degree: Int): Int { + return when (degree) { + 0, 270 -> 0 + 90 -> -width + 180 -> -height + else -> throw IllegalArgumentException("Incorrect degree: $degree") + } + } + + /** + * Update the rounded corner drawables. + */ + fun updateRoundedCornerDrawable(top: Drawable, bottom: Drawable) { + roundedCornerDrawableTop = top + roundedCornerDrawableBottom = bottom + updateRoundedCornerDrawableBounds() + invalidate() + } + + /** + * Update the rounded corner size. + */ + fun updateRoundedCornerSize(top: Int, bottom: Int) { + roundedCornerTopSize = top + roundedCornerBottomSize = bottom + updateRoundedCornerDrawableBounds() + invalidate() + } + + private fun updateRoundedCornerDrawableBounds() { + if (roundedCornerDrawableTop != null) { + roundedCornerDrawableTop?.setBounds(0, 0, roundedCornerTopSize, + roundedCornerTopSize) + } + if (roundedCornerDrawableBottom != null) { + roundedCornerDrawableBottom?.setBounds(0, 0, roundedCornerBottomSize, + roundedCornerBottomSize) + } + invalidate() + } + + companion object { + private val DEBUG_COLOR = ScreenDecorations.DEBUG_COLOR + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 783415e98875..2ec5f4f894f9 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -14,24 +14,17 @@ package com.android.systemui; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM; import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT; import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH; import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT; import static android.view.DisplayCutout.BOUNDS_POSITION_TOP; -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.annotation.Dimension; import android.annotation.IdRes; import android.annotation.NonNull; import android.annotation.Nullable; @@ -40,11 +33,11 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ActivityInfo; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; -import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; @@ -52,10 +45,10 @@ import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Region; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; +import android.hardware.graphics.common.AlphaInterpretation; +import android.hardware.graphics.common.DisplayDecorationSupport; import android.os.Handler; import android.os.SystemProperties; import android.os.UserHandle; @@ -63,14 +56,11 @@ import android.provider.Settings.Secure; import android.util.DisplayMetrics; import android.util.DisplayUtils; import android.util.Log; -import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayCutout.BoundsPosition; -import android.view.DisplayInfo; import android.view.Gravity; import android.view.LayoutInflater; import android.view.RoundedCorners; -import android.view.Surface; import android.view.View; import android.view.View.OnLayoutChangeListener; import android.view.ViewGroup; @@ -83,8 +73,6 @@ import android.widget.ImageView; import androidx.annotation.VisibleForTesting; import com.android.internal.util.Preconditions; -import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView; -import com.android.systemui.animation.Interpolators; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; @@ -106,6 +94,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; import javax.inject.Inject; @@ -117,7 +106,7 @@ import kotlin.Pair; * for antialiasing and emulation purposes. */ @SysUISingleton -public class ScreenDecorations extends CoreStartable implements Tunable , Dumpable{ +public class ScreenDecorations extends CoreStartable implements Tunable , Dumpable { private static final boolean DEBUG = false; private static final String TAG = "ScreenDecorations"; @@ -129,7 +118,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS = SystemProperties.getBoolean("debug.screenshot_rounded_corners", false); private static final boolean VERBOSE = false; - private static final boolean DEBUG_COLOR = DEBUG_SCREENSHOT_ROUNDED_CORNERS; + static final boolean DEBUG_COLOR = DEBUG_SCREENSHOT_ROUNDED_CORNERS; private DisplayManager mDisplayManager; @VisibleForTesting @@ -138,7 +127,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab private final Executor mMainExecutor; private final TunerService mTunerService; private final SecureSettings mSecureSettings; - private DisplayManager.DisplayListener mDisplayListener; + @VisibleForTesting + DisplayManager.DisplayListener mDisplayListener; private CameraAvailabilityListener mCameraListener; private final UserTracker mUserTracker; private final PrivacyDotViewController mDotViewController; @@ -158,23 +148,36 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab protected OverlayWindow[] mOverlays = null; @Nullable private DisplayCutoutView[] mCutoutViews; + @VisibleForTesting + ViewGroup mScreenDecorHwcWindow; + @VisibleForTesting + ScreenDecorHwcLayer mScreenDecorHwcLayer; private float mDensity; private WindowManager mWindowManager; private int mRotation; private SettingObserver mColorInversionSetting; private DelayableExecutor mExecutor; private Handler mHandler; - private boolean mPendingRotationChange; + boolean mPendingRotationChange; private boolean mIsRoundedCornerMultipleRadius; private Drawable mRoundedCornerDrawable; private Drawable mRoundedCornerDrawableTop; private Drawable mRoundedCornerDrawableBottom; - private String mDisplayUniqueId; + @VisibleForTesting + String mDisplayUniqueId; + private int mTintColor = Color.BLACK; + @VisibleForTesting + protected DisplayDecorationSupport mHwcScreenDecorationSupport; private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback = new CameraAvailabilityListener.CameraTransitionCallback() { @Override public void onApplyCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) { + if (mScreenDecorHwcLayer != null) { + mScreenDecorHwcLayer.setProtection(protectionPath, bounds); + mScreenDecorHwcLayer.enableShowProtection(true); + return; + } if (mCutoutViews == null) { Log.w(TAG, "DisplayCutoutView do not initialized"); return; @@ -184,13 +187,17 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile if (dcv != null) { dcv.setProtection(protectionPath, bounds); - dcv.setShowProtection(true); + dcv.enableShowProtection(true); } } } @Override public void onHideCameraProtection() { + if (mScreenDecorHwcLayer != null) { + mScreenDecorHwcLayer.enableShowProtection(false); + return; + } if (mCutoutViews == null) { Log.w(TAG, "DisplayCutoutView do not initialized"); return; @@ -199,27 +206,59 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab for (DisplayCutoutView dcv : mCutoutViews) { // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile if (dcv != null) { - dcv.setShowProtection(false); + dcv.enableShowProtection(false); } } } }; - /** - * Converts a set of {@link Rect}s into a {@link Region} - * - * @hide - */ - public static Region rectsToRegion(List<Rect> rects) { - Region result = Region.obtain(); - if (rects != null) { - for (Rect r : rects) { - if (r != null && !r.isEmpty()) { - result.op(r, Region.Op.UNION); + private PrivacyDotViewController.ShowingListener mPrivacyDotShowingListener = + new PrivacyDotViewController.ShowingListener() { + @Override + public void onPrivacyDotShown(@Nullable View v) { + // We don't need to control the window visibility when the hwc doesn't support screen + // decoration since the overlay windows are always visible in this case. + if (mHwcScreenDecorationSupport == null || v == null) { + return; + } + mExecutor.execute(() -> { + for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { + if (mOverlays[i] == null) { + continue; + } + final ViewGroup overlayView = mOverlays[i].getRootView(); + if (overlayView.findViewById(v.getId()) != null) { + overlayView.setVisibility(View.VISIBLE); + } } + }); + } + + @Override + public void onPrivacyDotHidden(@Nullable View v) { + // We don't need to control the window visibility when the hwc doesn't support screen + // decoration since the overlay windows are always visible in this case. + if (mHwcScreenDecorationSupport == null || v == null) { + return; } + mExecutor.execute(() -> { + for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { + if (mOverlays[i] == null) { + continue; + } + final ViewGroup overlayView = mOverlays[i].getRootView(); + if (overlayView.findViewById(v.getId()) != null) { + overlayView.setVisibility(View.INVISIBLE); + } + } + }); } - return result; + }; + + private static boolean eq(DisplayDecorationSupport a, DisplayDecorationSupport b) { + if (a == null) return (b == null); + if (b == null) return false; + return a.format == b.format && a.alphaInterpretation == b.alphaInterpretation; } @Inject @@ -241,6 +280,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mDotViewController = dotViewController; mThreadFactory = threadFactory; mDotFactory = dotFactory; + dotViewController.setShowingListener(mPrivacyDotShowingListener); } @Override @@ -265,6 +305,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mIsRoundedCornerMultipleRadius = isRoundedCornerMultipleRadius(mContext, mDisplayUniqueId); mWindowManager = mContext.getSystemService(WindowManager.class); mDisplayManager = mContext.getSystemService(DisplayManager.class); + mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport(); updateRoundedCornerDrawable(); updateRoundedCornerRadii(); setupDecorations(); @@ -305,15 +346,36 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab new RestartingPreDrawListener(overlayView, i, newRotation)); } } + + if (mScreenDecorHwcWindow != null) { + mScreenDecorHwcWindow.getViewTreeObserver().addOnPreDrawListener( + new RestartingPreDrawListener( + mScreenDecorHwcWindow, + -1, // Pass -1 for views with no specific position. + newRotation)); + } } + final String newUniqueId = mContext.getDisplay().getUniqueId(); - if ((newUniqueId != null && !newUniqueId.equals(mDisplayUniqueId)) - || (mDisplayUniqueId != null && !mDisplayUniqueId.equals(newUniqueId))) { + if (!Objects.equals(newUniqueId, mDisplayUniqueId)) { mDisplayUniqueId = newUniqueId; mIsRoundedCornerMultipleRadius = isRoundedCornerMultipleRadius(mContext, mDisplayUniqueId); + final DisplayDecorationSupport newScreenDecorationSupport = + mContext.getDisplay().getDisplayDecorationSupport(); + // When the value of mSupportHwcScreenDecoration is changed, re-setup the whole + // screen decoration. + if (!eq(newScreenDecorationSupport, mHwcScreenDecorationSupport)) { + mHwcScreenDecorationSupport = newScreenDecorationSupport; + removeAllOverlays(); + setupDecorations(); + return; + } updateRoundedCornerDrawable(); } + if (mScreenDecorHwcLayer != null) { + mScreenDecorHwcLayer.onDisplayChanged(displayId); + } updateOrientation(); } }; @@ -359,6 +421,11 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab List<DecorProvider> decorProviders = mDotFactory.getProviders(); if (hasRoundedCorners() || shouldDrawCutout() || !decorProviders.isEmpty()) { + if (mHwcScreenDecorationSupport != null) { + createHwcOverlay(); + } else { + removeHwcOverlay(); + } final DisplayCutout cutout = getCutout(); for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { if (shouldShowCutout(i, cutout) || shouldShowRoundedCorner(i, cutout) @@ -383,14 +450,15 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } } else { removeAllOverlays(); + removeHwcOverlay(); } - if (hasOverlays()) { + if (hasOverlays() || hasHwcOverlay()) { if (mIsRegistered) { return; } DisplayMetrics metrics = new DisplayMetrics(); - mDisplayManager.getDisplay(DEFAULT_DISPLAY).getMetrics(metrics); + mContext.getDisplay().getMetrics(metrics); mDensity = metrics.density; mMainExecutor.execute(() -> mTunerService.addTunable(this, SIZE)); @@ -475,25 +543,27 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mOverlays = new OverlayWindow[BOUNDS_POSITION_LENGTH]; } - if (mCutoutViews == null) { - mCutoutViews = new DisplayCutoutView[BOUNDS_POSITION_LENGTH]; - } - if (mOverlays[pos] != null) { return; } mOverlays[pos] = overlayForPosition(pos, decorProviders); - - mCutoutViews[pos] = new DisplayCutoutView(mContext, pos, this); - mOverlays[pos].getRootView().addView(mCutoutViews[pos]); - final ViewGroup overlayView = mOverlays[pos].getRootView(); overlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); overlayView.setAlpha(0); overlayView.setForceDarkAllowed(false); - updateView(pos, cutout); + // Only show cutout and rounded corners in mOverlays when hwc don't support screen + // decoration. + if (mHwcScreenDecorationSupport == null) { + if (mCutoutViews == null) { + mCutoutViews = new DisplayCutoutView[BOUNDS_POSITION_LENGTH]; + } + mCutoutViews[pos] = new DisplayCutoutView(mContext, pos); + mCutoutViews[pos].setColor(mTintColor); + overlayView.addView(mCutoutViews[pos]); + updateView(pos, cutout); + } mWindowManager.addView(overlayView, getWindowLayoutParams(pos)); @@ -509,8 +579,37 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } }); - mOverlays[pos].getRootView().getViewTreeObserver().addOnPreDrawListener( - new ValidatingPreDrawListener(mOverlays[pos].getRootView())); + overlayView.getRootView().getViewTreeObserver().addOnPreDrawListener( + new ValidatingPreDrawListener(overlayView.getRootView())); + } + + private boolean hasHwcOverlay() { + return mScreenDecorHwcWindow != null; + } + + private void removeHwcOverlay() { + if (mScreenDecorHwcWindow == null) { + return; + } + mWindowManager.removeViewImmediate(mScreenDecorHwcWindow); + mScreenDecorHwcWindow = null; + mScreenDecorHwcLayer = null; + } + + private void createHwcOverlay() { + if (mScreenDecorHwcWindow != null) { + return; + } + mScreenDecorHwcWindow = (ViewGroup) LayoutInflater.from(mContext).inflate( + R.layout.screen_decor_hwc_layer, null); + mScreenDecorHwcLayer = new ScreenDecorHwcLayer(mContext, mHwcScreenDecorationSupport); + mScreenDecorHwcWindow.addView(mScreenDecorHwcLayer, new FrameLayout.LayoutParams( + MATCH_PARENT, MATCH_PARENT, Gravity.TOP | Gravity.START)); + mWindowManager.addView(mScreenDecorHwcWindow, getHwcWindowLayoutParams()); + updateRoundedCornerSize(mRoundedDefault, mRoundedDefaultTop, mRoundedDefaultBottom); + updateRoundedCornerImageView(); + mScreenDecorHwcWindow.getViewTreeObserver().addOnPreDrawListener( + new ValidatingPreDrawListener(mScreenDecorHwcWindow)); } /** @@ -523,12 +622,18 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab decorProviders.forEach(provider -> { removeOverlayView(provider.getViewId()); currentOverlay.addDecorProvider(provider, mRotation); + // If the hwc supports screen decoration and privacy dot is enabled, it means there will + // be only privacy dot in mOverlay. So set the initial visibility of mOverlays to + // INVISIBLE and will only set it to VISIBLE when the privacy dot is showing. + if (mHwcScreenDecorationSupport != null) { + currentOverlay.getRootView().setVisibility(View.INVISIBLE); + } }); return currentOverlay; } private void updateView(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { - if (mOverlays == null || mOverlays[pos] == null) { + if (mOverlays == null || mOverlays[pos] == null || mHwcScreenDecorationSupport != null) { return; } @@ -540,15 +645,34 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab // update cutout view rotation if (mCutoutViews != null && mCutoutViews[pos] != null) { - mCutoutViews[pos].setRotation(mRotation); + mCutoutViews[pos].updateRotation(mRotation); } } @VisibleForTesting WindowManager.LayoutParams getWindowLayoutParams(@BoundsPosition int pos) { + final WindowManager.LayoutParams lp = getWindowLayoutBaseParams(); + lp.width = getWidthLayoutParamByPos(pos); + lp.height = getHeightLayoutParamByPos(pos); + lp.setTitle(getWindowTitleByPos(pos)); + lp.gravity = getOverlayWindowGravity(pos); + return lp; + } + + private WindowManager.LayoutParams getHwcWindowLayoutParams() { + final WindowManager.LayoutParams lp = getWindowLayoutBaseParams(); + lp.width = MATCH_PARENT; + lp.height = MATCH_PARENT; + lp.setTitle("ScreenDecorHwcOverlay"); + lp.gravity = Gravity.TOP | Gravity.START; + if (!DEBUG_COLOR) { + lp.setColorMode(ActivityInfo.COLOR_MODE_A8); + } + return lp; + } + + private WindowManager.LayoutParams getWindowLayoutBaseParams() { final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - getWidthLayoutParamByPos(pos), - getHeightLayoutParamByPos(pos), WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL @@ -566,8 +690,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; } - lp.setTitle(getWindowTitleByPos(pos)); - lp.gravity = getOverlayWindowGravity(pos); lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; lp.setFitInsetsTypes(0 /* types */); lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC; @@ -648,15 +770,19 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab }; private void updateColorInversion(int colorsInvertedValue) { - int tint = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK; + mTintColor = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK; if (DEBUG_COLOR) { - tint = Color.RED; + mTintColor = Color.RED; } - ColorStateList tintList = ColorStateList.valueOf(tint); - if (mOverlays == null) { + // When the hwc supports screen decorations, the layer will use the A8 color mode which + // won't be affected by the color inversion. If the composition goes the client composition + // route, the color inversion will be handled by the RenderEngine. + if (mOverlays == null || mHwcScreenDecorationSupport != null) { return; } + + ColorStateList tintList = ColorStateList.valueOf(mTintColor); for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { if (mOverlays[i] == null) { continue; @@ -676,7 +802,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (child instanceof ImageView) { ((ImageView) child).setImageTintList(tintList); } else if (child instanceof DisplayCutoutView) { - ((DisplayCutoutView) child).setColor(tint); + ((DisplayCutoutView) child).setColor(mTintColor); } } } @@ -688,6 +814,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab Log.i(TAG, "ScreenDecorations is disabled"); return; } + mExecutor.execute(() -> { int oldRotation = mRotation; mPendingRotationChange = false; @@ -705,6 +832,14 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab }); } + private static String alphaInterpretationToString(int alpha) { + switch (alpha) { + case AlphaInterpretation.COVERAGE: return "COVERAGE"; + case AlphaInterpretation.MASK: return "MASK"; + default: return "Unknown: " + alpha; + } + } + @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.println("ScreenDecorations state:"); @@ -712,6 +847,15 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab pw.println(" mIsRoundedCornerMultipleRadius:" + mIsRoundedCornerMultipleRadius); pw.println(" mIsPrivacyDotEnabled:" + isPrivacyDotEnabled()); pw.println(" mPendingRotationChange:" + mPendingRotationChange); + pw.println(" mHwcScreenDecorationSupport:"); + if (mHwcScreenDecorationSupport == null) { + pw.println(" null"); + } else { + pw.println(" format: " + + PixelFormat.formatToString(mHwcScreenDecorationSupport.format)); + pw.println(" alphaInterpretation: " + + alphaInterpretationToString(mHwcScreenDecorationSupport.alphaInterpretation)); + } pw.println(" mRoundedDefault(x,y)=(" + mRoundedDefault.x + "," + mRoundedDefault.y + ")"); pw.println(" mRoundedDefaultTop(x,y)=(" + mRoundedDefaultTop.x + "," + mRoundedDefaultTop.y + ")"); @@ -739,7 +883,10 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } if (newRotation != mRotation) { mRotation = newRotation; - + if (mScreenDecorHwcLayer != null) { + mScreenDecorHwcLayer.pendingRotationChange = false; + mScreenDecorHwcLayer.updateRotation(mRotation); + } if (mOverlays != null) { updateLayoutParams(); final DisplayCutout cutout = getCutout(); @@ -956,7 +1103,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab private boolean shouldShowRoundedCorner(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { - return hasRoundedCorners() && isDefaultShownOverlayPos(pos, cutout); + return hasRoundedCorners() && isDefaultShownOverlayPos(pos, cutout) + && mHwcScreenDecorationSupport == null; } private boolean shouldShowPrivacyDot(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { @@ -966,7 +1114,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab private boolean shouldShowCutout(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll(); final int rotatedPos = getBoundPositionFromRotation(pos, mRotation); - return (bounds != null && !bounds[rotatedPos].isEmpty()); + return (bounds != null && !bounds[rotatedPos].isEmpty() + && mHwcScreenDecorationSupport == null); } private boolean shouldDrawCutout() { @@ -1027,14 +1176,22 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab final Drawable bottom = mRoundedCornerDrawableBottom != null ? mRoundedCornerDrawableBottom : mRoundedCornerDrawable; + if (mScreenDecorHwcLayer != null) { + mScreenDecorHwcLayer.updateRoundedCornerDrawable(top, bottom); + return; + } + if (mOverlays == null) { return; } + final ColorStateList colorStateList = ColorStateList.valueOf(mTintColor); for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { if (mOverlays[i] == null) { continue; } final ViewGroup overlayView = mOverlays[i].getRootView(); + ((ImageView) overlayView.findViewById(R.id.left)).setImageTintList(colorStateList); + ((ImageView) overlayView.findViewById(R.id.right)).setImageTintList(colorStateList); ((ImageView) overlayView.findViewById(R.id.left)).setImageDrawable( isTopRoundedCorner(i, R.id.left) ? top : bottom); ((ImageView) overlayView.findViewById(R.id.right)).setImageDrawable( @@ -1065,9 +1222,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab Point sizeDefault, Point sizeTop, Point sizeBottom) { - if (mOverlays == null) { - return; - } if (sizeTop.x == 0) { sizeTop = sizeDefault; } @@ -1075,6 +1229,14 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab sizeBottom = sizeDefault; } + if (mScreenDecorHwcLayer != null) { + mScreenDecorHwcLayer.updateRoundedCornerSize(sizeTop.x, sizeBottom.x); + return; + } + + if (mOverlays == null) { + return; + } for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { if (mOverlays[i] == null) { continue; @@ -1095,40 +1257,21 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab view.setLayoutParams(params); } - public static class DisplayCutoutView extends View implements DisplayManager.DisplayListener, - RegionInterceptableView { - - private static final float HIDDEN_CAMERA_PROTECTION_SCALE = 0.5f; - - private Display.Mode mDisplayMode = null; - private final DisplayInfo mInfo = new DisplayInfo(); - private final Paint mPaint = new Paint(); + public static class DisplayCutoutView extends DisplayCutoutBaseView { private final List<Rect> mBounds = new ArrayList(); private final Rect mBoundingRect = new Rect(); - private final Path mBoundingPath = new Path(); - // Don't initialize these yet because they may never exist - private RectF mProtectionRect; - private RectF mProtectionRectOrig; - private Path mProtectionPath; - private Path mProtectionPathOrig; private Rect mTotalBounds = new Rect(); - // Whether or not to show the cutout protection path - private boolean mShowProtection = false; - private final int[] mLocation = new int[2]; - private final ScreenDecorations mDecorations; private int mColor = Color.BLACK; private int mRotation; private int mInitialPosition; private int mPosition; - private float mCameraProtectionProgress = HIDDEN_CAMERA_PROTECTION_SCALE; - private ValueAnimator mCameraProtectionAnimator; - public DisplayCutoutView(Context context, @BoundsPosition int pos, - ScreenDecorations decorations) { + public DisplayCutoutView(Context context, @BoundsPosition int pos) { super(context); mInitialPosition = pos; - mDecorations = decorations; + paint.setColor(mColor); + paint.setStyle(Paint.Style.FILL); setId(R.id.display_cutout); if (DEBUG) { getViewTreeObserver().addOnDrawListener(() -> Log.i(TAG, @@ -1138,145 +1281,31 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab public void setColor(int color) { mColor = color; + paint.setColor(mColor); invalidate(); } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mContext.getSystemService(DisplayManager.class).registerDisplayListener(this, - getHandler()); - update(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - getLocationOnScreen(mLocation); - canvas.translate(-mLocation[0], -mLocation[1]); - - if (!mBoundingPath.isEmpty()) { - mPaint.setColor(mColor); - mPaint.setStyle(Paint.Style.FILL); - mPaint.setAntiAlias(true); - canvas.drawPath(mBoundingPath, mPaint); - } - if (mCameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE - && !mProtectionRect.isEmpty()) { - canvas.scale(mCameraProtectionProgress, mCameraProtectionProgress, - mProtectionRect.centerX(), mProtectionRect.centerY()); - canvas.drawPath(mProtectionPath, mPaint); - } - } - - @Override - public void onDisplayAdded(int displayId) { - } - - @Override - public void onDisplayRemoved(int displayId) { - } - - @Override - public void onDisplayChanged(int displayId) { - Display.Mode oldMode = mDisplayMode; - mDisplayMode = getDisplay().getMode(); - - // Display mode hasn't meaningfully changed, we can ignore it - if (!modeChanged(oldMode, mDisplayMode)) { - return; - } - - if (displayId == getDisplay().getDisplayId()) { - update(); - } - } - - private boolean modeChanged(Display.Mode oldMode, Display.Mode newMode) { - if (oldMode == null) { - return true; - } - - boolean changed = false; - changed |= oldMode.getPhysicalHeight() != newMode.getPhysicalHeight(); - changed |= oldMode.getPhysicalWidth() != newMode.getPhysicalWidth(); - // We purposely ignore refresh rate and id changes here, because we don't need to - // invalidate for those, and they can trigger the refresh rate to increase - - return changed; - } - - public void setRotation(int rotation) { + public void updateRotation(int rotation) { mRotation = rotation; - update(); + updateCutout(); } - void setProtection(Path protectionPath, Rect pathBounds) { - if (mProtectionPathOrig == null) { - mProtectionPathOrig = new Path(); - mProtectionPath = new Path(); - } - mProtectionPathOrig.set(protectionPath); - if (mProtectionRectOrig == null) { - mProtectionRectOrig = new RectF(); - mProtectionRect = new RectF(); - } - mProtectionRectOrig.set(pathBounds); - } - - void setShowProtection(boolean shouldShow) { - if (mShowProtection == shouldShow) { - return; - } - - mShowProtection = shouldShow; - updateBoundingPath(); - // Delay the relayout until the end of the animation when hiding the cutout, - // otherwise we'd clip it. - if (mShowProtection) { - requestLayout(); - } - if (mCameraProtectionAnimator != null) { - mCameraProtectionAnimator.cancel(); - } - mCameraProtectionAnimator = ValueAnimator.ofFloat(mCameraProtectionProgress, - mShowProtection ? 1.0f : HIDDEN_CAMERA_PROTECTION_SCALE).setDuration(750); - mCameraProtectionAnimator.setInterpolator(Interpolators.DECELERATE_QUINT); - mCameraProtectionAnimator.addUpdateListener(animation -> { - mCameraProtectionProgress = (float) animation.getAnimatedValue(); - invalidate(); - }); - mCameraProtectionAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mCameraProtectionAnimator = null; - if (!mShowProtection) { - requestLayout(); - } - } - }); - mCameraProtectionAnimator.start(); - } - - private void update() { - if (!isAttachedToWindow() || mDecorations.mPendingRotationChange) { + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) + @Override + public void updateCutout() { + if (!isAttachedToWindow() || pendingRotationChange) { return; } mPosition = getBoundPositionFromRotation(mInitialPosition, mRotation); requestLayout(); - getDisplay().getDisplayInfo(mInfo); + getDisplay().getDisplayInfo(displayInfo); mBounds.clear(); mBoundingRect.setEmpty(); - mBoundingPath.reset(); + cutoutPath.reset(); int newVisible; if (shouldDrawCutout(getContext()) && hasCutout()) { - mBounds.addAll(mInfo.displayCutout.getBoundingRects()); + mBounds.addAll(displayInfo.displayCutout.getBoundingRects()); localBounds(mBoundingRect); updateGravity(); updateBoundingPath(); @@ -1291,10 +1320,11 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } private void updateBoundingPath() { - int lw = mInfo.logicalWidth; - int lh = mInfo.logicalHeight; + int lw = displayInfo.logicalWidth; + int lh = displayInfo.logicalHeight; - boolean flipped = mInfo.rotation == ROTATION_90 || mInfo.rotation == ROTATION_270; + boolean flipped = displayInfo.rotation == ROTATION_90 + || displayInfo.rotation == ROTATION_270; int dw = flipped ? lh : lw; int dh = flipped ? lw : lh; @@ -1302,49 +1332,20 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab Path path = DisplayCutout.pathFromResources( getResources(), getDisplay().getUniqueId(), dw, dh); if (path != null) { - mBoundingPath.set(path); + cutoutPath.set(path); } else { - mBoundingPath.reset(); + cutoutPath.reset(); } Matrix m = new Matrix(); - transformPhysicalToLogicalCoordinates(mInfo.rotation, dw, dh, m); - mBoundingPath.transform(m); - if (mProtectionPathOrig != null) { - // Reset the protection path so we don't aggregate rotations - mProtectionPath.set(mProtectionPathOrig); - mProtectionPath.transform(m); - m.mapRect(mProtectionRect, mProtectionRectOrig); - } - } - - private static void transformPhysicalToLogicalCoordinates(@Surface.Rotation int rotation, - @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) { - switch (rotation) { - case ROTATION_0: - out.reset(); - break; - case ROTATION_90: - out.setRotate(270); - out.postTranslate(0, physicalWidth); - break; - case ROTATION_180: - out.setRotate(180); - out.postTranslate(physicalWidth, physicalHeight); - break; - case ROTATION_270: - out.setRotate(90); - out.postTranslate(physicalHeight, 0); - break; - default: - throw new IllegalArgumentException("Unknown rotation: " + rotation); - } + transformPhysicalToLogicalCoordinates(displayInfo.rotation, dw, dh, m); + cutoutPath.transform(m); } private void updateGravity() { LayoutParams lp = getLayoutParams(); if (lp instanceof FrameLayout.LayoutParams) { FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) lp; - int newGravity = getGravity(mInfo.displayCutout); + int newGravity = getGravity(displayInfo.displayCutout); if (flp.gravity != newGravity) { flp.gravity = newGravity; setLayoutParams(flp); @@ -1353,7 +1354,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } private boolean hasCutout() { - final DisplayCutout displayCutout = mInfo.displayCutout; + final DisplayCutout displayCutout = displayInfo.displayCutout; if (displayCutout == null) { return false; } @@ -1377,11 +1378,11 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return; } - if (mShowProtection) { + if (showProtection) { // Make sure that our measured height encompases the protection mTotalBounds.union(mBoundingRect); - mTotalBounds.union((int) mProtectionRect.left, (int) mProtectionRect.top, - (int) mProtectionRect.right, (int) mProtectionRect.bottom); + mTotalBounds.union((int) protectionRect.left, (int) protectionRect.top, + (int) protectionRect.right, (int) protectionRect.bottom); setMeasuredDimension( resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0), resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0)); @@ -1413,7 +1414,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } private void localBounds(Rect out) { - DisplayCutout displayCutout = mInfo.displayCutout; + DisplayCutout displayCutout = displayInfo.displayCutout; boundsFromDirection(displayCutout, getGravity(displayCutout), out); } @@ -1437,32 +1438,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } return Gravity.NO_GRAVITY; } - - @Override - public boolean shouldInterceptTouch() { - return mInfo.displayCutout != null && getVisibility() == VISIBLE; - } - - @Override - public Region getInterceptRegion() { - if (mInfo.displayCutout == null) { - return null; - } - - View rootView = getRootView(); - Region cutoutBounds = rectsToRegion( - mInfo.displayCutout.getBoundingRects()); - - // Transform to window's coordinate space - rootView.getLocationOnScreen(mLocation); - cutoutBounds.translate(-mLocation[0], -mLocation[1]); - - // Intersect with window's frame - cutoutBounds.op(rootView.getLeft(), rootView.getTop(), rootView.getRight(), - rootView.getBottom(), Region.Op.INTERSECT); - - return cutoutBounds; - } } /** @@ -1473,6 +1448,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab private final View mView; private final int mTargetRotation; + // Pass -1 for ScreenDecorHwcLayer since it's a fullscreen window and has no specific + // position. private final int mPosition; private RestartingPreDrawListener(View view, @BoundsPosition int position, @@ -1488,7 +1465,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (mTargetRotation == mRotation) { if (DEBUG) { - Log.i(TAG, getWindowTitleByPos(mPosition) + " already in target rot " + final String title = mPosition < 0 ? "ScreenDecorHwcLayer" + : getWindowTitleByPos(mPosition); + Log.i(TAG, title + " already in target rot " + mTargetRotation + ", allow draw without restarting it"); } return true; @@ -1499,7 +1478,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab // take effect. updateOrientation(); if (DEBUG) { - Log.i(TAG, getWindowTitleByPos(mPosition) + final String title = mPosition < 0 ? "ScreenDecorHwcLayer" + : getWindowTitleByPos(mPosition); + Log.i(TAG, title + " restarting listener fired, restarting draw for rot " + mRotation); } mView.invalidate(); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 5bdee2a61b9b..23ca923957b6 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -49,8 +49,11 @@ import com.android.systemui.util.NotificationChannels; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Collections; +import java.util.Comparator; +import java.util.Map; +import java.util.TreeMap; + +import javax.inject.Provider; /** * Application class for SystemUI. @@ -181,17 +184,16 @@ public class SystemUIApplication extends Application implements */ public void startServicesIfNeeded() { - final String[] names = SystemUIFactory.getInstance() - .getSystemUIServiceComponents(getResources()); - final String[] additionalNames = SystemUIFactory.getInstance() - .getAdditionalSystemUIServiceComponents(getResources()); - - final ArrayList<String> serviceComponents = new ArrayList<>(); - Collections.addAll(serviceComponents, names); - Collections.addAll(serviceComponents, additionalNames); - - startServicesIfNeeded(/* metricsPrefix= */ "StartServices", - serviceComponents.toArray(new String[serviceComponents.size()])); + final String vendorComponent = SystemUIFactory.getInstance() + .getVendorComponent(getResources()); + + // Sort the startables so that we get a deterministic ordering. + // TODO: make #start idempotent and require users of CoreStartable to call it. + Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>( + Comparator.comparing(Class::getName)); + sortedStartables.putAll(SystemUIFactory.getInstance().getStartableComponents()); + startServicesIfNeeded( + sortedStartables, "StartServices", vendorComponent); } /** @@ -201,16 +203,22 @@ public class SystemUIApplication extends Application implements * <p>This method must only be called from the main thread.</p> */ void startSecondaryUserServicesIfNeeded() { - String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponentsPerUser( - getResources()); - startServicesIfNeeded(/* metricsPrefix= */ "StartSecondaryServices", names); + // Sort the startables so that we get a deterministic ordering. + Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>( + Comparator.comparing(Class::getName)); + sortedStartables.putAll(SystemUIFactory.getInstance().getStartableComponentsPerUser()); + startServicesIfNeeded( + sortedStartables, "StartSecondaryServices", null); } - private void startServicesIfNeeded(String metricsPrefix, String[] services) { + private void startServicesIfNeeded( + Map<Class<?>, Provider<CoreStartable>> startables, + String metricsPrefix, + String vendorComponent) { if (mServicesStarted) { return; } - mServices = new CoreStartable[services.length]; + mServices = new CoreStartable[startables.size() + (vendorComponent == null ? 0 : 1)]; if (!mBootCompleteCache.isBootComplete()) { // check to see if maybe it was already completed long before we began @@ -230,36 +238,29 @@ public class SystemUIApplication extends Application implements TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming", Trace.TRACE_TAG_APP); log.traceBegin(metricsPrefix); - final int N = services.length; - for (int i = 0; i < N; i++) { - String clsName = services[i]; - if (DEBUG) Log.d(TAG, "loading: " + clsName); - log.traceBegin(metricsPrefix + clsName); - long ti = System.currentTimeMillis(); - try { - CoreStartable obj = mComponentHelper.resolveCoreStartable(clsName); - if (obj == null) { - Constructor constructor = Class.forName(clsName).getConstructor(Context.class); - obj = (CoreStartable) constructor.newInstance(this); - } - mServices[i] = obj; - } catch (ClassNotFoundException - | NoSuchMethodException - | IllegalAccessException - | InstantiationException - | InvocationTargetException ex) { - throw new RuntimeException(ex); - } - if (DEBUG) Log.d(TAG, "running: " + mServices[i]); - mServices[i].start(); - log.traceEnd(); + int i = 0; + for (Map.Entry<Class<?>, Provider<CoreStartable>> entry : startables.entrySet()) { + String clsName = entry.getKey().getName(); + int j = i; // Copied to make lambda happy. + timeInitialization( + clsName, + () -> mServices[j] = startStartable(clsName, entry.getValue()), + log, + metricsPrefix); + i++; + } - // Warn if initialization of component takes too long - ti = System.currentTimeMillis() - ti; - if (ti > 1000) { - Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms"); - } + if (vendorComponent != null) { + timeInitialization( + vendorComponent, + () -> mServices[mServices.length - 1] = + startAdditionalStartable(vendorComponent), + log, + metricsPrefix); + } + + for (i = 0; i < mServices.length; i++) { if (mBootCompleteCache.isBootComplete()) { mServices[i].onBootCompleted(); } @@ -272,6 +273,50 @@ public class SystemUIApplication extends Application implements mServicesStarted = true; } + private void timeInitialization(String clsName, Runnable init, TimingsTraceLog log, + String metricsPrefix) { + long ti = System.currentTimeMillis(); + log.traceBegin(metricsPrefix + " " + clsName); + init.run(); + log.traceEnd(); + + // Warn if initialization of component takes too long + ti = System.currentTimeMillis() - ti; + if (ti > 1000) { + Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms"); + } + } + + private CoreStartable startAdditionalStartable(String clsName) { + CoreStartable startable; + if (DEBUG) Log.d(TAG, "loading: " + clsName); + try { + Constructor<?> constructor = Class.forName(clsName).getConstructor( + Context.class); + startable = (CoreStartable) constructor.newInstance(this); + } catch (ClassNotFoundException + | NoSuchMethodException + | IllegalAccessException + | InstantiationException + | InvocationTargetException ex) { + throw new RuntimeException(ex); + } + + return startStartable(startable); + } + + private CoreStartable startStartable(String clsName, Provider<CoreStartable> provider) { + if (DEBUG) Log.d(TAG, "loading: " + clsName); + return startStartable(provider.get()); + } + + private CoreStartable startStartable(CoreStartable startable) { + if (DEBUG) Log.d(TAG, "running: " + startable); + startable.start(); + + return startable; + } + // TODO(b/217567642): add unit tests? There doesn't seem to be a SystemUiApplicationTest... @Override public boolean addDumpable(Dumpable dumpable) { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index b3be87731fbc..11fffd053143 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -32,10 +32,13 @@ import com.android.systemui.navigationbar.gestural.BackGestureTfClassifierProvid import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider; import com.android.wm.shell.transition.ShellTransitions; +import java.util.Map; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import javax.inject.Provider; + /** * Class factory to provide customizable SystemUI components. */ @@ -190,24 +193,24 @@ public class SystemUIFactory { } /** - * Returns the list of system UI components that should be started. + * Returns the list of {@link CoreStartable} components that should be started at startup. */ - public String[] getSystemUIServiceComponents(Resources resources) { - return resources.getStringArray(R.array.config_systemUIServiceComponents); + public Map<Class<?>, Provider<CoreStartable>> getStartableComponents() { + return mSysUIComponent.getStartables(); } /** * Returns the list of additional system UI components that should be started. */ - public String[] getAdditionalSystemUIServiceComponents(Resources resources) { - return resources.getStringArray(R.array.config_additionalSystemUIServiceComponents); + public String getVendorComponent(Resources resources) { + return resources.getString(R.string.config_systemUIVendorServiceComponent); } /** - * Returns the list of system UI components that should be started per user. + * Returns the list of {@link CoreStartable} components that should be started per user. */ - public String[] getSystemUIServiceComponentsPerUser(Resources resources) { - return resources.getStringArray(R.array.config_systemUIServiceComponentsPerUser); + public Map<Class<?>, Provider<CoreStartable>> getStartableComponentsPerUser() { + return mSysUIComponent.getPerUserStartables(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java index f8e7697f5831..2b0c083e2f31 100644 --- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java @@ -56,6 +56,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.text.NumberFormat; +import java.util.ArrayList; public class BatteryMeterView extends LinearLayout implements DarkReceiver { @@ -125,7 +126,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { updateShowPercent(); mDualToneHandler = new DualToneHandler(context); // Init to not dark at all. - onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT); + onDarkChanged(new ArrayList<Rect>(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT); setClipChildren(false); setClipToPadding(false); @@ -353,8 +354,8 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { } @Override - public void onDarkChanged(Rect area, float darkIntensity, int tint) { - float intensity = DarkIconDispatcher.isInArea(area, this) ? darkIntensity : 0; + public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) { + float intensity = DarkIconDispatcher.isInAreas(areas, this) ? darkIntensity : 0; mNonAdaptedSingleToneColor = mDualToneHandler.getSingleColor(intensity); mNonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity); mNonAdaptedBackgroundColor = mDualToneHandler.getBackgroundColor(intensity); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java index 72b40d42b7b8..54664f2fdd93 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -48,7 +48,7 @@ public class ClipboardListener extends CoreStartable @Override public void start() { if (DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, false)) { + DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, true)) { mClipboardManager = requireNonNull(mContext.getSystemService(ClipboardManager.class)); mClipboardManager.addPrimaryClipChangedListener(this); } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index 236c43baec9e..40689ee8cead 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -262,7 +262,8 @@ public class ClipboardOverlayController { resetActionChips(); for (RemoteAction action : actions) { Intent targetIntent = action.getActionIntent().getIntent(); - if (!TextUtils.equals(source, targetIntent.getComponent().getPackageName())) { + ComponentName component = targetIntent.getComponent(); + if (component != null && !TextUtils.equals(source, component.getPackageName())) { OverlayActionChip chip = constructActionChip(action); mActionContainer.addView(chip); mActionChips.add(chip); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java index f53221c959a5..e868d43a6a8e 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java @@ -20,7 +20,6 @@ import android.app.Activity; import android.app.Service; import android.content.BroadcastReceiver; -import com.android.systemui.CoreStartable; import com.android.systemui.recents.RecentsImplementation; /** @@ -37,8 +36,5 @@ public interface ContextComponentHelper { Service resolveService(String className); /** Turns a classname into an instance of the class or returns null. */ - CoreStartable resolveCoreStartable(String className); - - /** Turns a classname into an instance of the class or returns null. */ BroadcastReceiver resolveBroadcastReceiver(String className); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java index fba8d351e990..3607e2578bc5 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java @@ -20,7 +20,6 @@ import android.app.Activity; import android.app.Service; import android.content.BroadcastReceiver; -import com.android.systemui.CoreStartable; import com.android.systemui.recents.RecentsImplementation; import java.util.Map; @@ -35,19 +34,16 @@ import javax.inject.Provider; public class ContextComponentResolver implements ContextComponentHelper { private final Map<Class<?>, Provider<Activity>> mActivityCreators; private final Map<Class<?>, Provider<Service>> mServiceCreators; - private final Map<Class<?>, Provider<CoreStartable>> mSystemUICreators; private final Map<Class<?>, Provider<RecentsImplementation>> mRecentsCreators; private final Map<Class<?>, Provider<BroadcastReceiver>> mBroadcastReceiverCreators; @Inject ContextComponentResolver(Map<Class<?>, Provider<Activity>> activityCreators, Map<Class<?>, Provider<Service>> serviceCreators, - Map<Class<?>, Provider<CoreStartable>> systemUICreators, Map<Class<?>, Provider<RecentsImplementation>> recentsCreators, Map<Class<?>, Provider<BroadcastReceiver>> broadcastReceiverCreators) { mActivityCreators = activityCreators; mServiceCreators = serviceCreators; - mSystemUICreators = systemUICreators; mRecentsCreators = recentsCreators; mBroadcastReceiverCreators = broadcastReceiverCreators; } @@ -84,14 +80,6 @@ public class ContextComponentResolver implements ContextComponentHelper { return resolve(className, mServiceCreators); } - /** - * Looks up the SystemUI class name to see if Dagger has an instance of it. - */ - @Override - public CoreStartable resolveCoreStartable(String className) { - return resolve(className, mSystemUICreators); - } - private <T> T resolve(String className, Map<Class<?>, Provider<T>> creators) { try { Class<?> clazz = Class.forName(className); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java index 33f07c716f95..e1cbdcdd86c1 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java @@ -25,6 +25,7 @@ import com.android.systemui.dreams.DreamOverlayService; import com.android.systemui.dump.SystemUIAuxiliaryDumpService; import com.android.systemui.keyguard.KeyguardService; import com.android.systemui.screenrecord.RecordingService; +import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins; import dagger.Binds; import dagger.Module; @@ -63,6 +64,13 @@ public abstract class DefaultServiceBinder { /** */ @Binds @IntoMap + @ClassKey(NotificationListenerWithPlugins.class) + public abstract Service bindNotificationListenerWithPlugins( + NotificationListenerWithPlugins service); + + /** */ + @Binds + @IntoMap @ClassKey(SystemUIService.class) public abstract Service bindSystemUIService(SystemUIService service); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index bda8e3c2ed63..81fa99a8d22b 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -18,11 +18,14 @@ package com.android.systemui.dagger; import com.android.keyguard.clock.ClockOptionsProvider; import com.android.systemui.BootCompleteCacheImpl; +import com.android.systemui.CoreStartable; import com.android.systemui.Dependency; import com.android.systemui.InitController; import com.android.systemui.SystemUIAppComponentFactory; +import com.android.systemui.dagger.qualifiers.PerUser; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardSliceProvider; +import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli; import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper; import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver; import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender; @@ -51,8 +54,11 @@ import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper; import com.android.wm.shell.transition.ShellTransitions; +import java.util.Map; import java.util.Optional; +import javax.inject.Provider; + import dagger.BindsInstance; import dagger.Subcomponent; @@ -65,6 +71,7 @@ import dagger.Subcomponent; DependencyProvider.class, SystemUIBinder.class, SystemUIModule.class, + SystemUICoreStartableModule.class, SystemUIDefaultModule.class}) public interface SysUIComponent { @@ -144,6 +151,7 @@ public interface SysUIComponent { getMediaTttChipControllerSender(); getMediaTttChipControllerReceiver(); getMediaTttCommandLineHelper(); + getMediaMuteAwaitConnectionCli(); getUnfoldLatencyTracker().init(); getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init); getFoldStateLogger().ifPresent(FoldStateLogger::init); @@ -220,6 +228,19 @@ public interface SysUIComponent { /** */ Optional<MediaTttCommandLineHelper> getMediaTttCommandLineHelper(); + /** */ + Optional<MediaMuteAwaitConnectionCli> getMediaMuteAwaitConnectionCli(); + + /** + * Returns {@link CoreStartable}s that should be started with the application. + */ + Map<Class<?>, Provider<CoreStartable>> getStartables(); + + /** + * Returns {@link CoreStartable}s that should be started for every user. + */ + @PerUser Map<Class<?>, Provider<CoreStartable>> getPerUserStartables(); + /** * Member injection into the supplied argument. */ diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java index ec2beb15959e..b32f8786899a 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -16,46 +16,11 @@ package com.android.systemui.dagger; -import com.android.keyguard.KeyguardBiometricLockoutLogger; -import com.android.systemui.CoreStartable; -import com.android.systemui.LatencyTester; -import com.android.systemui.ScreenDecorations; -import com.android.systemui.SliceBroadcastRelayHandler; -import com.android.systemui.accessibility.SystemActions; -import com.android.systemui.accessibility.WindowMagnification; -import com.android.systemui.biometrics.AuthController; -import com.android.systemui.clipboardoverlay.ClipboardListener; -import com.android.systemui.dreams.DreamOverlayRegistrant; -import com.android.systemui.dreams.SmartSpaceComplication; -import com.android.systemui.dreams.complication.DreamClockDateComplication; -import com.android.systemui.dreams.complication.DreamClockTimeComplication; -import com.android.systemui.dreams.complication.DreamWeatherComplication; -import com.android.systemui.globalactions.GlobalActionsComponent; -import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.dagger.KeyguardModule; -import com.android.systemui.log.SessionTracker; -import com.android.systemui.media.dream.MediaDreamSentinel; -import com.android.systemui.media.systemsounds.HomeSoundEffectController; -import com.android.systemui.power.PowerUI; -import com.android.systemui.privacy.television.TvOngoingPrivacyChip; -import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsModule; -import com.android.systemui.shortcut.ShortcutKeyDispatcher; import com.android.systemui.statusbar.dagger.StatusBarModule; -import com.android.systemui.statusbar.notification.InstantAppNotifier; -import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.tv.TvStatusBar; -import com.android.systemui.statusbar.tv.notifications.TvNotificationPanel; -import com.android.systemui.theme.ThemeOverlayController; -import com.android.systemui.toast.ToastUI; -import com.android.systemui.util.leak.GarbageMonitor; -import com.android.systemui.volume.VolumeUI; -import com.android.systemui.wmshell.WMShell; -import dagger.Binds; import dagger.Module; -import dagger.multibindings.ClassKey; -import dagger.multibindings.IntoMap; /** * SystemUI objects that are injectable should go here. @@ -66,196 +31,4 @@ import dagger.multibindings.IntoMap; KeyguardModule.class, }) public abstract class SystemUIBinder { - /** Inject into AuthController. */ - @Binds - @IntoMap - @ClassKey(AuthController.class) - public abstract CoreStartable bindAuthController(AuthController service); - - /** Inject into SessionTracker. */ - @Binds - @IntoMap - @ClassKey(SessionTracker.class) - public abstract CoreStartable bindSessionTracker(SessionTracker service); - - /** Inject into GarbageMonitor.Service. */ - @Binds - @IntoMap - @ClassKey(GarbageMonitor.Service.class) - public abstract CoreStartable bindGarbageMonitorService(GarbageMonitor.Service sysui); - - /** Inject into ClipboardListener. */ - @Binds - @IntoMap - @ClassKey(ClipboardListener.class) - public abstract CoreStartable bindClipboardListener(ClipboardListener sysui); - - /** Inject into GlobalActionsComponent. */ - @Binds - @IntoMap - @ClassKey(GlobalActionsComponent.class) - public abstract CoreStartable bindGlobalActionsComponent(GlobalActionsComponent sysui); - - /** Inject into InstantAppNotifier. */ - @Binds - @IntoMap - @ClassKey(InstantAppNotifier.class) - public abstract CoreStartable bindInstantAppNotifier(InstantAppNotifier sysui); - - /** Inject into KeyguardViewMediator. */ - @Binds - @IntoMap - @ClassKey(KeyguardViewMediator.class) - public abstract CoreStartable bindKeyguardViewMediator(KeyguardViewMediator sysui); - - /** Inject into KeyguardBiometricLockoutLogger. */ - @Binds - @IntoMap - @ClassKey(KeyguardBiometricLockoutLogger.class) - public abstract CoreStartable bindKeyguardBiometricLockoutLogger( - KeyguardBiometricLockoutLogger sysui); - - /** Inject into LatencyTests. */ - @Binds - @IntoMap - @ClassKey(LatencyTester.class) - public abstract CoreStartable bindLatencyTester(LatencyTester sysui); - - /** Inject into PowerUI. */ - @Binds - @IntoMap - @ClassKey(PowerUI.class) - public abstract CoreStartable bindPowerUI(PowerUI sysui); - - /** Inject into Recents. */ - @Binds - @IntoMap - @ClassKey(Recents.class) - public abstract CoreStartable bindRecents(Recents sysui); - - /** Inject into ScreenDecorations. */ - @Binds - @IntoMap - @ClassKey(ScreenDecorations.class) - public abstract CoreStartable bindScreenDecorations(ScreenDecorations sysui); - - /** Inject into ShortcutKeyDispatcher. */ - @Binds - @IntoMap - @ClassKey(ShortcutKeyDispatcher.class) - public abstract CoreStartable bindsShortcutKeyDispatcher(ShortcutKeyDispatcher sysui); - - /** Inject into SliceBroadcastRelayHandler. */ - @Binds - @IntoMap - @ClassKey(SliceBroadcastRelayHandler.class) - public abstract CoreStartable bindSliceBroadcastRelayHandler(SliceBroadcastRelayHandler sysui); - - /** Inject into StatusBar. */ - @Binds - @IntoMap - @ClassKey(StatusBar.class) - public abstract CoreStartable bindsStatusBar(StatusBar sysui); - - /** Inject into SystemActions. */ - @Binds - @IntoMap - @ClassKey(SystemActions.class) - public abstract CoreStartable bindSystemActions(SystemActions sysui); - - /** Inject into ThemeOverlayController. */ - @Binds - @IntoMap - @ClassKey(ThemeOverlayController.class) - public abstract CoreStartable bindThemeOverlayController(ThemeOverlayController sysui); - - /** Inject into ToastUI. */ - @Binds - @IntoMap - @ClassKey(ToastUI.class) - public abstract CoreStartable bindToastUI(ToastUI service); - - /** Inject into TvStatusBar. */ - @Binds - @IntoMap - @ClassKey(TvStatusBar.class) - public abstract CoreStartable bindsTvStatusBar(TvStatusBar sysui); - - /** Inject into TvNotificationPanel. */ - @Binds - @IntoMap - @ClassKey(TvNotificationPanel.class) - public abstract CoreStartable bindsTvNotificationPanel(TvNotificationPanel sysui); - - /** Inject into TvOngoingPrivacyChip. */ - @Binds - @IntoMap - @ClassKey(TvOngoingPrivacyChip.class) - public abstract CoreStartable bindsTvOngoingPrivacyChip(TvOngoingPrivacyChip sysui); - - /** Inject into VolumeUI. */ - @Binds - @IntoMap - @ClassKey(VolumeUI.class) - public abstract CoreStartable bindVolumeUI(VolumeUI sysui); - - /** Inject into WindowMagnification. */ - @Binds - @IntoMap - @ClassKey(WindowMagnification.class) - public abstract CoreStartable bindWindowMagnification(WindowMagnification sysui); - - /** Inject into WMShell. */ - @Binds - @IntoMap - @ClassKey(WMShell.class) - public abstract CoreStartable bindWMShell(WMShell sysui); - - /** Inject into HomeSoundEffectController. */ - @Binds - @IntoMap - @ClassKey(HomeSoundEffectController.class) - public abstract CoreStartable bindHomeSoundEffectController(HomeSoundEffectController sysui); - - /** Inject into DreamOverlay. */ - @Binds - @IntoMap - @ClassKey(DreamOverlayRegistrant.class) - public abstract CoreStartable bindDreamOverlayRegistrant( - DreamOverlayRegistrant dreamOverlayRegistrant); - - /** Inject into SmartSpaceComplication.Registrant */ - @Binds - @IntoMap - @ClassKey(SmartSpaceComplication.Registrant.class) - public abstract CoreStartable bindSmartSpaceComplicationRegistrant( - SmartSpaceComplication.Registrant registrant); - - /** Inject into MediaDreamSentinel. */ - @Binds - @IntoMap - @ClassKey(MediaDreamSentinel.class) - public abstract CoreStartable bindMediaDreamSentinel( - MediaDreamSentinel sentinel); - - /** Inject into DreamClockTimeComplication.Registrant */ - @Binds - @IntoMap - @ClassKey(DreamClockTimeComplication.Registrant.class) - public abstract CoreStartable bindDreamClockTimeComplicationRegistrant( - DreamClockTimeComplication.Registrant registrant); - - /** Inject into DreamClockDateComplication.Registrant */ - @Binds - @IntoMap - @ClassKey(DreamClockDateComplication.Registrant.class) - public abstract CoreStartable bindDreamClockDateComplicationRegistrant( - DreamClockDateComplication.Registrant registrant); - - /** Inject into DreamWeatherComplication.Registrant */ - @Binds - @IntoMap - @ClassKey(DreamWeatherComplication.Registrant.class) - public abstract CoreStartable bindDreamWeatherComplicationRegistrant( - DreamWeatherComplication.Registrant registrant); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt new file mode 100644 index 000000000000..f78929f75b04 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dagger + +import com.android.keyguard.KeyguardBiometricLockoutLogger +import com.android.systemui.CoreStartable +import com.android.systemui.LatencyTester +import com.android.systemui.ScreenDecorations +import com.android.systemui.SliceBroadcastRelayHandler +import com.android.systemui.accessibility.SystemActions +import com.android.systemui.accessibility.WindowMagnification +import com.android.systemui.biometrics.AuthController +import com.android.systemui.clipboardoverlay.ClipboardListener +import com.android.systemui.dagger.qualifiers.PerUser +import com.android.systemui.globalactions.GlobalActionsComponent +import com.android.systemui.keyboard.KeyboardUI +import com.android.systemui.keyguard.KeyguardViewMediator +import com.android.systemui.log.SessionTracker +import com.android.systemui.media.RingtonePlayer +import com.android.systemui.power.PowerUI +import com.android.systemui.recents.Recents +import com.android.systemui.shortcut.ShortcutKeyDispatcher +import com.android.systemui.statusbar.notification.InstantAppNotifier +import com.android.systemui.theme.ThemeOverlayController +import com.android.systemui.toast.ToastUI +import com.android.systemui.usb.StorageNotification +import com.android.systemui.util.NotificationChannels +import com.android.systemui.util.leak.GarbageMonitor +import com.android.systemui.volume.VolumeUI +import com.android.systemui.wmshell.WMShell +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +/** + * Collection of {@link CoreStartable}s that should be run on AOSP. + */ +@Module +abstract class SystemUICoreStartableModule { + /** Inject into AuthController. */ + @Binds + @IntoMap + @ClassKey(AuthController::class) + abstract fun bindAuthController(service: AuthController): CoreStartable + + /** Inject into ClipboardListener. */ + @Binds + @IntoMap + @ClassKey(ClipboardListener::class) + abstract fun bindClipboardListener(sysui: ClipboardListener): CoreStartable + + /** Inject into GarbageMonitor.Service. */ + @Binds + @IntoMap + @ClassKey(GarbageMonitor::class) + abstract fun bindGarbageMonitorService(sysui: GarbageMonitor.Service): CoreStartable + + /** Inject into GlobalActionsComponent. */ + @Binds + @IntoMap + @ClassKey(GlobalActionsComponent::class) + abstract fun bindGlobalActionsComponent(sysui: GlobalActionsComponent): CoreStartable + + /** Inject into InstantAppNotifier. */ + @Binds + @IntoMap + @ClassKey(InstantAppNotifier::class) + abstract fun bindInstantAppNotifier(sysui: InstantAppNotifier): CoreStartable + + /** Inject into KeyboardUI. */ + @Binds + @IntoMap + @ClassKey(KeyboardUI::class) + abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable + + /** Inject into KeyguardBiometricLockoutLogger */ + @Binds + @IntoMap + @ClassKey(KeyguardBiometricLockoutLogger::class) + abstract fun bindKeyguardBiometricLockoutLogger( + sysui: KeyguardBiometricLockoutLogger + ): CoreStartable + + /** Inject into KeyguardViewMediator. */ + @Binds + @IntoMap + @ClassKey(KeyguardViewMediator::class) + abstract fun bindKeyguardViewMediator(sysui: KeyguardViewMediator): CoreStartable + + /** Inject into LatencyTests. */ + @Binds + @IntoMap + @ClassKey(LatencyTester::class) + abstract fun bindLatencyTester(sysui: LatencyTester): CoreStartable + + /** Inject into NotificationChannels. */ + @Binds + @IntoMap + @ClassKey(NotificationChannels::class) + @PerUser + abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable + + /** Inject into PowerUI. */ + @Binds + @IntoMap + @ClassKey(PowerUI::class) + abstract fun bindPowerUI(sysui: PowerUI): CoreStartable + + /** Inject into Recents. */ + @Binds + @IntoMap + @ClassKey(Recents::class) + abstract fun bindRecents(sysui: Recents): CoreStartable + + /** Inject into RingtonePlayer. */ + @Binds + @IntoMap + @ClassKey(RingtonePlayer::class) + abstract fun bind(sysui: RingtonePlayer): CoreStartable + + /** Inject into ScreenDecorations. */ + @Binds + @IntoMap + @ClassKey(ScreenDecorations::class) + abstract fun bindScreenDecorations(sysui: ScreenDecorations): CoreStartable + + /** Inject into SessionTracker. */ + @Binds + @IntoMap + @ClassKey(SessionTracker::class) + abstract fun bindSessionTracker(service: SessionTracker): CoreStartable + + /** Inject into ShortcutKeyDispatcher. */ + @Binds + @IntoMap + @ClassKey(ShortcutKeyDispatcher::class) + abstract fun bindShortcutKeyDispatcher(sysui: ShortcutKeyDispatcher): CoreStartable + + /** Inject into SliceBroadcastRelayHandler. */ + @Binds + @IntoMap + @ClassKey(SliceBroadcastRelayHandler::class) + abstract fun bindSliceBroadcastRelayHandler(sysui: SliceBroadcastRelayHandler): CoreStartable + + /** Inject into StorageNotification. */ + @Binds + @IntoMap + @ClassKey(StorageNotification::class) + abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable + + /** Inject into SystemActions. */ + @Binds + @IntoMap + @ClassKey(SystemActions::class) + abstract fun bindSystemActions(sysui: SystemActions): CoreStartable + + /** Inject into ThemeOverlayController. */ + @Binds + @IntoMap + @ClassKey(ThemeOverlayController::class) + abstract fun bindThemeOverlayController(sysui: ThemeOverlayController): CoreStartable + + /** Inject into ToastUI. */ + @Binds + @IntoMap + @ClassKey(ToastUI::class) + abstract fun bindToastUI(service: ToastUI): CoreStartable + + /** Inject into VolumeUI. */ + @Binds + @IntoMap + @ClassKey(VolumeUI::class) + abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable + + /** Inject into WindowMagnification. */ + @Binds + @IntoMap + @ClassKey(WindowMagnification::class) + abstract fun bindWindowMagnification(sysui: WindowMagnification): CoreStartable + + /** Inject into WMShell. */ + @Binds + @IntoMap + @ClassKey(WMShell::class) + abstract fun bindWMShell(sysui: WMShell): CoreStartable +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java index a17873869691..a4da6b422bde 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java @@ -48,6 +48,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.dagger.StartStatusBarModule; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; @@ -86,6 +87,7 @@ import dagger.Provides; MediaModule.class, PowerModule.class, QSModule.class, + StartStatusBarModule.class, VolumeModule.class }) public abstract class SystemUIDefaultModule { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java new file mode 100644 index 000000000000..f6d5ece74ae7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java @@ -0,0 +1,30 @@ +/* + * 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.dagger.qualifiers; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface PerUser { +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java index 63d4d6becc27..a9e310d25f9c 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java @@ -105,17 +105,7 @@ public class DozeScreenState implements DozeMachine.Part { updateUdfpsController(); if (mUdfpsController == null) { - mAuthController.addCallback(new AuthController.Callback() { - @Override - public void onAllAuthenticatorsRegistered() { - updateUdfpsController(); - } - - @Override - public void onEnrollmentsChanged() { - updateUdfpsController(); - } - }); + mAuthController.addCallback(mAuthControllerCallback); } } @@ -128,6 +118,11 @@ public class DozeScreenState implements DozeMachine.Part { } @Override + public void destroy() { + mAuthController.removeCallback(mAuthControllerCallback); + } + + @Override public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { int screenState = newState.screenState(mParameters); mDozeHost.cancelGentleSleep(); @@ -234,4 +229,16 @@ public class DozeScreenState implements DozeMachine.Part { mWakeLock.setAcquired(false); } } + + private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { + @Override + public void onAllAuthenticatorsRegistered() { + updateUdfpsController(); + } + + @Override + public void onEnrollmentsChanged() { + updateUdfpsController(); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 77997e437e33..4dacf654496c 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -31,10 +31,8 @@ import androidx.lifecycle.ViewModelStore; import com.android.internal.policy.PhoneWindow; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; -import com.android.settingslib.dream.DreamBackend; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.complication.Complication; -import com.android.systemui.dreams.complication.ComplicationUtils; import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor; @@ -59,7 +57,6 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ // content area). private final DreamOverlayContainerViewController mDreamOverlayContainerViewController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final DreamBackend mDreamBackend; // A reference to the {@link Window} used to hold the dream overlay. private Window mWindow; @@ -112,7 +109,6 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ setCurrentState(Lifecycle.State.CREATED); mLifecycleRegistry = component.getLifecycleRegistry(); mDreamOverlayTouchMonitor = component.getDreamOverlayTouchMonitor(); - mDreamBackend = component.getDreamBackend(); mDreamOverlayTouchMonitor.init(); } @@ -122,10 +118,12 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ @Override public void onDestroy() { - mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback); + mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback); setCurrentState(Lifecycle.State.DESTROYED); final WindowManager windowManager = mContext.getSystemService(WindowManager.class); - windowManager.removeView(mWindow.getDecorView()); + if (mWindow != null) { + windowManager.removeView(mWindow.getDecorView()); + } mStateController.setOverlayActive(false); super.onDestroy(); } @@ -134,9 +132,6 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) { setCurrentState(Lifecycle.State.STARTED); mExecutor.execute(() -> { - mStateController.setAvailableComplicationTypes( - ComplicationUtils.convertComplicationTypes( - mDreamBackend.getEnabledComplications())); addOverlayWindowLocked(layoutParams); setCurrentState(Lifecycle.State.RESUMED); mStateController.setOverlayActive(true); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java new file mode 100644 index 000000000000..83249aa324d1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication; + +import android.content.Context; +import android.database.ContentObserver; +import android.os.UserHandle; +import android.provider.Settings; + +import com.android.settingslib.dream.DreamBackend; +import com.android.systemui.CoreStartable; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.util.settings.SecureSettings; + +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +/** + * {@link ComplicationTypesUpdater} observes the state of available complication types set by the + * user, and pushes updates to {@link DreamOverlayStateController}. + */ +@SysUISingleton +public class ComplicationTypesUpdater extends CoreStartable { + private final DreamBackend mDreamBackend; + private final Executor mExecutor; + private final SecureSettings mSecureSettings; + + private final DreamOverlayStateController mDreamOverlayStateController; + + @Inject + ComplicationTypesUpdater(Context context, + DreamBackend dreamBackend, + @Main Executor executor, + SecureSettings secureSettings, + DreamOverlayStateController dreamOverlayStateController) { + super(context); + + mDreamBackend = dreamBackend; + mExecutor = executor; + mSecureSettings = secureSettings; + mDreamOverlayStateController = dreamOverlayStateController; + } + + @Override + public void start() { + final ContentObserver settingsObserver = new ContentObserver(null /*handler*/) { + @Override + public void onChange(boolean selfChange) { + mExecutor.execute(() -> mDreamOverlayStateController.setAvailableComplicationTypes( + getAvailableComplicationTypes())); + } + }; + + mSecureSettings.registerContentObserverForUser( + Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS, + settingsObserver, + UserHandle.myUserId()); + settingsObserver.onChange(false); + } + + /** + * Returns complication types that are currently available by user setting. + */ + @Complication.ComplicationType + private int getAvailableComplicationTypes() { + return ComplicationUtils.convertComplicationTypes(mDreamBackend.getEnabledComplications()); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index d8af9e5f1f4a..c61f7968c595 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -16,10 +16,14 @@ package com.android.systemui.dreams.dagger; +import android.content.Context; + +import com.android.settingslib.dream.DreamBackend; import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule; import com.android.systemui.dreams.touch.dagger.DreamTouchModule; import dagger.Module; +import dagger.Provides; /** * Dagger Module providing Communal-related functionality. @@ -32,4 +36,11 @@ import dagger.Module; DreamOverlayComponent.class, }) public interface DreamModule { + /** + * Provides an instance of the dream backend. + */ + @Provides + static DreamBackend providesDreamBackend(Context context) { + return DreamBackend.getInstance(context); + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java index 60278a9da7eb..05ab9015fecc 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java @@ -22,7 +22,6 @@ import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; import androidx.lifecycle.ViewModelStore; -import com.android.settingslib.dream.DreamBackend; import com.android.systemui.dreams.DreamOverlayContainerViewController; import com.android.systemui.dreams.complication.Complication; import com.android.systemui.dreams.complication.dagger.ComplicationModule; @@ -69,7 +68,4 @@ public interface DreamOverlayComponent { /** Builds a {@link DreamOverlayTouchMonitor} */ DreamOverlayTouchMonitor getDreamOverlayTouchMonitor(); - - /** Builds a ${@link DreamBackend} */ - DreamBackend getDreamBackend(); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java index efa063f4dfc8..4eb5cb97607a 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java @@ -17,7 +17,6 @@ package com.android.systemui.dreams.dagger; import android.content.ContentResolver; -import android.content.Context; import android.content.res.Resources; import android.os.Handler; import android.view.LayoutInflater; @@ -28,7 +27,6 @@ import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; import com.android.internal.util.Preconditions; -import com.android.settingslib.dream.DreamBackend; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.battery.BatteryMeterViewController; @@ -149,10 +147,4 @@ public abstract class DreamOverlayModule { static Lifecycle providesLifecycle(LifecycleOwner lifecycleOwner) { return lifecycleOwner.getLifecycle(); } - - @Provides - @DreamOverlayComponent.DreamOverlayScope - static DreamBackend providesDreamBackend(Context context) { - return DreamBackend.getInstance(context); - } } diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt index fa951fa09ef6..e4a7406687d2 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt @@ -19,6 +19,7 @@ package com.android.systemui.dump import android.content.Context import android.os.SystemClock import android.os.Trace +import com.android.systemui.CoreStartable import com.android.systemui.R import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH @@ -27,6 +28,7 @@ import com.android.systemui.log.LogBuffer import java.io.FileDescriptor import java.io.PrintWriter import javax.inject.Inject +import javax.inject.Provider /** * Oversees SystemUI's output during bug reports (and dumpsys in general) @@ -80,7 +82,8 @@ import javax.inject.Inject class DumpHandler @Inject constructor( private val context: Context, private val dumpManager: DumpManager, - private val logBufferEulogizer: LogBufferEulogizer + private val logBufferEulogizer: LogBufferEulogizer, + private val startables: MutableMap<Class<*>, Provider<CoreStartable>> ) { /** * Dump the diagnostics! Behavior can be controlled via [args]. @@ -173,12 +176,21 @@ class DumpHandler @Inject constructor( pw.println("SystemUiServiceComponents configuration:") pw.print("vendor component: ") pw.println(context.resources.getString(R.string.config_systemUIVendorServiceComponent)) - dumpServiceList(pw, "global", R.array.config_systemUIServiceComponents) + val services: MutableList<String> = startables.keys + .map({ cls: Class<*> -> cls.simpleName }) + .toMutableList() + + services.add(context.resources.getString(R.string.config_systemUIVendorServiceComponent)) + dumpServiceList(pw, "global", services.toTypedArray()) dumpServiceList(pw, "per-user", R.array.config_systemUIServiceComponentsPerUser) } private fun dumpServiceList(pw: PrintWriter, type: String, resId: Int) { - val services: Array<String>? = context.resources.getStringArray(resId) + val services: Array<String> = context.resources.getStringArray(resId) + dumpServiceList(pw, type, services) + } + + private fun dumpServiceList(pw: PrintWriter, type: String, services: Array<String>?) { pw.print(type) pw.print(": ") if (services == null) { diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 51101da89748..1ba6e3453310 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -95,6 +95,10 @@ public class Flags { /***************************************/ // 500 - quick settings + /** + * @deprecated Not needed anymore + */ + @Deprecated public static final BooleanFlag NEW_USER_SWITCHER = new BooleanFlag(500, true); @@ -142,6 +146,12 @@ public class Flags { public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true); public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, true); public static final BooleanFlag MEDIA_SESSION_LAYOUT = new BooleanFlag(902, false); + public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true); + + // 1000 - dock + public static final BooleanFlag SIMULATE_DOCK_THROUGH_CHARGING = + new BooleanFlag(1000, true); + // Pay no attention to the reflection behind the curtain. // ========================== Curtain ========================== diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayoutLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayoutLite.java index f1e5b0862ae0..42230aed15d5 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayoutLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayoutLite.java @@ -16,8 +16,6 @@ package com.android.systemui.globalactions; -import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; - import android.content.Context; import android.util.AttributeSet; import android.view.View; @@ -33,15 +31,9 @@ import com.android.systemui.R; * ConstraintLayout implementation of the button layout created by the global actions dialog. */ public class GlobalActionsLayoutLite extends GlobalActionsLayout { - private final int mMaxColumns; - private final int mMaxRows; public GlobalActionsLayoutLite(Context context, AttributeSet attrs) { super(context, attrs); - mMaxColumns = getResources().getInteger( - com.android.systemui.R.integer.power_menu_lite_max_columns); - mMaxRows = getResources().getInteger( - com.android.systemui.R.integer.power_menu_lite_max_rows); setOnClickListener(v -> { }); // Prevent parent onClickListener from triggering } @@ -60,10 +52,13 @@ public class GlobalActionsLayoutLite extends GlobalActionsLayout { @Override public void onUpdateList() { super.onUpdateList(); - int nElementsWrap = (getCurrentRotation() == ROTATION_NONE) ? mMaxColumns : mMaxRows; + int nElementsWrap = getResources().getInteger( + com.android.systemui.R.integer.power_menu_lite_max_columns); int nChildren = getListView().getChildCount() - 1; // don't count flow element - if (getCurrentRotation() != ROTATION_NONE && nChildren > mMaxRows) { - // up to 4 elements can fit in a row in landscape, otherwise limit for balance + + // Avoid having just one action on the last row if there are more than 2 columns because + // it looks unbalanced. Instead, bring the column size down to balance better. + if (nChildren == nElementsWrap + 1 && nElementsWrap > 2) { nElementsWrap -= 1; } Flow flow = findViewById(R.id.list_flow); diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java index 1c0b104b6945..6f7e73fd5190 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java @@ -52,6 +52,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.systemui.CoreStartable; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.dagger.SysUISingleton; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -60,6 +61,10 @@ import java.util.Collection; import java.util.List; import java.util.Set; +import javax.inject.Inject; + +/** */ +@SysUISingleton public class KeyboardUI extends CoreStartable implements InputManager.OnTabletModeChangedListener { private static final String TAG = "KeyboardUI"; private static final boolean DEBUG = false; @@ -117,6 +122,7 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo private int mState; + @Inject public KeyboardUI(Context context) { super(context); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 88555edd1e8b..b96eee717260 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -102,7 +102,7 @@ public class KeyguardService extends Service { "persist.wm.enable_remote_keyguard_animation"; private static final int sEnableRemoteKeyguardAnimation = - SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1); + SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 2); /** * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 0f08a1863e08..ae7147ecae03 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -789,9 +789,10 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, @Override public int getBouncerPromptReason() { int currentUser = KeyguardUpdateMonitor.getCurrentUser(); - boolean trust = mUpdateMonitor.isTrustUsuallyManaged(currentUser); - boolean biometrics = mUpdateMonitor.isUnlockingWithBiometricsPossible(currentUser); - boolean any = trust || biometrics; + boolean trustAgentsEnabled = mUpdateMonitor.isTrustUsuallyManaged(currentUser); + boolean biometricsEnrolled = + mUpdateMonitor.isUnlockingWithBiometricsPossible(currentUser); + boolean any = trustAgentsEnabled || biometricsEnrolled; KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker = mUpdateMonitor.getStrongAuthTracker(); int strongAuth = strongAuthTracker.getStrongAuthForUser(currentUser); @@ -800,9 +801,10 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, return KeyguardSecurityView.PROMPT_REASON_RESTART; } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) != 0) { return KeyguardSecurityView.PROMPT_REASON_TIMEOUT; - } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) != 0) { + } else if ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) != 0) { return KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; - } else if (trust && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) { + } else if (trustAgentsEnabled + && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) { return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0 || mUpdateMonitor.isFingerprintLockedOut())) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index b3e66829a72b..ce7a6977df92 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -371,13 +371,6 @@ public class MediaControlPanel { // Output switcher chip ViewGroup seamlessView = mMediaViewHolder.getSeamless(); seamlessView.setVisibility(View.VISIBLE); - seamlessView.setOnClickListener( - v -> { - if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - mMediaOutputDialogFactory.create(data.getPackageName(), true, - mMediaViewHolder.getSeamlessButton()); - } - }); ImageView iconView = mMediaViewHolder.getSeamlessIcon(); TextView deviceName = mMediaViewHolder.getSeamlessText(); final MediaDeviceData device = data.getDevice(); @@ -387,8 +380,8 @@ public class MediaControlPanel { final float seamlessAlpha = seamlessDisabled ? DISABLED_ALPHA : 1.0f; mMediaViewHolder.getSeamlessButton().setAlpha(seamlessAlpha); seamlessView.setEnabled(!seamlessDisabled); - String deviceString = null; - if (device != null && device.getEnabled()) { + CharSequence deviceString = mContext.getString(R.string.media_seamless_other_device); + if (device != null) { Drawable icon = device.getIcon(); if (icon instanceof AdaptiveIcon) { AdaptiveIcon aIcon = (AdaptiveIcon) icon; @@ -399,13 +392,32 @@ public class MediaControlPanel { } deviceString = device.getName(); } else { - // Reset to default - Log.w(TAG, "Device is null or not enabled: " + device + ", not binding output chip."); + // Set to default icon iconView.setImageResource(R.drawable.ic_media_home_devices); - deviceString = mContext.getString(R.string.media_seamless_other_device); } deviceName.setText(deviceString); seamlessView.setContentDescription(deviceString); + seamlessView.setOnClickListener( + v -> { + if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + return; + } + if (device.getIntent() != null) { + if (device.getIntent().isActivity()) { + mActivityStarter.startActivity( + device.getIntent().getIntent(), true); + } else { + try { + device.getIntent().send(); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Device pending intent was canceled"); + } + } + } else { + mMediaOutputDialogFactory.create(data.getPackageName(), true, + mMediaViewHolder.getSeamlessButton()); + } + }); // Dismiss mMediaViewHolder.getDismissText().setAlpha(isDismissible ? 1 : DISABLED_ALPHA); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index 4b8dfdee040e..500e82efdb0a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -164,8 +164,17 @@ data class MediaAction( ) /** State of the media device. */ -data class MediaDeviceData( +data class MediaDeviceData +@JvmOverloads constructor( + /** Whether or not to enable the chip */ val enabled: Boolean, + + /** Device icon to show in the chip */ val icon: Drawable?, - val name: String? + + /** Device display name */ + val name: CharSequence?, + + /** Optional intent to override the default output switcher for this control */ + val intent: PendingIntent? = null ) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 240ca3678765..e1ff110f8d73 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -27,8 +27,6 @@ import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.content.pm.ApplicationInfo -import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.ImageDecoder import android.graphics.drawable.Icon @@ -129,7 +127,7 @@ class MediaDataManager( private val useQsMediaPlayer: Boolean, private val systemClock: SystemClock, private val tunerService: TunerService, - private val mediaFlags: MediaFlags, + private val mediaFlags: MediaFlags ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener { companion object { @@ -170,20 +168,9 @@ class MediaDataManager( /** * Check whether this notification is an RCN - * TODO(b/204910409) implement new API for explicitly declaring this */ private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean { - val pm = context.packageManager - try { - val info = pm.getApplicationInfo(sbn.packageName, PackageManager.MATCH_SYSTEM_ONLY) - if (info.privateFlags and ApplicationInfo.PRIVATE_FLAG_PRIVILEGED != 0) { - val extras = sbn.notification.extras - if (extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) { - return true - } - } - } catch (e: PackageManager.NameNotFoundException) { } - return false + return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE) } @Inject @@ -597,6 +584,25 @@ class MediaDataManager( artist = HybridGroupManager.resolveText(notif) } + // Device name (used for remote cast notifications) + var device: MediaDeviceData? = null + if (isRemoteCastNotification(sbn)) { + val extras = sbn.notification.extras + val deviceName = extras.getCharSequence(Notification.EXTRA_MEDIA_REMOTE_DEVICE, null) + val deviceIcon = extras.getInt(Notification.EXTRA_MEDIA_REMOTE_ICON, -1) + val deviceIntent = extras.getParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT) + as PendingIntent? + Log.d(TAG, "$key is RCN for $deviceName") + + if (deviceName != null && deviceIcon > -1) { + // Name and icon must be present, but intent may be null + val enabled = deviceIntent != null && deviceIntent.isActivity + val deviceDrawable = Icon.createWithResource(sbn.packageName, deviceIcon) + .loadDrawable(sbn.getPackageContext(context)) + device = MediaDeviceData(enabled, deviceDrawable, deviceName, deviceIntent) + } + } + // Control buttons // If flag is enabled and controller has a PlaybackState, create actions from session info // Otherwise, use the notification actions @@ -624,7 +630,7 @@ class MediaDataManager( val active = mediaEntries[key]?.active ?: true onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app, smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed, - semanticActions, sbn.packageName, token, notif.contentIntent, null, + semanticActions, sbn.packageName, token, notif.contentIntent, device, active, resumeAction = resumeAction, playbackLocation = playbackLocation, notificationKey = key, hasCheckedForResume = hasCheckedForResume, isPlaying = isPlaying, isClearable = sbn.isClearable(), diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt index bed254fe8249..ffae898d07fa 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt @@ -16,6 +16,7 @@ package com.android.systemui.media +import android.graphics.drawable.Drawable import android.media.MediaRouter2Manager import android.media.session.MediaController import androidx.annotation.AnyThread @@ -27,6 +28,8 @@ import com.android.systemui.Dumpable import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager +import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory import java.io.FileDescriptor import java.io.PrintWriter import java.util.concurrent.Executor @@ -41,6 +44,7 @@ class MediaDeviceManager @Inject constructor( private val controllerFactory: MediaControllerFactory, private val localMediaManagerFactory: LocalMediaManagerFactory, private val mr2manager: MediaRouter2Manager, + private val muteAwaitConnectionManagerFactory: MediaMuteAwaitConnectionManagerFactory, @Main private val fgExecutor: Executor, @Background private val bgExecutor: Executor, dumpManager: DumpManager @@ -77,11 +81,25 @@ class MediaDeviceManager @Inject constructor( var entry = entries[key] if (entry == null || entry?.token != data.token) { entry?.stop() + if (data.device != null) { + // If we were already provided device info (e.g. from RCN), keep that and don't + // listen for updates, but process once to push updates to listeners + processDevice(key, oldKey, data.device) + return + } val controller = data.token?.let { controllerFactory.create(it) } - entry = Entry(key, oldKey, controller, - localMediaManagerFactory.create(data.packageName)) + val localMediaManager = localMediaManagerFactory.create(data.packageName) + val muteAwaitConnectionManager = + muteAwaitConnectionManagerFactory.create(localMediaManager) + entry = Entry( + key, + oldKey, + controller, + localMediaManager, + muteAwaitConnectionManager + ) entries[key] = entry entry.start() } @@ -126,7 +144,8 @@ class MediaDeviceManager @Inject constructor( val key: String, val oldKey: String?, val controller: MediaController?, - val localMediaManager: LocalMediaManager + val localMediaManager: LocalMediaManager, + val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager? ) : LocalMediaManager.DeviceCallback, MediaController.Callback() { val token @@ -142,11 +161,15 @@ class MediaDeviceManager @Inject constructor( } } } + // A device that is not yet connected but is expected to connect imminently. Because it's + // expected to connect imminently, it should be displayed as the current device. + private var aboutToConnectDeviceOverride: MediaDeviceData? = null @AnyThread fun start() = bgExecutor.execute { localMediaManager.registerCallback(this) localMediaManager.startScan() + muteAwaitConnectionManager?.startListening() playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN controller?.registerCallback(this) updateCurrent() @@ -159,6 +182,7 @@ class MediaDeviceManager @Inject constructor( controller?.unregisterCallback(this) localMediaManager.stopScan() localMediaManager.unregisterCallback(this) + muteAwaitConnectionManager?.stopListening() } fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) { @@ -197,8 +221,21 @@ class MediaDeviceManager @Inject constructor( } } + override fun onAboutToConnectDeviceChanged(deviceName: String?, deviceIcon: Drawable?) { + aboutToConnectDeviceOverride = if (deviceName == null || deviceIcon == null) { + null + } else { + MediaDeviceData(enabled = true, deviceIcon, deviceName) + } + updateCurrent() + } + @WorkerThread private fun updateCurrent() { + if (aboutToConnectDeviceOverride != null) { + current = aboutToConnectDeviceOverride + return + } val device = localMediaManager.currentConnectedDevice val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt index b9795f1265fa..e1467683c986 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt @@ -37,4 +37,9 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { return featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS) && featureFlags.isEnabled(Flags.MEDIA_SESSION_LAYOUT) } -}
\ No newline at end of file + + /** + * Check whether we support displaying information about mute await connections. + */ + fun areMuteAwaitConnectionsEnabled() = featureFlags.isEnabled(Flags.MEDIA_MUTE_AWAIT) +} diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java index ae5f9b63fb3d..4e35d16457e8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java @@ -38,16 +38,20 @@ import android.provider.MediaStore; import android.util.Log; import com.android.systemui.CoreStartable; +import com.android.systemui.dagger.SysUISingleton; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; +import javax.inject.Inject; + /** * Service that offers to play ringtones by {@link Uri}, since our process has * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}. */ +@SysUISingleton public class RingtonePlayer extends CoreStartable { private static final String TAG = "RingtonePlayer"; private static final boolean LOGD = false; @@ -59,6 +63,7 @@ public class RingtonePlayer extends CoreStartable { private final NotificationPlayer mAsyncPlayer = new NotificationPlayer(TAG); private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>(); + @Inject public RingtonePlayer(Context context) { super(context); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index f8b34f9769e4..3225f73adb82 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -18,15 +18,18 @@ package com.android.systemui.media.dagger; import android.app.Service; import android.content.Context; +import android.os.Handler; import android.view.WindowManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.media.MediaDataManager; +import com.android.systemui.media.MediaFlags; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.media.MediaHost; import com.android.systemui.media.MediaHostStatesManager; import com.android.systemui.media.dream.dagger.MediaComplicationComponent; +import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli; import com.android.systemui.media.nearby.NearbyMediaDevicesService; import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper; import com.android.systemui.media.taptotransfer.MediaTttFlags; @@ -117,12 +120,14 @@ public interface MediaModule { MediaTttFlags mediaTttFlags, CommandQueue commandQueue, Context context, - WindowManager windowManager) { + WindowManager windowManager, + @Main Handler mainHandler) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); } return Optional.of( - new MediaTttChipControllerReceiver(commandQueue, context, windowManager)); + new MediaTttChipControllerReceiver( + commandQueue, context, windowManager, mainHandler)); } /** */ @@ -140,6 +145,20 @@ public interface MediaModule { new MediaTttCommandLineHelper(commandRegistry, context, mainExecutor)); } + /** */ + @Provides + @SysUISingleton + static Optional<MediaMuteAwaitConnectionCli> providesMediaMuteAwaitConnectionCli( + MediaFlags mediaFlags, + CommandRegistry commandRegistry, + Context context + ) { + if (!mediaFlags.areMuteAwaitConnectionsEnabled()) { + return Optional.empty(); + } + return Optional.of(new MediaMuteAwaitConnectionCli(commandRegistry, context)); + } + /** Inject into NearbyMediaDevicesService. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index a9e9f0fa9111..8731a2c288e8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -153,6 +153,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements @VisibleForTesting void refresh() { + refresh(false); + } + + void refresh(boolean deviceSetChanged) { // Update header icon final int iconRes = getHeaderIconRes(); final IconCompat iconCompat = getHeaderIcon(); @@ -190,7 +194,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } if (!mAdapter.isDragging() && !mAdapter.isAnimating()) { int currentActivePosition = mAdapter.getCurrentActivePosition(); - if (currentActivePosition >= 0 && currentActivePosition < mAdapter.getItemCount()) { + if (!deviceSetChanged && currentActivePosition >= 0 + && currentActivePosition < mAdapter.getItemCount()) { mAdapter.notifyItemChanged(currentActivePosition); } else { mAdapter.notifyDataSetChanged(); @@ -232,6 +237,11 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } @Override + public void onDeviceListChanged() { + mMainThreadHandler.post(() -> refresh(true)); + } + + @Override public void dismissDialog() { dismiss(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 2caecf2aec91..4961711675c9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -171,7 +171,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { @Override public void onDeviceListUpdate(List<MediaDevice> devices) { buildMediaDevices(devices); - mCallback.onRouteChanged(); + mCallback.onDeviceListChanged(); } @Override @@ -570,11 +570,16 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { void onMediaStoppedOrPaused(); /** - * Override to handle the device updating. + * Override to handle the device status or attributes updating. */ void onRouteChanged(); /** + * Override to handle the devices set updating. + */ + void onDeviceListChanged(); + + /** * Override to dismiss dialog. */ void dismissDialog(); diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt new file mode 100644 index 000000000000..2ae3a631fa5a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.muteawait + +import android.content.Context +import android.media.AudioAttributes.USAGE_MEDIA +import android.media.AudioDeviceAttributes +import android.media.AudioManager +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry +import java.io.PrintWriter +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +/** A command line interface to manually test [MediaMuteAwaitConnectionManager]. */ +@SysUISingleton +class MediaMuteAwaitConnectionCli @Inject constructor( + commandRegistry: CommandRegistry, + private val context: Context +) { + init { + commandRegistry.registerCommand(MEDIA_MUTE_AWAIT_COMMAND) { MuteAwaitCommand() } + } + + inner class MuteAwaitCommand : Command { + override fun execute(pw: PrintWriter, args: List<String>) { + val device = AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + /* type= */ Integer.parseInt(args[0]), + ADDRESS, + /* name= */ args[1], + listOf(), + listOf(), + ) + val startOrCancel = args[2] + + val audioManager: AudioManager = + context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + when (startOrCancel) { + START -> + audioManager.muteAwaitConnection( + intArrayOf(USAGE_MEDIA), device, TIMEOUT, TIMEOUT_UNITS + ) + CANCEL -> audioManager.cancelMuteAwaitConnection(device) + else -> pw.println("Must specify `$START` or `$CANCEL`; was $startOrCancel") + } + } + override fun help(pw: PrintWriter) { + pw.println("Usage: adb shell cmd statusbar $MEDIA_MUTE_AWAIT_COMMAND " + + "[type] [name] [$START|$CANCEL]") + } + } +} + +private const val MEDIA_MUTE_AWAIT_COMMAND = "media-mute-await" +private const val START = "start" +private const val CANCEL = "cancel" +private const val ADDRESS = "address" +private const val TIMEOUT = 5L +private val TIMEOUT_UNITS = TimeUnit.SECONDS diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt new file mode 100644 index 000000000000..22bc5572f5a5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.muteawait + +import android.content.Context +import android.graphics.drawable.Drawable +import android.media.AudioAttributes.USAGE_MEDIA +import android.media.AudioDeviceAttributes +import android.media.AudioManager +import com.android.settingslib.media.DeviceIconUtil +import com.android.settingslib.media.LocalMediaManager +import com.android.systemui.dagger.qualifiers.Main +import java.util.concurrent.Executor + +/** + * A class responsible for keeping track of devices that have muted audio playback until the device + * is connected. The device connection expected to happen imminently, so we'd like to display the + * device name in the media player. When the about-to-connect device changes, [localMediaManager] + * will be notified. + * + * See [AudioManager.muteAwaitConnection] and b/206614671 for more details. + * + * TODO(b/206614671): Add logging. + */ +class MediaMuteAwaitConnectionManager constructor( + @Main private val mainExecutor: Executor, + private val localMediaManager: LocalMediaManager, + private val context: Context, + private val deviceIconUtil: DeviceIconUtil +) { + var currentMutedDevice: AudioDeviceAttributes? = null + + val audioManager: AudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + + val muteAwaitConnectionChangeListener = object : AudioManager.MuteAwaitConnectionCallback() { + override fun onMutedUntilConnection(device: AudioDeviceAttributes, mutedUsages: IntArray) { + if (USAGE_MEDIA in mutedUsages) { + // There should only be one device that's mutedUntilConnection at a time, so we can + // safely override any previous value. + currentMutedDevice = device + localMediaManager.dispatchAboutToConnectDeviceChanged(device.name, device.getIcon()) + } + } + + override fun onUnmutedEvent( + @UnmuteEvent unmuteEvent: Int, + device: AudioDeviceAttributes, + mutedUsages: IntArray + ) { + if (currentMutedDevice == device && USAGE_MEDIA in mutedUsages) { + currentMutedDevice = null + localMediaManager.dispatchAboutToConnectDeviceChanged(null, null) + } + } + } + + /** Start listening for mute await events. */ + fun startListening() { + audioManager.registerMuteAwaitConnectionCallback( + mainExecutor, muteAwaitConnectionChangeListener + ) + val currentDevice = audioManager.mutingExpectedDevice + if (currentDevice != null) { + currentMutedDevice = currentDevice + localMediaManager.dispatchAboutToConnectDeviceChanged( + currentDevice.name, currentDevice.getIcon() + ) + } + } + + /** Stop listening for mute await events. */ + fun stopListening() { + audioManager.unregisterMuteAwaitConnectionCallback(muteAwaitConnectionChangeListener) + } + + private fun AudioDeviceAttributes.getIcon(): Drawable { + return deviceIconUtil.getIconFromAudioDeviceType(this.type, context) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt new file mode 100644 index 000000000000..118b2dd4dc90 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.muteawait + +import android.content.Context +import com.android.settingslib.media.DeviceIconUtil +import com.android.settingslib.media.LocalMediaManager +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.media.MediaFlags +import java.util.concurrent.Executor +import javax.inject.Inject + +/** Factory class to create [MediaMuteAwaitConnectionManager] instances. */ +@SysUISingleton +class MediaMuteAwaitConnectionManagerFactory @Inject constructor( + private val mediaFlags: MediaFlags, + private val context: Context, + @Main private val mainExecutor: Executor +) { + private val deviceIconUtil = DeviceIconUtil() + + /** Creates a [MediaMuteAwaitConnectionManager]. */ + fun create(localMediaManager: LocalMediaManager): MediaMuteAwaitConnectionManager? { + if (!mediaFlags.areMuteAwaitConnectionsEnabled()) { + return null + } + return MediaMuteAwaitConnectionManager( + mainExecutor, localMediaManager, context, deviceIconUtil + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index 26f31cd11704..9dd8222ff6a1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -78,6 +78,7 @@ class MediaTttCommandLineHelper @Inject constructor( override fun execute(pw: PrintWriter, args: List<String>) { val routeInfo = MediaRoute2Info.Builder("id", args[0]) .addFeature("feature") + .setPackageName(TEST_PACKAGE_NAME) .build() val commandName = args[1] @@ -137,16 +138,25 @@ class MediaTttCommandLineHelper @Inject constructor( override fun execute(pw: PrintWriter, args: List<String>) { val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE) as StatusBarManager + val routeInfo = MediaRoute2Info.Builder("id", "Test Name") + .addFeature("feature") + .setPackageName(TEST_PACKAGE_NAME) + .build() + when(val commandName = args[0]) { CLOSE_TO_SENDER_STATE -> statusBarManager.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, - routeInfo + routeInfo, + null, + null ) FAR_FROM_SENDER_STATE -> statusBarManager.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, - routeInfo + routeInfo, + null, + null ) else -> pw.println("Invalid command name $commandName") @@ -170,7 +180,4 @@ const val CLOSE_TO_SENDER_STATE = "CloseToSender" @VisibleForTesting const val FAR_FROM_SENDER_STATE = "FarFromSender" private const val CLI_TAG = "MediaTransferCli" - -private val routeInfo = MediaRoute2Info.Builder("id", "Test Name") - .addFeature("feature") - .build()
\ No newline at end of file +private const val TEST_PACKAGE_NAME = "com.android.systemui" diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt index 2ed2f4f3dbee..4993105fa681 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt @@ -22,6 +22,7 @@ import android.content.Context import android.graphics.PixelFormat import android.view.Gravity import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import android.view.WindowManager import com.android.internal.widget.CachingIconView @@ -35,7 +36,7 @@ import com.android.systemui.R * gets displayed to the user. */ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>( - private val context: Context, + internal val context: Context, private val windowManager: WindowManager, @LayoutRes private val chipLayoutRes: Int ) { @@ -100,10 +101,17 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>( * This is in the common superclass since both the sender and the receiver show an icon. */ internal fun setIcon(chipState: T, currentChipView: ViewGroup) { - currentChipView.findViewById<CachingIconView>(R.id.app_icon).apply { - this.setImageDrawable(chipState.appIconDrawable) - this.contentDescription = chipState.appIconContentDescription + val appIconView = currentChipView.requireViewById<CachingIconView>(R.id.app_icon) + appIconView.contentDescription = chipState.getAppName(context) + + val appIcon = chipState.getAppIcon(context) + val visibility = if (appIcon != null) { + View.VISIBLE + } else { + View.GONE } + appIconView.setImageDrawable(appIcon) + appIconView.visibility = visibility } } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt index c510cbba9c35..2da48cef792a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt @@ -16,15 +16,42 @@ package com.android.systemui.media.taptotransfer.common +import android.content.Context +import android.content.pm.PackageManager import android.graphics.drawable.Drawable +import android.util.Log /** * A superclass chip state that will be subclassed by the sender chip and receiver chip. * - * @property appIconDrawable a drawable representing the icon of the app playing the media. - * @property appIconContentDescription a string to use as the content description for the icon. + * @property appPackageName the package name of the app playing the media. Will be used to fetch the + * app icon and app name. */ open class MediaTttChipState( - internal val appIconDrawable: Drawable, - internal val appIconContentDescription: String -) + internal val appPackageName: String?, +) { + open fun getAppIcon(context: Context): Drawable? { + appPackageName ?: return null + return try { + context.packageManager.getApplicationIcon(appPackageName) + } catch (e: PackageManager.NameNotFoundException) { + Log.w(TAG, "Cannot find icon for package $appPackageName", e) + null + } + } + + /** Returns the name of the app playing the media or null if we can't find it. */ + open fun getAppName(context: Context): String? { + appPackageName ?: return null + return try { + context.packageManager.getApplicationInfo( + appPackageName, PackageManager.ApplicationInfoFlags.of(0) + ).loadLabel(context.packageManager).toString() + } catch (e: PackageManager.NameNotFoundException) { + Log.w(TAG, "Cannot find name for package $appPackageName", e) + null + } + } +} + +private val TAG = MediaTttChipState::class.simpleName!! diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt index df6b93431c93..6a4b62a8c1ae 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt @@ -16,14 +16,35 @@ package com.android.systemui.media.taptotransfer.receiver +import android.content.Context import android.graphics.drawable.Drawable import com.android.systemui.media.taptotransfer.common.MediaTttChipState /** * A class that stores all the information necessary to display the media tap-to-transfer chip on * the receiver device. + * + * @property appIconDrawable a drawable representing the icon of the app playing the media. If + * present, this will be used in [this.getAppIcon] instead of [appPackageName]. + * @property appName a name for the app playing the media. If present, this will be used in + * [this.getAppName] instead of [appPackageName]. */ class ChipStateReceiver( - appIconDrawable: Drawable, - appIconContentDescription: String -) : MediaTttChipState(appIconDrawable, appIconContentDescription) + appPackageName: String?, + private val appIconDrawable: Drawable?, + private val appName: CharSequence? +) : MediaTttChipState(appPackageName) { + override fun getAppIcon(context: Context): Drawable? { + if (appIconDrawable != null) { + return appIconDrawable + } + return super.getAppIcon(context) + } + + override fun getAppName(context: Context): String? { + if (appName != null) { + return appName.toString() + } + return super.getAppName(context) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 2d3ca5fdb6b8..18623c0686fd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -18,14 +18,17 @@ package com.android.systemui.media.taptotransfer.receiver import android.app.StatusBarManager import android.content.Context -import android.graphics.Color +import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.media.MediaRoute2Info +import android.os.Handler import android.util.Log import android.view.ViewGroup import android.view.WindowManager import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon import com.android.systemui.statusbar.CommandQueue import javax.inject.Inject @@ -40,22 +43,19 @@ class MediaTttChipControllerReceiver @Inject constructor( commandQueue: CommandQueue, context: Context, windowManager: WindowManager, + @Main private val mainHandler: Handler, ) : MediaTttChipControllerCommon<ChipStateReceiver>( context, windowManager, R.layout.media_ttt_chip_receiver ) { - // TODO(b/216141279): Use app icon from media route info instead of this fake one. - private val fakeAppIconDrawable = - Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also { - it.setTint(Color.YELLOW) - } - private val commandQueueCallbacks = object : CommandQueue.Callbacks { override fun updateMediaTapToTransferReceiverDisplay( @StatusBarManager.MediaTransferReceiverState displayState: Int, - routeInfo: MediaRoute2Info + routeInfo: MediaRoute2Info, + appIcon: Icon?, + appName: CharSequence? ) { this@MediaTttChipControllerReceiver.updateMediaTapToTransferReceiverDisplay( - displayState, routeInfo + displayState, routeInfo, appIcon, appName ) } } @@ -66,11 +66,28 @@ class MediaTttChipControllerReceiver @Inject constructor( private fun updateMediaTapToTransferReceiverDisplay( @StatusBarManager.MediaTransferReceiverState displayState: Int, - routeInfo: MediaRoute2Info + routeInfo: MediaRoute2Info, + appIcon: Icon?, + appName: CharSequence? ) { when(displayState) { - StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER -> - displayChip(ChipStateReceiver(fakeAppIconDrawable, routeInfo.name.toString())) + StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER -> { + val packageName = routeInfo.packageName + if (appIcon == null) { + displayChip(ChipStateReceiver(packageName, null, appName)) + } else { + appIcon.loadDrawableAsync( + context, + Icon.OnDrawableLoadedListener { drawable -> + displayChip( + ChipStateReceiver(packageName, drawable, appName) + )}, + // Notify the listener on the main handler since the listener will update + // the UI. + mainHandler + ) + } + } StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER -> removeChip() else -> Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState") @@ -82,4 +99,4 @@ class MediaTttChipControllerReceiver @Inject constructor( } } -private const val RECEIVER_TAG = "MediaTapToTransferReceiver" +private const val RECEIVER_TAG = "MediaTapToTransferRcvr" diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index 05baf7806bda..9b537fb48ba9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -17,7 +17,6 @@ package com.android.systemui.media.taptotransfer.sender import android.content.Context -import android.graphics.drawable.Drawable import android.view.View import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.R @@ -31,9 +30,8 @@ import com.android.systemui.media.taptotransfer.common.MediaTttChipState * contain additional information that is necessary for only that state. */ sealed class ChipStateSender( - appIconDrawable: Drawable, - appIconContentDescription: String -) : MediaTttChipState(appIconDrawable, appIconContentDescription) { + appPackageName: String? +) : MediaTttChipState(appPackageName) { /** Returns a fully-formed string with the text that the chip should display. */ abstract fun getChipTextString(context: Context): String @@ -60,10 +58,9 @@ sealed class ChipStateSender( * @property otherDeviceName the name of the other device involved in the transfer. */ class AlmostCloseToStartCast( - appIconDrawable: Drawable, - appIconContentDescription: String, + appPackageName: String?, private val otherDeviceName: String, -) : ChipStateSender(appIconDrawable, appIconContentDescription) { +) : ChipStateSender(appPackageName) { override fun getChipTextString(context: Context): String { return context.getString(R.string.media_move_closer_to_start_cast, otherDeviceName) } @@ -77,10 +74,9 @@ class AlmostCloseToStartCast( * @property otherDeviceName the name of the other device involved in the transfer. */ class AlmostCloseToEndCast( - appIconDrawable: Drawable, - appIconContentDescription: String, + appPackageName: String?, private val otherDeviceName: String, -) : ChipStateSender(appIconDrawable, appIconContentDescription) { +) : ChipStateSender(appPackageName) { override fun getChipTextString(context: Context): String { return context.getString(R.string.media_move_closer_to_end_cast, otherDeviceName) } @@ -93,10 +89,9 @@ class AlmostCloseToEndCast( * @property otherDeviceName the name of the other device involved in the transfer. */ class TransferToReceiverTriggered( - appIconDrawable: Drawable, - appIconContentDescription: String, + appPackageName: String?, private val otherDeviceName: String -) : ChipStateSender(appIconDrawable, appIconContentDescription) { +) : ChipStateSender(appPackageName) { override fun getChipTextString(context: Context): String { return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName) } @@ -109,9 +104,8 @@ class TransferToReceiverTriggered( * sender) has been initiated (but not completed). */ class TransferToThisDeviceTriggered( - appIconDrawable: Drawable, - appIconContentDescription: String -) : ChipStateSender(appIconDrawable, appIconContentDescription) { + appPackageName: String?, +) : ChipStateSender(appPackageName) { override fun getChipTextString(context: Context): String { return context.getString(R.string.media_transfer_playing_this_device) } @@ -127,11 +121,10 @@ class TransferToThisDeviceTriggered( * undo button. The undo button will only be shown if this is non-null. */ class TransferToReceiverSucceeded( - appIconDrawable: Drawable, - appIconContentDescription: String, + appPackageName: String?, private val otherDeviceName: String, val undoCallback: IUndoMediaTransferCallback? = null -) : ChipStateSender(appIconDrawable, appIconContentDescription) { +) : ChipStateSender(appPackageName) { override fun getChipTextString(context: Context): String { return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName) } @@ -149,10 +142,7 @@ class TransferToReceiverSucceeded( // but that may take too long to go through the binder and the user may be confused as // to why the UI hasn't changed yet. So, we immediately change the UI here. controllerSender.displayChip( - TransferToThisDeviceTriggered( - this.appIconDrawable, - this.appIconContentDescription - ) + TransferToThisDeviceTriggered(this.appPackageName) ) } } @@ -166,11 +156,10 @@ class TransferToReceiverSucceeded( * undo button. The undo button will only be shown if this is non-null. */ class TransferToThisDeviceSucceeded( - appIconDrawable: Drawable, - appIconContentDescription: String, + appPackageName: String?, private val otherDeviceName: String, val undoCallback: IUndoMediaTransferCallback? = null -) : ChipStateSender(appIconDrawable, appIconContentDescription) { +) : ChipStateSender(appPackageName) { override fun getChipTextString(context: Context): String { return context.getString(R.string.media_transfer_playing_this_device) } @@ -189,8 +178,7 @@ class TransferToThisDeviceSucceeded( // to why the UI hasn't changed yet. So, we immediately change the UI here. controllerSender.displayChip( TransferToReceiverTriggered( - this.appIconDrawable, - this.appIconContentDescription, + this.appPackageName, this.otherDeviceName ) ) @@ -200,9 +188,8 @@ class TransferToThisDeviceSucceeded( /** A state representing that a transfer has failed. */ class TransferFailed( - appIconDrawable: Drawable, - appIconContentDescription: String -) : ChipStateSender(appIconDrawable, appIconContentDescription) { + appPackageName: String?, +) : ChipStateSender(appPackageName) { override fun getChipTextString(context: Context): String { return context.getString(R.string.media_transfer_failed) } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index d1790d2fd5e1..da767ea90055 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -18,8 +18,6 @@ package com.android.systemui.media.taptotransfer.sender import android.app.StatusBarManager import android.content.Context -import android.graphics.Color -import android.graphics.drawable.Icon import android.media.MediaRoute2Info import android.util.Log import android.view.View @@ -45,12 +43,6 @@ class MediaTttChipControllerSender @Inject constructor( ) : MediaTttChipControllerCommon<ChipStateSender>( context, windowManager, R.layout.media_ttt_chip ) { - // TODO(b/216141276): Use app icon from media route info instead of this fake one. - private val fakeAppIconDrawable = - Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also { - it.setTint(Color.YELLOW) - } - private val commandQueueCallbacks = object : CommandQueue.Callbacks { override fun updateMediaTapToTransferSenderDisplay( @StatusBarManager.MediaTransferSenderState displayState: Int, @@ -72,46 +64,24 @@ class MediaTttChipControllerSender @Inject constructor( routeInfo: MediaRoute2Info, undoCallback: IUndoMediaTransferCallback? ) { - // TODO(b/217418566): This app icon content description is incorrect -- - // routeInfo.name is the name of the device, not the name of the app. - val appIconContentDescription = routeInfo.name.toString() + val appPackageName = routeInfo.packageName val otherDeviceName = routeInfo.name.toString() val chipState = when(displayState) { StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST -> - AlmostCloseToStartCast( - fakeAppIconDrawable, appIconContentDescription, otherDeviceName - ) + AlmostCloseToStartCast(appPackageName, otherDeviceName) StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST -> - AlmostCloseToEndCast( - fakeAppIconDrawable, appIconContentDescription, otherDeviceName - ) + AlmostCloseToEndCast(appPackageName, otherDeviceName) StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED -> - TransferToReceiverTriggered( - fakeAppIconDrawable, appIconContentDescription, otherDeviceName - ) + TransferToReceiverTriggered(appPackageName, otherDeviceName) StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED -> - TransferToThisDeviceTriggered( - fakeAppIconDrawable, appIconContentDescription - ) + TransferToThisDeviceTriggered(appPackageName) StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED -> - TransferToReceiverSucceeded( - fakeAppIconDrawable, - appIconContentDescription, - otherDeviceName, - undoCallback - ) + TransferToReceiverSucceeded(appPackageName, otherDeviceName, undoCallback) StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED -> - TransferToThisDeviceSucceeded( - fakeAppIconDrawable, - appIconContentDescription, - otherDeviceName, - undoCallback - ) + TransferToThisDeviceSucceeded(appPackageName, otherDeviceName, undoCallback) StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED -> - TransferFailed( - fakeAppIconDrawable, appIconContentDescription - ) + TransferFailed(appPackageName) StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER -> { removeChip() null diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 98b49b1c4890..aa1117ca23f7 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -31,6 +31,7 @@ import android.hardware.display.DisplayManager; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; +import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; @@ -215,9 +216,11 @@ public class NavigationBarController implements /** @return {@code true} if taskbar is enabled, false otherwise */ private boolean initializeTaskbarIfNecessary() { if (mIsTablet) { + Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary"); // Remove navigation bar when taskbar is showing removeNavigationBar(mContext.getDisplayId()); mTaskbarDelegate.init(mContext.getDisplayId()); + Trace.endSection(); } else { mTaskbarDelegate.destroy(); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index be45a62eeba6..919991189676 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -263,11 +263,14 @@ public class EdgeBackGestureHandler extends CurrentUserTracker // Notify FalsingManager that an intentional gesture has occurred. // TODO(b/186519446): use a different method than isFalseTouch mFalsingManager.isFalseTouch(BACK_GESTURE); - boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); - boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); - if (DEBUG_MISSING_GESTURE) { - Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down=" + sendDown - + ", up=" + sendUp); + // Only inject back keycodes when ahead-of-time back dispatching is disabled. + if (mBackAnimation == null) { + boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); + boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); + if (DEBUG_MISSING_GESTURE) { + Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down=" + + sendDown + ", up=" + sendUp); + } } mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x, diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java index c18209d9abca..4da574d571b2 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java @@ -43,6 +43,7 @@ import android.view.View; import android.view.WindowManager; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import android.window.BackEvent; import androidx.core.graphics.ColorUtils; import androidx.dynamicanimation.animation.DynamicAnimation; @@ -464,7 +465,8 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl @Override public void onMotionEvent(MotionEvent event) { if (mBackAnimation != null) { - mBackAnimation.onBackMotion(event); + mBackAnimation.onBackMotion( + event, mIsLeftPanel ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT); } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java b/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java deleted file mode 100644 index 18d28bf511bc..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java +++ /dev/null @@ -1,132 +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. - */ - -package com.android.systemui.qs; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.res.TypedArray; -import android.database.DataSetObserver; -import android.os.Handler; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.ListAdapter; - -import com.android.systemui.R; - -/** - * Similar to a ListView, but it will show only as many items as fit on screen and - * bind those instead of scrolling. - */ -public class AutoSizingList extends LinearLayout { - - private static final String TAG = "AutoSizingList"; - private final int mItemSize; - private final Handler mHandler; - - @Nullable - private ListAdapter mAdapter; - private int mCount; - private boolean mEnableAutoSizing; - - public AutoSizingList(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - - mHandler = new Handler(); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoSizingList); - mItemSize = a.getDimensionPixelSize(R.styleable.AutoSizingList_itemHeight, 0); - mEnableAutoSizing = a.getBoolean(R.styleable.AutoSizingList_enableAutoSizing, true); - a.recycle(); - } - - public void setAdapter(ListAdapter adapter) { - if (mAdapter != null) { - mAdapter.unregisterDataSetObserver(mDataObserver); - } - mAdapter = adapter; - if (adapter != null) { - adapter.registerDataSetObserver(mDataObserver); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int requestedHeight = MeasureSpec.getSize(heightMeasureSpec); - if (requestedHeight != 0) { - int count = getItemCount(requestedHeight); - if (mCount != count) { - postRebindChildren(); - mCount = count; - } - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - private int getItemCount(int requestedHeight) { - int desiredCount = getDesiredCount(); - return mEnableAutoSizing ? Math.min(requestedHeight / mItemSize, desiredCount) - : desiredCount; - } - - private int getDesiredCount() { - return mAdapter != null ? mAdapter.getCount() : 0; - } - - private void postRebindChildren() { - mHandler.post(mBindChildren); - } - - private void rebindChildren() { - if (mAdapter == null) { - return; - } - for (int i = 0; i < mCount; i++) { - View v = i < getChildCount() ? getChildAt(i) : null; - View newView = mAdapter.getView(i, v, this); - if (newView != v) { - if (v != null) { - removeView(v); - } - addView(newView, i); - } - } - // Ditch extra views. - while (getChildCount() > mCount) { - removeViewAt(getChildCount() - 1); - } - } - - private final Runnable mBindChildren = new Runnable() { - @Override - public void run() { - rebindChildren(); - } - }; - - private final DataSetObserver mDataObserver = new DataSetObserver() { - @Override - public void onChanged() { - if (mCount > getDesiredCount()) { - mCount = getDesiredCount(); - } - postRebindChildren(); - } - - @Override - public void onInvalidated() { - postRebindChildren(); - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt index bbe0a99309c6..89735c3a547d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt @@ -27,6 +27,7 @@ import android.os.RemoteException import android.provider.DeviceConfig.NAMESPACE_SYSTEMUI import android.text.format.DateUtils import android.util.ArrayMap +import android.util.IndentingPrintWriter import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -38,14 +39,19 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED +import com.android.systemui.Dumpable import com.android.systemui.R import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.DeviceConfigProxy +import com.android.systemui.util.indentIfPossible import com.android.systemui.util.time.SystemClock +import java.io.FileDescriptor +import java.io.PrintWriter import java.util.Objects import java.util.concurrent.Executor import javax.inject.Inject @@ -60,8 +66,9 @@ class FgsManagerController @Inject constructor( private val activityManager: IActivityManager, private val packageManager: PackageManager, private val deviceConfigProxy: DeviceConfigProxy, - private val dialogLaunchAnimator: DialogLaunchAnimator -) : IForegroundServiceObserver.Stub() { + private val dialogLaunchAnimator: DialogLaunchAnimator, + private val dumpManager: DumpManager +) : IForegroundServiceObserver.Stub(), Dumpable { companion object { private val LOG_TAG = FgsManagerController::class.java.simpleName @@ -116,6 +123,8 @@ class FgsManagerController @Inject constructor( isAvailable = deviceConfigProxy .getBoolean(NAMESPACE_SYSTEMUI, TASK_MANAGER_ENABLED, true) + dumpManager.registerDumpable(this) + initialized = true } } @@ -379,6 +388,16 @@ class FgsManagerController @Inject constructor( } override fun hashCode(): Int = Objects.hash(userId, packageName) + + fun dump(pw: PrintWriter) { + pw.println("UserPackage: [") + pw.indentIfPossible { + pw.println("userId=$userId") + pw.println("packageName=$packageName") + pw.println("uiControl=$uiControl") + } + pw.println("]") + } } private data class StartTimeAndTokens( @@ -398,6 +417,22 @@ class FgsManagerController @Inject constructor( fun isEmpty(): Boolean { return tokens.isEmpty() } + + fun dump(pw: PrintWriter) { + pw.println("StartTimeAndTokens: [") + pw.indentIfPossible { + pw.println("startTime=$startTime (time running =" + + " ${systemClock.elapsedRealtime() - startTime}ms)") + pw.println("tokens: [") + pw.indentIfPossible { + for (token in tokens) { + pw.println("$token") + } + } + pw.println("]") + } + pw.println("]") + } } private class AppItemViewHolder(parent: View) : RecyclerView.ViewHolder(parent) { @@ -429,9 +464,54 @@ class FgsManagerController @Inject constructor( var appLabel: CharSequence = "" var icon: Drawable? = null var stopped = false + + fun dump(pw: PrintWriter, systemClock: SystemClock) { + pw.println("RunningApp: [") + pw.indentIfPossible { + pw.println("userId=$userId") + pw.println("packageName=$packageName") + pw.println("timeStarted=$timeStarted (time since start =" + + " ${systemClock.elapsedRealtime() - timeStarted}ms)\"") + pw.println("uiControl=$uiControl") + pw.println("appLabel=$appLabel") + pw.println("icon=$icon") + pw.println("stopped=$stopped") + } + pw.println("]") + } } private enum class UIControl { NORMAL, HIDE_BUTTON, HIDE_ENTRY } + + override fun dump(fd: FileDescriptor, printwriter: PrintWriter, args: Array<out String>) { + val pw = IndentingPrintWriter(printwriter) + synchronized(lock) { + pw.println("changesSinceDialog=$changesSinceDialog") + pw.println("Running service tokens: [") + pw.indentIfPossible { + runningServiceTokens.forEach { (userPackage, startTimeAndTokens) -> + pw.println("{") + pw.indentIfPossible { + userPackage.dump(pw) + startTimeAndTokens.dump(pw) + } + pw.println("}") + } + } + pw.println("]") + + pw.println("Loaded package UI info: [") + pw.indentIfPossible { + runningApps.forEach { (userPackage, runningApp) -> + pw.println("{") + userPackage.dump(pw) + runningApp.dump(pw, systemClock) + pw.println("}") + } + } + pw.println("]") + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index 77feb90f575a..5df8b8082a41 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -191,6 +191,10 @@ internal class FooterActionsController @Inject constructor( reformatForNewFooter(securityFooter) val fgsFooter = fgsManagerFooterController.view securityFootersContainer?.addView(fgsFooter) + (fgsFooter.layoutParams as LinearLayout.LayoutParams).apply { + width = 0 + weight = 1f + } val visibilityListener = VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility -> diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 707313f08af6..f8680552f90a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -44,7 +44,6 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { private final float[] mFancyClippingRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0}; private final Path mFancyClippingPath = new Path(); private int mHeightOverride = -1; - private View mQSDetail; private QuickStatusBarHeader mHeader; private float mQsExpansion; private QSCustomizer mQSCustomizer; @@ -63,7 +62,6 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { protected void onFinishInflate() { super.onFinishInflate(); mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view); - mQSDetail = findViewById(R.id.qs_detail); mHeader = findViewById(R.id.header); mQSCustomizer = findViewById(R.id.qs_customize); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); @@ -175,9 +173,6 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { int height = calculateContainerHeight(); int scrollBottom = calculateContainerBottom(); setBottom(getTop() + height); - mQSDetail.setBottom(getTop() + scrollBottom); - int qsDetailBottomMargin = ((MarginLayoutParams) mQSDetail.getLayoutParams()).bottomMargin; - mQSDetail.setBottom(getTop() + scrollBottom - qsDetailBottomMargin); } protected int calculateContainerHeight() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java deleted file mode 100644 index 04e22522bcd0..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java +++ /dev/null @@ -1,439 +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. - */ - -package com.android.systemui.qs; - -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS; - -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; -import android.animation.AnimatorListenerAdapter; -import android.annotation.Nullable; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.drawable.Animatable; -import android.util.AttributeSet; -import android.util.SparseArray; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewStub; -import android.view.WindowInsets; -import android.view.accessibility.AccessibilityEvent; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.Switch; -import android.widget.TextView; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.policy.SystemBarUtils; -import com.android.systemui.Dependency; -import com.android.systemui.FontSizeUtils; -import com.android.systemui.R; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.DetailAdapter; -import com.android.systemui.plugins.qs.QSContainerController; -import com.android.systemui.statusbar.CommandQueue; - -public class QSDetail extends LinearLayout { - - private static final String TAG = "QSDetail"; - private static final long FADE_DURATION = 300; - - private final SparseArray<View> mDetailViews = new SparseArray<>(); - private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger(); - - private ViewGroup mDetailContent; - private FalsingManager mFalsingManager; - protected TextView mDetailSettingsButton; - protected TextView mDetailDoneButton; - @VisibleForTesting - QSDetailClipper mClipper; - @Nullable - private DetailAdapter mDetailAdapter; - private QSPanelController mQsPanelController; - - protected View mQsDetailHeader; - protected TextView mQsDetailHeaderTitle; - private ViewStub mQsDetailHeaderSwitchStub; - @Nullable - private Switch mQsDetailHeaderSwitch; - protected ImageView mQsDetailHeaderProgress; - - @Nullable - protected QSTileHost mHost; - - private boolean mScanState; - private boolean mClosingDetail; - private boolean mFullyExpanded; - private QuickStatusBarHeader mHeader; - private boolean mTriggeredExpand; - private boolean mShouldAnimate; - private int mOpenX; - private int mOpenY; - private boolean mAnimatingOpen; - private boolean mSwitchState; - private QSFooter mFooter; - - @Nullable - private QSContainerController mQsContainerController; - - public QSDetail(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - FontSizeUtils.updateFontSize(mDetailDoneButton, R.dimen.qs_detail_button_text_size); - FontSizeUtils.updateFontSize(mDetailSettingsButton, R.dimen.qs_detail_button_text_size); - - for (int i = 0; i < mDetailViews.size(); i++) { - mDetailViews.valueAt(i).dispatchConfigurationChanged(newConfig); - } - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mDetailContent = findViewById(android.R.id.content); - mDetailSettingsButton = findViewById(android.R.id.button2); - mDetailDoneButton = findViewById(android.R.id.button1); - - mQsDetailHeader = findViewById(R.id.qs_detail_header); - mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title); - mQsDetailHeaderSwitchStub = mQsDetailHeader.findViewById(R.id.toggle_stub); - mQsDetailHeaderProgress = findViewById(R.id.qs_detail_header_progress); - - updateDetailText(); - - mClipper = new QSDetailClipper(this); - } - - public void setContainerController(QSContainerController controller) { - mQsContainerController = controller; - } - - /** */ - public void setQsPanel(QSPanelController panelController, QuickStatusBarHeader header, - QSFooter footer, FalsingManager falsingManager) { - mQsPanelController = panelController; - mHeader = header; - mFooter = footer; - mHeader.setCallback(mQsPanelCallback); - mQsPanelController.setCallback(mQsPanelCallback); - mFalsingManager = falsingManager; - } - - public void setHost(QSTileHost host) { - mHost = host; - } - public boolean isShowingDetail() { - return mDetailAdapter != null; - } - - public void setFullyExpanded(boolean fullyExpanded) { - mFullyExpanded = fullyExpanded; - } - - public void setExpanded(boolean qsExpanded) { - if (!qsExpanded) { - mTriggeredExpand = false; - } - } - - private void updateDetailText() { - int resId = mDetailAdapter != null ? mDetailAdapter.getDoneText() : Resources.ID_NULL; - mDetailDoneButton.setText( - (resId != Resources.ID_NULL) ? resId : R.string.quick_settings_done); - resId = mDetailAdapter != null ? mDetailAdapter.getSettingsText() : Resources.ID_NULL; - mDetailSettingsButton.setText( - (resId != Resources.ID_NULL) ? resId : R.string.quick_settings_more_settings); - } - - public void updateResources() { - updateDetailText(); - MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams(); - lp.topMargin = SystemBarUtils.getQuickQsOffsetHeight(mContext); - setLayoutParams(lp); - } - - @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - int bottomNavBar = insets.getInsets(WindowInsets.Type.navigationBars()).bottom; - MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams(); - lp.bottomMargin = bottomNavBar; - setLayoutParams(lp); - return super.onApplyWindowInsets(insets); - } - - public boolean isClosingDetail() { - return mClosingDetail; - } - - public interface Callback { - /** Handle an event of showing detail. */ - void onShowingDetail(@Nullable DetailAdapter detail, int x, int y); - void onToggleStateChanged(boolean state); - void onScanStateChanged(boolean state); - } - - /** Handle an event of showing detail. */ - public void handleShowingDetail(final @Nullable DetailAdapter adapter, int x, int y, - boolean toggleQs) { - final boolean showingDetail = adapter != null; - final boolean wasShowingDetail = mDetailAdapter != null; - setClickable(showingDetail); - if (showingDetail) { - setupDetailHeader(adapter); - if (toggleQs && !mFullyExpanded) { - mTriggeredExpand = true; - Dependency.get(CommandQueue.class).animateExpandSettingsPanel(null); - } else { - mTriggeredExpand = false; - } - mShouldAnimate = adapter.shouldAnimate(); - mOpenX = x; - mOpenY = y; - } else { - // Ensure we collapse into the same point we opened from. - x = mOpenX; - y = mOpenY; - if (toggleQs && mTriggeredExpand) { - Dependency.get(CommandQueue.class).animateCollapsePanels(); - mTriggeredExpand = false; - } - // Always animate on close, even if the last opened detail adapter had shouldAnimate() - // return false. This is necessary to avoid a race condition which could leave the - // keyguard in a bad state where QS remains visible underneath the notifications, clock, - // and status area. - mShouldAnimate = true; - } - - boolean visibleDiff = wasShowingDetail != showingDetail; - if (!visibleDiff && !wasShowingDetail) return; // already in right state - AnimatorListener listener; - if (showingDetail) { - int viewCacheIndex = adapter.getMetricsCategory(); - View detailView = adapter.createDetailView(mContext, mDetailViews.get(viewCacheIndex), - mDetailContent); - if (detailView == null) throw new IllegalStateException("Must return detail view"); - - setupDetailFooter(adapter); - - mDetailContent.removeAllViews(); - mDetailContent.addView(detailView); - mDetailViews.put(viewCacheIndex, detailView); - Dependency.get(MetricsLogger.class).visible(adapter.getMetricsCategory()); - mUiEventLogger.log(adapter.openDetailEvent()); - announceForAccessibility(mContext.getString( - R.string.accessibility_quick_settings_detail, - adapter.getTitle())); - mDetailAdapter = adapter; - listener = mHideGridContentWhenDone; - setVisibility(View.VISIBLE); - updateDetailText(); - } else { - if (wasShowingDetail) { - Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory()); - mUiEventLogger.log(mDetailAdapter.closeDetailEvent()); - } - mClosingDetail = true; - mDetailAdapter = null; - listener = mTeardownDetailWhenDone; - // Only update visibility if already expanded. Otherwise, a race condition can cause the - // keyguard to enter a bad state where the QS tiles are displayed underneath the - // notifications, clock, and status area. - if (mQsPanelController.isExpanded()) { - mHeader.setVisibility(View.VISIBLE); - mFooter.setVisibility(View.VISIBLE); - mQsPanelController.setGridContentVisibility(true); - mQsPanelCallback.onScanStateChanged(false); - } - } - sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - animateDetailVisibleDiff(x, y, visibleDiff, listener); - if (mQsContainerController != null) { - mQsContainerController.setDetailShowing(showingDetail); - } - } - - protected void animateDetailVisibleDiff(int x, int y, boolean visibleDiff, AnimatorListener listener) { - if (visibleDiff) { - mAnimatingOpen = mDetailAdapter != null; - if (mFullyExpanded || mDetailAdapter != null) { - setAlpha(1); - mClipper.updateCircularClip(mShouldAnimate, x, y, mDetailAdapter != null, listener); - } else { - animate().alpha(0) - .setDuration(mShouldAnimate ? FADE_DURATION : 0) - .setListener(listener) - .start(); - } - } - } - - protected void setupDetailFooter(DetailAdapter adapter) { - final Intent settingsIntent = adapter.getSettingsIntent(); - mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE); - mDetailSettingsButton.setOnClickListener(v -> { - if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - return; - } - Dependency.get(MetricsLogger.class).action(ACTION_QS_MORE_SETTINGS, - adapter.getMetricsCategory()); - mUiEventLogger.log(adapter.moreSettingsEvent()); - Dependency.get(ActivityStarter.class) - .postStartActivityDismissingKeyguard(settingsIntent, 0); - }); - mDetailDoneButton.setOnClickListener(v -> { - if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - return; - } - announceForAccessibility( - mContext.getString(R.string.accessibility_desc_quick_settings)); - if (!adapter.onDoneButtonClicked()) { - mQsPanelController.closeDetail(); - } - }); - } - - protected void setupDetailHeader(final DetailAdapter adapter) { - mQsDetailHeaderTitle.setText(adapter.getTitle()); - final Boolean toggleState = adapter.getToggleState(); - if (toggleState == null) { - if (mQsDetailHeaderSwitch != null) mQsDetailHeaderSwitch.setVisibility(INVISIBLE); - mQsDetailHeader.setClickable(false); - } else { - if (mQsDetailHeaderSwitch == null) { - mQsDetailHeaderSwitch = (Switch) mQsDetailHeaderSwitchStub.inflate(); - } - mQsDetailHeaderSwitch.setVisibility(VISIBLE); - handleToggleStateChanged(toggleState, adapter.getToggleEnabled()); - mQsDetailHeader.setClickable(true); - mQsDetailHeader.setOnClickListener(v -> { - if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - return; - } - boolean checked = !mQsDetailHeaderSwitch.isChecked(); - mQsDetailHeaderSwitch.setChecked(checked); - adapter.setToggleState(checked); - }); - } - } - - private void handleToggleStateChanged(boolean state, boolean toggleEnabled) { - mSwitchState = state; - if (mAnimatingOpen) { - return; - } - if (mQsDetailHeaderSwitch != null) mQsDetailHeaderSwitch.setChecked(state); - mQsDetailHeader.setEnabled(toggleEnabled); - if (mQsDetailHeaderSwitch != null) mQsDetailHeaderSwitch.setEnabled(toggleEnabled); - } - - private void handleScanStateChanged(boolean state) { - if (mScanState == state) return; - mScanState = state; - final Animatable anim = (Animatable) mQsDetailHeaderProgress.getDrawable(); - if (state) { - mQsDetailHeaderProgress.animate().cancel(); - mQsDetailHeaderProgress.animate() - .alpha(1) - .withEndAction(anim::start) - .start(); - } else { - mQsDetailHeaderProgress.animate().cancel(); - mQsDetailHeaderProgress.animate() - .alpha(0f) - .withEndAction(anim::stop) - .start(); - } - } - - private void checkPendingAnimations() { - handleToggleStateChanged(mSwitchState, - mDetailAdapter != null && mDetailAdapter.getToggleEnabled()); - } - - protected Callback mQsPanelCallback = new Callback() { - @Override - public void onToggleStateChanged(final boolean state) { - post(new Runnable() { - @Override - public void run() { - handleToggleStateChanged(state, - mDetailAdapter != null && mDetailAdapter.getToggleEnabled()); - } - }); - } - - @Override - public void onShowingDetail( - final @Nullable DetailAdapter detail, final int x, final int y) { - post(new Runnable() { - @Override - public void run() { - if (isAttachedToWindow()) { - handleShowingDetail(detail, x, y, false /* toggleQs */); - } - } - }); - } - - @Override - public void onScanStateChanged(final boolean state) { - post(new Runnable() { - @Override - public void run() { - handleScanStateChanged(state); - } - }); - } - }; - - private final AnimatorListenerAdapter mHideGridContentWhenDone = new AnimatorListenerAdapter() { - public void onAnimationCancel(Animator animation) { - // If we have been cancelled, remove the listener so that onAnimationEnd doesn't get - // called, this will avoid accidentally turning off the grid when we don't want to. - animation.removeListener(this); - mAnimatingOpen = false; - checkPendingAnimations(); - }; - - @Override - public void onAnimationEnd(Animator animation) { - // Only hide content if still in detail state. - if (mDetailAdapter != null) { - mQsPanelController.setGridContentVisibility(false); - mHeader.setVisibility(View.INVISIBLE); - mFooter.setVisibility(View.INVISIBLE); - } - mAnimatingOpen = false; - checkPendingAnimations(); - } - }; - - private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - mDetailContent.removeAllViews(); - setVisibility(View.INVISIBLE); - mClosingDetail = false; - }; - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java index 43136d32c68f..b02efba93161 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java @@ -25,7 +25,7 @@ import android.view.ViewAnimationUtils; import androidx.annotation.Nullable; -/** Helper for quick settings detail panel clip animations. **/ +/** Helper for quick settings detail panel clip animations. Currently used by the customizer **/ public class QSDetailClipper { private final View mDetail; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java deleted file mode 100644 index afd4f0f7d1e2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs; - -import androidx.annotation.Nullable; - -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.plugins.qs.DetailAdapter; - -import javax.inject.Inject; - -/** - * Proxy class for talking with the QSPanel and showing custom content within it. - */ -@SysUISingleton -public class QSDetailDisplayer { - @Nullable - private QSPanelController mQsPanelController; - - @Inject - public QSDetailDisplayer() { - } - - public void setQsPanelController(@Nullable QSPanelController qsPanelController) { - mQsPanelController = qsPanelController; - } - - /** Show the supplied DetailAdapter in the Quick Settings. */ - public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) { - if (mQsPanelController != null) { - mQsPanelController.showDetailAdapter(detailAdapter, x, y); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java deleted file mode 100644 index eb3247b53823..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs; - -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.Nullable; - -import com.android.systemui.FontSizeUtils; -import com.android.systemui.R; -import com.android.systemui.plugins.qs.QSTile; - -/** - * Quick settings common detail view with line items. - */ -public class QSDetailItems extends FrameLayout { - private static final String TAG = "QSDetailItems"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - private final int mQsDetailIconOverlaySize; - private final Context mContext; - private final H mHandler = new H(); - private final Adapter mAdapter = new Adapter(); - - private String mTag; - @Nullable - private Callback mCallback; - private boolean mItemsVisible = true; - private AutoSizingList mItemList; - private View mEmpty; - private TextView mEmptyText; - private ImageView mEmptyIcon; - - private Item[] mItems; - - public QSDetailItems(Context context, AttributeSet attrs) { - super(context, attrs); - mContext = context; - mTag = TAG; - mQsDetailIconOverlaySize = (int) getResources().getDimension( - R.dimen.qs_detail_icon_overlay_size); - } - - public static QSDetailItems convertOrInflate(Context context, View convert, ViewGroup parent) { - if (convert instanceof QSDetailItems) { - return (QSDetailItems) convert; - } - return (QSDetailItems) LayoutInflater.from(context).inflate(R.layout.qs_detail_items, - parent, false); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mItemList = findViewById(android.R.id.list); - mItemList.setVisibility(GONE); - mItemList.setAdapter(mAdapter); - mEmpty = findViewById(android.R.id.empty); - mEmpty.setVisibility(GONE); - mEmptyText = mEmpty.findViewById(android.R.id.title); - mEmptyIcon = mEmpty.findViewById(android.R.id.icon); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - FontSizeUtils.updateFontSize(mEmptyText, R.dimen.qs_detail_empty_text_size); - int count = mItemList.getChildCount(); - for (int i = 0; i < count; i++) { - View item = mItemList.getChildAt(i); - FontSizeUtils.updateFontSize(item, android.R.id.title, - R.dimen.qs_detail_item_primary_text_size); - FontSizeUtils.updateFontSize(item, android.R.id.summary, - R.dimen.qs_detail_item_secondary_text_size); - } - } - - public void setTagSuffix(String suffix) { - mTag = TAG + "." + suffix; - } - - public void setEmptyState(int icon, int text) { - mEmptyIcon.post(() -> { - mEmptyIcon.setImageResource(icon); - mEmptyText.setText(text); - }); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (DEBUG) Log.d(mTag, "onAttachedToWindow"); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (DEBUG) Log.d(mTag, "onDetachedFromWindow"); - mCallback = null; - } - - public void setCallback(Callback callback) { - mHandler.removeMessages(H.SET_CALLBACK); - mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget(); - } - - /** Set items. */ - public void setItems(@Nullable Item[] items) { - mHandler.removeMessages(H.SET_ITEMS); - mHandler.obtainMessage(H.SET_ITEMS, items).sendToTarget(); - } - - public void setItemsVisible(boolean visible) { - mHandler.removeMessages(H.SET_ITEMS_VISIBLE); - mHandler.obtainMessage(H.SET_ITEMS_VISIBLE, visible ? 1 : 0, 0).sendToTarget(); - } - - private void handleSetCallback(Callback callback) { - mCallback = callback; - } - - private void handleSetItems(Item[] items) { - final int itemCount = items != null ? items.length : 0; - mEmpty.setVisibility(itemCount == 0 ? VISIBLE : GONE); - mItemList.setVisibility(itemCount == 0 ? GONE : VISIBLE); - mItems = items; - mAdapter.notifyDataSetChanged(); - } - - private void handleSetItemsVisible(boolean visible) { - if (mItemsVisible == visible) return; - mItemsVisible = visible; - for (int i = 0; i < mItemList.getChildCount(); i++) { - mItemList.getChildAt(i).setVisibility(mItemsVisible ? VISIBLE : INVISIBLE); - } - } - - private class Adapter extends BaseAdapter { - - @Override - public int getCount() { - return mItems != null ? mItems.length : 0; - } - - @Override - public Object getItem(int position) { - return mItems[position]; - } - - @Override - public long getItemId(int position) { - return 0; - } - - @Override - public View getView(int position, View view, ViewGroup parent) { - final Item item = mItems[position]; - if (view == null) { - view = LayoutInflater.from(mContext).inflate(R.layout.qs_detail_item, parent, - false); - } - view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE); - final ImageView iv = (ImageView) view.findViewById(android.R.id.icon); - if (item.icon != null) { - iv.setImageDrawable(item.icon.getDrawable(iv.getContext())); - } else { - iv.setImageResource(item.iconResId); - } - iv.getOverlay().clear(); - if (item.overlay != null) { - item.overlay.setBounds(0, 0, mQsDetailIconOverlaySize, mQsDetailIconOverlaySize); - iv.getOverlay().add(item.overlay); - } - final TextView title = (TextView) view.findViewById(android.R.id.title); - title.setText(item.line1); - final TextView summary = (TextView) view.findViewById(android.R.id.summary); - final boolean twoLines = !TextUtils.isEmpty(item.line2); - title.setMaxLines(twoLines ? 1 : 2); - summary.setVisibility(twoLines ? VISIBLE : GONE); - summary.setText(twoLines ? item.line2 : null); - view.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (mCallback != null) { - mCallback.onDetailItemClick(item); - } - } - }); - - final ImageView icon2 = (ImageView) view.findViewById(android.R.id.icon2); - if (item.canDisconnect) { - icon2.setImageResource(R.drawable.ic_qs_cancel); - icon2.setVisibility(VISIBLE); - icon2.setClickable(true); - icon2.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (mCallback != null) { - mCallback.onDetailItemDisconnect(item); - } - } - }); - } else if (item.icon2 != -1) { - icon2.setVisibility(VISIBLE); - icon2.setImageResource(item.icon2); - icon2.setClickable(false); - } else { - icon2.setVisibility(GONE); - } - - return view; - } - }; - - private class H extends Handler { - private static final int SET_ITEMS = 1; - private static final int SET_CALLBACK = 2; - private static final int SET_ITEMS_VISIBLE = 3; - - public H() { - super(Looper.getMainLooper()); - } - - @Override - public void handleMessage(Message msg) { - if (msg.what == SET_ITEMS) { - handleSetItems((Item[]) msg.obj); - } else if (msg.what == SET_CALLBACK) { - handleSetCallback((QSDetailItems.Callback) msg.obj); - } else if (msg.what == SET_ITEMS_VISIBLE) { - handleSetItemsVisible(msg.arg1 != 0); - } - } - } - - public static class Item { - public Item(int iconResId, CharSequence line1, Object tag) { - this.iconResId = iconResId; - this.line1 = line1; - this.tag = tag; - } - - public int iconResId; - @Nullable - public QSTile.Icon icon; - @Nullable - public Drawable overlay; - public CharSequence line1; - @Nullable - public CharSequence line2; - public Object tag; - public boolean canDisconnect; - public int icon2 = -1; - } - - public interface Callback { - void onDetailItemClick(Item item); - void onDetailItemDisconnect(Item item); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt index 26adf464195c..24bb16a11fe7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt @@ -124,13 +124,13 @@ enum class QSUserSwitcherEvent(private val _id: Int) : UiEventLogger.UiEventEnum @UiEvent(doc = "The current user has been switched in the detail panel") QS_USER_SWITCH(424), - @UiEvent(doc = "User switcher QS detail panel open") + @UiEvent(doc = "User switcher QS dialog open") QS_USER_DETAIL_OPEN(425), - @UiEvent(doc = "User switcher QS detail panel closed") + @UiEvent(doc = "User switcher QS dialog closed") QS_USER_DETAIL_CLOSE(426), - @UiEvent(doc = "User switcher QS detail panel more settings pressed") + @UiEvent(doc = "User switcher QS dialog more settings pressed") QS_USER_MORE_SETTINGS(427), @UiEvent(doc = "The user has added a guest in the detail panel") diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 50952bd0ec28..17f85eedbbc1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -33,7 +33,6 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewTreeObserver; -import android.widget.FrameLayout.LayoutParams; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -85,7 +84,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private QSSquishinessController mQSSquishinessController; protected QuickStatusBarHeader mHeader; protected NonInterceptingScrollView mQSPanelScrollView; - private QSDetail mQSDetail; private boolean mListening; private QSContainerImpl mContainer; private int mLayoutDirection; @@ -96,8 +94,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private boolean mQsDisabled; private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; - private final CommandQueue mCommandQueue; - private final QSDetailDisplayer mQsDetailDisplayer; private final MediaHost mQsMediaHost; private final MediaHost mQqsMediaHost; private final QSFragmentComponent.Factory mQsComponentFactory; @@ -126,7 +122,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca * otherwise. */ private boolean mInSplitShade; - private boolean mPulseExpanding; /** * Are we currently transitioning from lockscreen to the full shade? @@ -150,15 +145,13 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, QSTileHost qsTileHost, StatusBarStateController statusBarStateController, CommandQueue commandQueue, - QSDetailDisplayer qsDetailDisplayer, @Named(QS_PANEL) MediaHost qsMediaHost, + @Named(QS_PANEL) MediaHost qsMediaHost, @Named(QUICK_QS_PANEL) MediaHost qqsMediaHost, KeyguardBypassController keyguardBypassController, QSFragmentComponent.Factory qsComponentFactory, QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger, FalsingManager falsingManager, DumpManager dumpManager) { mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; - mCommandQueue = commandQueue; - mQsDetailDisplayer = qsDetailDisplayer; mQsMediaHost = qsMediaHost; mQqsMediaHost = qqsMediaHost; mQsComponentFactory = qsComponentFactory; @@ -209,19 +202,15 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mScrollListener.onQsPanelScrollChanged(scrollY); } }); - mQSDetail = view.findViewById(R.id.qs_detail); mHeader = view.findViewById(R.id.header); mQSPanelController.setHeaderContainer(view.findViewById(R.id.header_text_container)); mFooter = qsFragmentComponent.getQSFooter(); - mQsDetailDisplayer.setQsPanelController(mQSPanelController); - mQSContainerImplController = qsFragmentComponent.getQSContainerImplController(); mQSContainerImplController.init(); mContainer = mQSContainerImplController.getView(); mDumpManager.registerDumpable(mContainer.getClass().getName(), mContainer); - mQSDetail.setQsPanel(mQSPanelController, mHeader, mFooter, mFalsingManager); mQSAnimator = qsFragmentComponent.getQSAnimator(); mQSSquishinessController = qsFragmentComponent.getQSSquishinessController(); @@ -237,7 +226,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQSPanelController.getTileLayout().restoreInstanceState(savedInstanceState); } } - setHost(mHost); mStatusBarStateController.addCallback(this); onStateChanged(mStatusBarStateController.getState()); view.addOnLayoutChangeListener( @@ -271,7 +259,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca setListening(false); } mQSCustomizerController.setQs(null); - mQsDetailDisplayer.setQsPanelController(null); mScrollListener = null; mDumpManager.unregisterDumpable(mContainer.getClass().getName()); } @@ -352,7 +339,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca @Override public void setContainerController(QSContainerController controller) { mQSCustomizerController.setContainerController(controller); - mQSDetail.setContainerController(controller); } @Override @@ -360,10 +346,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca return mQSCustomizerController.isCustomizing(); } - public void setHost(QSTileHost qsh) { - mQSDetail.setHost(qsh); - } - @Override public void disable(int displayId, int state1, int state2, boolean animate) { if (displayId != getContext().getDisplayId()) { @@ -392,7 +374,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca final boolean expandVisually = expanded || mStackScrollerOverscrolling || mHeaderAnimating; mQSPanelController.setExpanded(expanded); - mQSDetail.setExpanded(expanded); boolean keyguardShowing = isKeyguardState(); mHeader.setVisibility((expanded || !keyguardShowing || mHeaderAnimating || mShowCollapsedOnKeyguard) @@ -439,12 +420,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void setBrightnessMirrorController( BrightnessMirrorController brightnessMirrorController) { mQSPanelController.setBrightnessMirror(brightnessMirrorController); - mQuickQSPanelController.setBrightnessMirror(brightnessMirrorController); } @Override public boolean isShowingDetail() { - return mQSCustomizerController.isCustomizing() || mQSDetail.isShowingDetail(); + return mQSCustomizerController.isCustomizing(); } @Override @@ -573,7 +553,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca if (fullyCollapsed) { mQSPanelScrollView.setScrollY(0); } - mQSDetail.setFullyExpanded(fullyExpanded); if (!fullyExpanded) { // Set bounds on the QS panel so it doesn't run over the header when animating. @@ -732,14 +711,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca if (mQSCustomizerController.isCustomizing()) { return getView().getHeight(); } - if (mQSDetail.isClosingDetail()) { - LayoutParams layoutParams = (LayoutParams) mQSPanelScrollView.getLayoutParams(); - int panelHeight = layoutParams.topMargin + layoutParams.bottomMargin + - + mQSPanelScrollView.getMeasuredHeight(); - return panelHeight + getView().getPaddingBottom(); - } else { - return getView().getMeasuredHeight(); - } + return getView().getMeasuredHeight(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 7c04cd4dcb05..0c854df4a837 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -41,10 +41,8 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.internal.widget.RemeasuringLinearLayout; import com.android.systemui.R; -import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.settings.brightness.BrightnessSliderController; -import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -80,7 +78,6 @@ public class QSPanel extends LinearLayout implements Tunable { protected boolean mExpanded; protected boolean mListening; - private QSDetail.Callback mCallback; protected QSTileHost mHost; private final List<OnConfigurationChangedListener> mOnConfigurationChangedListeners = new ArrayList<>(); @@ -100,9 +97,6 @@ public class QSPanel extends LinearLayout implements Tunable { private int mContentMarginEnd; private boolean mUsingHorizontalLayout; - private Record mDetailRecord; - - private BrightnessMirrorController mBrightnessMirrorController; private LinearLayout mHorizontalLinearLayout; protected LinearLayout mHorizontalContentContainer; @@ -315,24 +309,12 @@ public class QSPanel extends LinearLayout implements Tunable { view.setVisibility(TunerService.parseIntegerSwitch(newValue, true) ? VISIBLE : GONE); } - /** */ - public void openDetails(QSTile tile) { - // If there's no tile with that name (as defined in QSFactoryImpl or other QSFactory), - // QSFactory will not be able to create a tile and getTile will return null - if (tile != null) { - showDetailAdapter(true, tile.getDetailAdapter(), new int[]{getWidth() / 2, 0}); - } - } @Nullable View getBrightnessView() { return mBrightnessView; } - public void setCallback(QSDetail.Callback callback) { - mCallback = callback; - } - /** * Links the footer's page indicator, which is used in landscape orientation to save space. * @@ -519,25 +501,6 @@ public class QSPanel extends LinearLayout implements Tunable { mListening = listening; } - public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) { - int xInWindow = locationInWindow[0]; - int yInWindow = locationInWindow[1]; - ((View) getParent()).getLocationInWindow(locationInWindow); - - Record r = new Record(); - r.detailAdapter = adapter; - r.x = xInWindow - locationInWindow[0]; - r.y = yInWindow - locationInWindow[1]; - - locationInWindow[0] = xInWindow; - locationInWindow[1] = yInWindow; - - showDetail(show, r); - } - - protected void showDetail(boolean show, Record r) { - mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget(); - } protected void drawTile(QSPanelControllerBase.TileRecord r, QSTile.State state) { r.tileView.onStateChanged(state); @@ -565,30 +528,6 @@ public class QSPanel extends LinearLayout implements Tunable { public void onStateChanged(QSTile.State state) { drawTile(tileRecord, state); } - - @Override - public void onShowDetail(boolean show) { - // Both the collapsed and full QS panels get this callback, this check determines - // which one should handle showing the detail. - if (shouldShowDetail()) { - QSPanel.this.showDetail(show, tileRecord); - } - } - - @Override - public void onToggleStateChanged(boolean state) { - if (mDetailRecord == tileRecord) { - fireToggleStateChanged(state); - } - } - - @Override - public void onScanStateChanged(boolean state) { - tileRecord.scanState = state; - if (mDetailRecord == tileRecord) { - fireScanStateChanged(tileRecord.scanState); - } - } }; tileRecord.tile.addCallback(callback); @@ -605,72 +544,10 @@ public class QSPanel extends LinearLayout implements Tunable { mTileLayout.removeTile(tileRecord); } - void closeDetail() { - showDetail(false, mDetailRecord); - } - public int getGridHeight() { return getMeasuredHeight(); } - protected void handleShowDetail(Record r, boolean show) { - if (r instanceof QSPanelControllerBase.TileRecord) { - handleShowDetailTile((QSPanelControllerBase.TileRecord) r, show); - } else { - int x = 0; - int y = 0; - if (r != null) { - x = r.x; - y = r.y; - } - handleShowDetailImpl(r, show, x, y); - } - } - - private void handleShowDetailTile(QSPanelControllerBase.TileRecord r, boolean show) { - if ((mDetailRecord != null) == show && mDetailRecord == r) return; - - if (show) { - r.detailAdapter = r.tile.getDetailAdapter(); - if (r.detailAdapter == null) return; - } - r.tile.setDetailListening(show); - int x = r.tileView.getLeft() + r.tileView.getWidth() / 2; - int y = r.tileView.getDetailY() + mTileLayout.getOffsetTop(r) + getTop(); - handleShowDetailImpl(r, show, x, y); - } - - private void handleShowDetailImpl(Record r, boolean show, int x, int y) { - setDetailRecord(show ? r : null); - fireShowingDetail(show ? r.detailAdapter : null, x, y); - } - - protected void setDetailRecord(Record r) { - if (r == mDetailRecord) return; - mDetailRecord = r; - final boolean scanState = mDetailRecord instanceof QSPanelControllerBase.TileRecord - && ((QSPanelControllerBase.TileRecord) mDetailRecord).scanState; - fireScanStateChanged(scanState); - } - - private void fireShowingDetail(DetailAdapter detail, int x, int y) { - if (mCallback != null) { - mCallback.onShowingDetail(detail, x, y); - } - } - - private void fireToggleStateChanged(boolean state) { - if (mCallback != null) { - mCallback.onToggleStateChanged(state); - } - } - - private void fireScanStateChanged(boolean state) { - if (mCallback != null) { - mCallback.onScanStateChanged(state); - } - } - QSTileLayout getTileLayout() { return mTileLayout; } @@ -774,26 +651,16 @@ public class QSPanel extends LinearLayout implements Tunable { } private class H extends Handler { - private static final int SHOW_DETAIL = 1; - private static final int SET_TILE_VISIBILITY = 2; - private static final int ANNOUNCE_FOR_ACCESSIBILITY = 3; + private static final int ANNOUNCE_FOR_ACCESSIBILITY = 1; @Override public void handleMessage(Message msg) { - if (msg.what == SHOW_DETAIL) { - handleShowDetail((Record) msg.obj, msg.arg1 != 0); - } else if (msg.what == ANNOUNCE_FOR_ACCESSIBILITY) { + if (msg.what == ANNOUNCE_FOR_ACCESSIBILITY) { announceForAccessibility((CharSequence) msg.obj); } } } - protected static class Record { - DetailAdapter detailAdapter; - int x; - int y; - } - public interface QSTileLayout { /** */ default void saveInstanceState(Bundle outState) {} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 418c4ae023ca..6a7f3c3bf339 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -36,15 +36,12 @@ import com.android.systemui.flags.Flags; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.media.MediaHost; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.DetailAdapter; -import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.brightness.BrightnessController; import com.android.systemui.settings.brightness.BrightnessMirrorHandler; import com.android.systemui.settings.brightness.BrightnessSliderController; -import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.Utils; @@ -57,7 +54,6 @@ import javax.inject.Named; */ @QSScope public class QSPanelController extends QSPanelControllerBase<QSPanel> { - public static final String QS_REMOVE_LABELS = "sysui_remove_labels"; private final QSFgsManagerFooter mQSFgsManagerFooter; private final QSSecurityFooter mQsSecurityFooter; @@ -65,7 +61,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { private final QSCustomizerController mQsCustomizerController; private final QSTileRevealController.Factory mQsTileRevealControllerFactory; private final FalsingManager mFalsingManager; - private final CommandQueue mCommandQueue; private final BrightnessController mBrightnessController; private final BrightnessSliderController mBrightnessSliderController; private final BrightnessMirrorHandler mBrightnessMirrorHandler; @@ -107,7 +102,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory, BrightnessSliderController.Factory brightnessSliderFactory, - FalsingManager falsingManager, CommandQueue commandQueue, FeatureFlags featureFlags) { + FalsingManager falsingManager, FeatureFlags featureFlags) { super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager); mQSFgsManagerFooter = qsFgsManagerFooter; @@ -116,7 +111,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mQsCustomizerController = qsCustomizerController; mQsTileRevealControllerFactory = qsTileRevealControllerFactory; mFalsingManager = falsingManager; - mCommandQueue = commandQueue; mBrightnessSliderController = brightnessSliderFactory.create(getContext(), mView); mView.setBrightnessView(mBrightnessSliderController.getRootView()); @@ -221,15 +215,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { return mHost; } - - /** Open the details for a specific tile.. */ - public void openDetails(String subPanel) { - QSTile tile = getTile(subPanel); - if (tile != null) { - mView.openDetails(tile); - } - } - /** Show the device monitoring dialog. */ public void showDeviceMonitoringDialog() { mQsSecurityFooter.showDeviceMonitoringDialog(); @@ -261,11 +246,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { } /** */ - public void setCallback(QSDetail.Callback qsPanelCallback) { - mView.setCallback(qsPanelCallback); - } - - /** */ public void setGridContentVisibility(boolean visible) { int newVis = visible ? View.VISIBLE : View.INVISIBLE; setVisibility(newVis); @@ -294,19 +274,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { } /** */ - public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) { - // TODO(b/199296365) - // Workaround for opening detail from QQS, when there might not be enough space to - // display e.g. in case of multiuser detail from split shade. Currently showing detail works - // only for QS (mView below) and that's why expanding panel (thus showing QS instead of QQS) - // makes it displayed correctly. - if (!isExpanded()) { - mCommandQueue.animateExpandSettingsPanel(null); - } - mView.showDetailAdapter(true, detailAdapter, new int[]{x, y}); - } - - /** */ public void setFooterPageIndicator(PageIndicator pageIndicator) { mView.setFooterPageIndicator(pageIndicator); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index d4d6da8f5910..0bff72286008 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -298,21 +298,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr mQsCustomizerController.hide(); return; } - mView.closeDetail(); } - /** */ - public void openDetails(String subPanel) { - QSTile tile = getTile(subPanel); - // If there's no tile with that name (as defined in QSFactoryImpl or other QSFactory), - // QSFactory will not be able to create a tile and getTile will return null - if (tile != null) { - mView.showDetailAdapter( - true, tile.getDetailAdapter(), new int[]{mView.getWidth() / 2, 0}); - } - } - - void setListening(boolean listening) { mView.setListening(listening); @@ -429,7 +416,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr } /** */ - public static final class TileRecord extends QSPanel.Record { + public static final class TileRecord { public TileRecord(QSTile tile, com.android.systemui.plugins.qs.QSTileView tileView) { this.tile = tile; this.tileView = tileView; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt deleted file mode 100644 index 65889d792769..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs - -import androidx.annotation.VisibleForTesting -import com.android.systemui.settings.brightness.BrightnessController -import com.android.systemui.settings.brightness.BrightnessSliderController -import com.android.systemui.settings.brightness.MirroredBrightnessController -import com.android.systemui.statusbar.policy.BrightnessMirrorController -import javax.inject.Inject - -/** - * Controls brightness slider in QQS, which is visible only in split shade. It's responsible for - * showing/hiding it when appropriate and (un)registering listeners - */ -class QuickQSBrightnessController @VisibleForTesting constructor( - private val brightnessControllerFactory: () -> BrightnessController -) : MirroredBrightnessController { - - @Inject constructor( - brightnessControllerFactory: BrightnessController.Factory, - brightnessSliderControllerFactory: BrightnessSliderController.Factory, - quickQSPanel: QuickQSPanel - ) : this(brightnessControllerFactory = { - val slider = brightnessSliderControllerFactory.create(quickQSPanel.context, - quickQSPanel) - slider.init() - quickQSPanel.setBrightnessView(slider.rootView) - brightnessControllerFactory.create(slider) - }) - - private var isListening = false - private var brightnessController: BrightnessController? = null - private var mirrorController: BrightnessMirrorController? = null - - fun init(shouldUseSplitNotificationShade: Boolean) { - refreshVisibility(shouldUseSplitNotificationShade) - } - - /** - * Starts/Stops listening for brightness changing events. - * It's fine to call this function even if slider is not visible (which would be the case for - * all small screen devices), it will just do nothing in that case - */ - fun setListening(listening: Boolean) { - if (listening) { - // controller can be null when slider was never shown - if (!isListening && brightnessController != null) { - brightnessController?.registerCallbacks() - isListening = true - } - } else { - brightnessController?.unregisterCallbacks() - isListening = false - } - } - - fun checkRestrictionAndSetEnabled() { - brightnessController?.checkRestrictionAndSetEnabled() - } - - fun refreshVisibility(shouldUseSplitNotificationShade: Boolean) { - if (shouldUseSplitNotificationShade) { - showBrightnessSlider() - } else { - hideBrightnessSlider() - } - } - - override fun setMirror(controller: BrightnessMirrorController) { - mirrorController = controller - mirrorController?.let { brightnessController?.setMirror(it) } - } - - private fun hideBrightnessSlider() { - brightnessController?.hideSlider() - } - - private fun showBrightnessSlider() { - if (brightnessController == null) { - brightnessController = brightnessControllerFactory() - mirrorController?.also { brightnessController?.setMirror(it) } - brightnessController?.registerCallbacks() - isListening = true - } - brightnessController?.showSlider() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index a3af0e58c43d..b2e008b9d2bc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -33,8 +33,6 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.logging.QSLogger; -import com.android.systemui.settings.brightness.BrightnessMirrorHandler; -import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.util.leak.RotationUtils; import java.util.ArrayList; @@ -55,10 +53,6 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> } }; - // brightness is visible only in split shade - private final QuickQSBrightnessController mBrightnessController; - private final BrightnessMirrorHandler mBrightnessMirrorHandler; - private final MediaFlags mMediaFlags; private final boolean mUsingCollapsedLandscapeMedia; @@ -70,13 +64,10 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA) boolean usingCollapsedLandscapeMedia, MediaFlags mediaFlags, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, - DumpManager dumpManager, - QuickQSBrightnessController quickQSBrightnessController + DumpManager dumpManager ) { super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager); - mBrightnessController = quickQSBrightnessController; - mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController); mUsingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia; mMediaFlags = mediaFlags; } @@ -87,7 +78,6 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> updateMediaExpansion(); mMediaHost.setShowsOnlyActiveMedia(true); mMediaHost.init(MediaHierarchyManager.LOCATION_QQS); - mBrightnessController.init(mShouldUseSplitNotificationShade); } private void updateMediaExpansion() { @@ -111,20 +101,17 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> protected void onViewAttached() { super.onViewAttached(); mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener); - mBrightnessMirrorHandler.onQsPanelAttached(); } @Override protected void onViewDetached() { super.onViewDetached(); mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener); - mBrightnessMirrorHandler.onQsPanelDettached(); } @Override void setListening(boolean listening) { super.setListening(listening); - mBrightnessController.setListening(listening); } public boolean isListening() { @@ -137,14 +124,7 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> } @Override - public void refreshAllTiles() { - mBrightnessController.checkRestrictionAndSetEnabled(); - super.refreshAllTiles(); - } - - @Override protected void onConfigurationChanged() { - mBrightnessController.refreshVisibility(mShouldUseSplitNotificationShade); updateMediaExpansion(); } @@ -168,8 +148,4 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> public int getNumQuickTiles() { return mView.getNumQuickTiles(); } - - public void setBrightnessMirror(BrightnessMirrorController brightnessMirrorController) { - mBrightnessMirrorHandler.setController(brightnessMirrorController); - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index d3f8db38fd59..8c08873b9ab6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -39,7 +39,6 @@ import com.android.internal.policy.SystemBarUtils; import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterView; -import com.android.systemui.qs.QSDetail.Callback; import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager; import com.android.systemui.statusbar.phone.StatusIconContainer; @@ -548,10 +547,6 @@ public class QuickStatusBarHeader extends FrameLayout { post(() -> setClickable(!mExpanded)); } - public void setCallback(Callback qsPanelCallback) { - mHeaderQsPanel.setCallback(qsPanelCallback); - } - private void setContentMargins(View view, int marginStart, int marginEnd) { MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams(); lp.setMarginStart(marginStart); diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index b29687fe74c3..32a7916da70f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -205,15 +205,6 @@ public class TileQueryHelper { finished(); } } - - @Override - public void onShowDetail(boolean show) {} - - @Override - public void onToggleStateChanged(boolean state) {} - - @Override - public void onScanStateChanged(boolean state) {} } private void addPackageTiles(final QSTileHost host) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt index 73d6b971f785..237b66e79ee5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt @@ -21,6 +21,7 @@ import android.app.StatusBarManager import android.content.ComponentName import android.content.DialogInterface import android.graphics.drawable.Icon +import android.os.RemoteException import android.util.Log import androidx.annotation.VisibleForTesting import com.android.internal.statusbar.IAddTileResultCallback @@ -32,6 +33,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.R import com.android.systemui.statusbar.CommandQueue import java.io.PrintWriter +import java.util.concurrent.atomic.AtomicBoolean import java.util.function.Consumer import javax.inject.Inject @@ -67,7 +69,11 @@ class TileServiceRequestController constructor( callback: IAddTileResultCallback ) { requestTileAdd(componentName, appName, label, icon) { - callback.onTileRequest(it) + try { + callback.onTileRequest(it) + } catch (e: RemoteException) { + Log.e(TAG, "Couldn't respond to request", e) + } } } @@ -105,7 +111,7 @@ class TileServiceRequestController constructor( eventLogger.logTileAlreadyAdded(packageName, instanceId) return } - val dialogResponse = Consumer<Int> { response -> + val dialogResponse = SingleShotConsumer<Int> { response -> if (response == ADD_TILE) { addTile(componentName) } @@ -127,7 +133,7 @@ class TileServiceRequestController constructor( private fun createDialog( tileData: TileRequestDialog.TileData, - responseHandler: Consumer<Int> + responseHandler: SingleShotConsumer<Int> ): SystemUIDialog { val dialogClickListener = DialogInterface.OnClickListener { _, which -> if (which == Dialog.BUTTON_POSITIVE) { @@ -141,6 +147,10 @@ class TileServiceRequestController constructor( setShowForAllUsers(true) setCanceledOnTouchOutside(true) setOnCancelListener { responseHandler.accept(DISMISSED) } + // We want this in case the dialog is dismissed without it being cancelled (for example + // by going home or locking the device). We use a SingleShotConsumer so the response + // is only sent once, with the first value. + setOnDismissListener { responseHandler.accept(DISMISSED) } setPositiveButton(R.string.qs_tile_request_dialog_add, dialogClickListener) setNegativeButton(R.string.qs_tile_request_dialog_not_add, dialogClickListener) } @@ -169,6 +179,16 @@ class TileServiceRequestController constructor( } } + private class SingleShotConsumer<T>(private val consumer: Consumer<T>) : Consumer<T> { + private val dispatched = AtomicBoolean(false) + + override fun accept(t: T) { + if (dispatched.compareAndSet(false, true)) { + consumer.accept(t) + } + } + } + @SysUISingleton class Builder @Inject constructor( private val commandQueue: CommandQueue, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 86fc4de7825a..1488231275c3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -53,7 +53,6 @@ import com.android.systemui.qs.tiles.ReduceBrightColorsTile; import com.android.systemui.qs.tiles.RotationLockTile; import com.android.systemui.qs.tiles.ScreenRecordTile; import com.android.systemui.qs.tiles.UiModeNightTile; -import com.android.systemui.qs.tiles.UserTile; import com.android.systemui.qs.tiles.WifiTile; import com.android.systemui.qs.tiles.WorkModeTile; import com.android.systemui.util.leak.GarbageMonitor; @@ -82,7 +81,6 @@ public class QSFactoryImpl implements QSFactory { private final Provider<LocationTile> mLocationTileProvider; private final Provider<CastTile> mCastTileProvider; private final Provider<HotspotTile> mHotspotTileProvider; - private final Provider<UserTile> mUserTileProvider; private final Provider<BatterySaverTile> mBatterySaverTileProvider; private final Provider<DataSaverTile> mDataSaverTileProvider; private final Provider<NightDisplayTile> mNightDisplayTileProvider; @@ -119,7 +117,6 @@ public class QSFactoryImpl implements QSFactory { Provider<LocationTile> locationTileProvider, Provider<CastTile> castTileProvider, Provider<HotspotTile> hotspotTileProvider, - Provider<UserTile> userTileProvider, Provider<BatterySaverTile> batterySaverTileProvider, Provider<DataSaverTile> dataSaverTileProvider, Provider<NightDisplayTile> nightDisplayTileProvider, @@ -152,7 +149,6 @@ public class QSFactoryImpl implements QSFactory { mLocationTileProvider = locationTileProvider; mCastTileProvider = castTileProvider; mHotspotTileProvider = hotspotTileProvider; - mUserTileProvider = userTileProvider; mBatterySaverTileProvider = batterySaverTileProvider; mDataSaverTileProvider = dataSaverTileProvider; mNightDisplayTileProvider = nightDisplayTileProvider; @@ -212,8 +208,6 @@ public class QSFactoryImpl implements QSFactory { return mCastTileProvider.get(); case "hotspot": return mHotspotTileProvider.get(); - case "user": - return mUserTileProvider.get(); case "battery": return mBatterySaverTileProvider.get(); case "saver": diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 6d9d5b16fcab..131589f33fee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -60,7 +60,6 @@ import com.android.systemui.Dumpable; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.State; @@ -261,16 +260,6 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy return new QSIconViewImpl(context); } - /** Returns corresponding DetailAdapter. */ - @Nullable - public DetailAdapter getDetailAdapter() { - return null; // optional - } - - protected DetailAdapter createDetailAdapter() { - throw new UnsupportedOperationException(); - } - /** * Is a startup check whether this device currently supports this tile. * Should not be used to conditionally hide tiles. Only checked on tile @@ -337,10 +326,6 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy .addTaggedData(FIELD_QS_POSITION, mHost.indexOf(mTileSpec)); } - public void showDetail(boolean show) { - mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget(); - } - public void refreshState() { refreshState(null); } @@ -353,14 +338,6 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget(); } - public void fireToggleStateChanged(boolean state) { - mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget(); - } - - public void fireScanStateChanged(boolean state) { - mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget(); - } - public void destroy() { mHandler.sendEmptyMessage(H.DESTROY); } @@ -462,29 +439,6 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } } - private void handleShowDetail(boolean show) { - mShowingDetail = show; - for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).onShowDetail(show); - } - } - - protected boolean isShowingDetail() { - return mShowingDetail; - } - - private void handleToggleStateChanged(boolean state) { - for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).onToggleStateChanged(state); - } - } - - private void handleScanStateChanged(boolean state) { - for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).onScanStateChanged(state); - } - } - protected void handleUserSwitch(int newUserId) { handleRefreshState(null); } @@ -591,17 +545,14 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy private static final int SECONDARY_CLICK = 3; private static final int LONG_CLICK = 4; private static final int REFRESH_STATE = 5; - private static final int SHOW_DETAIL = 6; - private static final int USER_SWITCH = 7; - private static final int TOGGLE_STATE_CHANGED = 8; - private static final int SCAN_STATE_CHANGED = 9; - private static final int DESTROY = 10; - private static final int REMOVE_CALLBACKS = 11; - private static final int REMOVE_CALLBACK = 12; - private static final int SET_LISTENING = 13; + private static final int USER_SWITCH = 6; + private static final int DESTROY = 7; + private static final int REMOVE_CALLBACKS = 8; + private static final int REMOVE_CALLBACK = 9; + private static final int SET_LISTENING = 10; @VisibleForTesting - protected static final int STALE = 14; - private static final int INITIALIZE = 15; + protected static final int STALE = 11; + private static final int INITIALIZE = 12; @VisibleForTesting protected H(Looper looper) { @@ -639,18 +590,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } else if (msg.what == REFRESH_STATE) { name = "handleRefreshState"; handleRefreshState(msg.obj); - } else if (msg.what == SHOW_DETAIL) { - name = "handleShowDetail"; - handleShowDetail(msg.arg1 != 0); } else if (msg.what == USER_SWITCH) { name = "handleUserSwitch"; handleUserSwitch(msg.arg1); - } else if (msg.what == TOGGLE_STATE_CHANGED) { - name = "handleToggleStateChanged"; - handleToggleStateChanged(msg.arg1 != 0); - } else if (msg.what == SCAN_STATE_CHANGED) { - name = "handleScanStateChanged"; - handleScanStateChanged(msg.arg1 != 0); } else if (msg.what == DESTROY) { name = "handleDestroy"; handleDestroy(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 754f8e26ee87..c61c18a2a029 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -19,7 +19,6 @@ package com.android.systemui.qs.tiles; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; @@ -29,7 +28,6 @@ import android.provider.Settings; import android.service.quicksettings.Tile; import android.text.TextUtils; import android.view.View; -import android.view.ViewGroup; import android.widget.Switch; import androidx.annotation.Nullable; @@ -38,24 +36,18 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.settingslib.graph.BluetoothDeviceLayerDrawable; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.qs.QSDetailItems; -import com.android.systemui.qs.QSDetailItems.Item; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.BluetoothController; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; import javax.inject.Inject; @@ -65,7 +57,6 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { private static final Intent BLUETOOTH_SETTINGS = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS); private final BluetoothController mController; - private final BluetoothDetailAdapter mDetailAdapter; @Inject public BluetoothTile( @@ -82,16 +73,10 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = bluetoothController; - mDetailAdapter = (BluetoothDetailAdapter) createDetailAdapter(); mController.observe(getLifecycle(), mCallback); } @Override - public DetailAdapter getDetailAdapter() { - return mDetailAdapter; - } - - @Override public BooleanState newTileState() { return new BooleanState(); } @@ -117,7 +102,6 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { new Intent(Settings.ACTION_BLUETOOTH_SETTINGS), 0); return; } - showDetail(true); if (!mState.value) { mController.setBluetoothEnabled(true); } @@ -251,53 +235,14 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { @Override public void onBluetoothStateChange(boolean enabled) { refreshState(); - if (isShowingDetail()) { - mDetailAdapter.updateItems(); - fireToggleStateChanged(mDetailAdapter.getToggleState()); - } } @Override public void onBluetoothDevicesChanged() { refreshState(); - if (isShowingDetail()) { - mDetailAdapter.updateItems(); - } } }; - @Override - protected DetailAdapter createDetailAdapter() { - return new BluetoothDetailAdapter(); - } - - /** - * Bluetooth icon wrapper for Quick Settings with a battery indicator that reflects the - * connected device's battery level. This is used instead of - * {@link com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon} in order to use a context - * that reflects dark/light theme attributes. - */ - private class BluetoothBatteryTileIcon extends Icon { - private int mBatteryLevel; - private float mIconScale; - - BluetoothBatteryTileIcon(int batteryLevel, float iconScale) { - mBatteryLevel = batteryLevel; - mIconScale = iconScale; - } - - @Override - public Drawable getDrawable(Context context) { - // This method returns Pair<Drawable, String> while first value is the drawable - return BluetoothDeviceLayerDrawable.createLayerDrawable( - context, - R.drawable.ic_bluetooth_connected, - mBatteryLevel, - mIconScale); - } - } - - /** * Bluetooth icon wrapper (when connected with no battery indicator) for Quick Settings. This is * used instead of {@link com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon} in order to @@ -315,129 +260,4 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { return context.getDrawable(R.drawable.ic_bluetooth_connected); } } - - protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback { - // We probably won't ever have space in the UI for more than 20 devices, so don't - // get info for them. - private static final int MAX_DEVICES = 20; - @Nullable - private QSDetailItems mItems; - - @Override - public CharSequence getTitle() { - return mContext.getString(R.string.quick_settings_bluetooth_label); - } - - @Override - public Boolean getToggleState() { - return mState.value; - } - - @Override - public boolean getToggleEnabled() { - return mController.getBluetoothState() == BluetoothAdapter.STATE_OFF - || mController.getBluetoothState() == BluetoothAdapter.STATE_ON; - } - - @Override - public Intent getSettingsIntent() { - return BLUETOOTH_SETTINGS; - } - - @Override - public void setToggleState(boolean state) { - MetricsLogger.action(mContext, MetricsEvent.QS_BLUETOOTH_TOGGLE, state); - mController.setBluetoothEnabled(state); - } - - @Override - public int getMetricsCategory() { - return MetricsEvent.QS_BLUETOOTH_DETAILS; - } - - @Override - public View createDetailView(Context context, View convertView, ViewGroup parent) { - mItems = QSDetailItems.convertOrInflate(context, convertView, parent); - mItems.setTagSuffix("Bluetooth"); - mItems.setCallback(this); - updateItems(); - setItemsVisible(mState.value); - return mItems; - } - - public void setItemsVisible(boolean visible) { - if (mItems == null) return; - mItems.setItemsVisible(visible); - } - - private void updateItems() { - if (mItems == null) return; - if (mController.isBluetoothEnabled()) { - mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty, - R.string.quick_settings_bluetooth_detail_empty_text); - } else { - mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty, - R.string.bt_is_off); - } - ArrayList<Item> items = new ArrayList<Item>(); - final Collection<CachedBluetoothDevice> devices = mController.getDevices(); - if (devices != null) { - int connectedDevices = 0; - int count = 0; - for (CachedBluetoothDevice device : devices) { - if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue; - final Item item = - new Item( - com.android.internal.R.drawable.ic_qs_bluetooth, - device.getName(), - device); - int state = device.getMaxConnectionState(); - if (state == BluetoothProfile.STATE_CONNECTED) { - item.iconResId = R.drawable.ic_bluetooth_connected; - int batteryLevel = device.getBatteryLevel(); - if (batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { - item.icon = new BluetoothBatteryTileIcon(batteryLevel,1 /* iconScale */); - item.line2 = mContext.getString( - R.string.quick_settings_connected_battery_level, - Utils.formatPercentage(batteryLevel)); - } else { - item.line2 = mContext.getString(R.string.quick_settings_connected); - } - item.canDisconnect = true; - items.add(connectedDevices, item); - connectedDevices++; - } else if (state == BluetoothProfile.STATE_CONNECTING) { - item.iconResId = R.drawable.ic_qs_bluetooth_connecting; - item.line2 = mContext.getString(R.string.quick_settings_connecting); - items.add(connectedDevices, item); - } else { - items.add(item); - } - if (++count == MAX_DEVICES) { - break; - } - } - } - mItems.setItems(items.toArray(new Item[items.size()])); - } - - @Override - public void onDetailItemClick(Item item) { - if (item == null || item.tag == null) return; - final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag; - if (device != null && device.getMaxConnectionState() - == BluetoothProfile.STATE_DISCONNECTED) { - mController.connect(device); - } - } - - @Override - public void onDetailItemDisconnect(Item item) { - if (item == null || item.tag == null) return; - final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag; - if (device != null) { - mController.disconnect(device); - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 76c84f9b1b9e..e8d27eccc823 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -20,7 +20,6 @@ import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; import android.annotation.NonNull; import android.app.Dialog; -import android.content.Context; import android.content.Intent; import android.media.MediaRouter.RouteInfo; import android.os.Handler; @@ -29,8 +28,6 @@ import android.provider.Settings; import android.service.quicksettings.Tile; import android.util.Log; import android.view.View; -import android.view.View.OnAttachStateChangeListener; -import android.view.ViewGroup; import android.widget.Button; import androidx.annotation.Nullable; @@ -44,11 +41,8 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.qs.QSDetailItems; -import com.android.systemui.qs.QSDetailItems.Item; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -62,7 +56,6 @@ import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.KeyguardStateController; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; import javax.inject.Inject; @@ -73,7 +66,6 @@ public class CastTile extends QSTileImpl<BooleanState> { new Intent(Settings.ACTION_CAST_SETTINGS); private final CastController mController; - private final CastDetailAdapter mDetailAdapter; private final KeyguardStateController mKeyguard; private final NetworkController mNetworkController; private final DialogLaunchAnimator mDialogLaunchAnimator; @@ -100,7 +92,6 @@ public class CastTile extends QSTileImpl<BooleanState> { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = castController; - mDetailAdapter = new CastDetailAdapter(); mKeyguard = keyguardStateController; mNetworkController = networkController; mDialogLaunchAnimator = dialogLaunchAnimator; @@ -111,11 +102,6 @@ public class CastTile extends QSTileImpl<BooleanState> { } @Override - public DetailAdapter getDetailAdapter() { - return mDetailAdapter; - } - - @Override public BooleanState newTileState() { BooleanState state = new BooleanState(); state.handlesLongClick = false; @@ -154,14 +140,14 @@ public class CastTile extends QSTileImpl<BooleanState> { } List<CastDevice> activeDevices = getActiveDevices(); - if (willPopDetail()) { + if (willPopDialog()) { if (!mKeyguard.isShowing()) { - showDetail(view); + showDialog(view); } else { mActivityStarter.postQSRunnableDismissingKeyguard(() -> { // Dismissing the keyguard will collapse the shade, so we don't animate from the // view here as it would not look good. - showDetail(null /* view */); + showDialog(null /* view */); }); } } else { @@ -173,7 +159,7 @@ public class CastTile extends QSTileImpl<BooleanState> { // (neither routes nor projection), or if we have an active route. In other cases, we assume // that a projection is active. This is messy, but this tile never correctly handled the // case where multiple devices were active :-/. - private boolean willPopDetail() { + private boolean willPopDialog() { List<CastDevice> activeDevices = getActiveDevices(); return activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo); } @@ -190,11 +176,6 @@ public class CastTile extends QSTileImpl<BooleanState> { return activeDevices; } - @Override - public void showDetail(boolean show) { - showDetail(null /* view */); - } - private static class DialogHolder { private Dialog mDialog; @@ -203,7 +184,7 @@ public class CastTile extends QSTileImpl<BooleanState> { } } - private void showDetail(@Nullable View view) { + private void showDialog(@Nullable View view) { mUiHandler.post(() -> { final DialogHolder holder = new DialogHolder(); final Dialog dialog = MediaRouteDialogPresenter.createDialog( @@ -218,7 +199,7 @@ public class CastTile extends QSTileImpl<BooleanState> { holder.init(dialog); SystemUIDialog.setShowForAllUsers(dialog, true); SystemUIDialog.registerDismissListener(dialog); - SystemUIDialog.setWindowOnTop(dialog); + SystemUIDialog.setWindowOnTop(dialog, mKeyguard.isShowing()); mUiHandler.post(() -> { if (view != null) { @@ -268,10 +249,8 @@ public class CastTile extends QSTileImpl<BooleanState> { if (!state.value) { state.secondaryLabel = ""; } - state.contentDescription = state.contentDescription + "," - + mContext.getString(R.string.accessibility_quick_settings_open_details); state.expandedAccessibilityClassName = Button.class.getName(); - state.forceExpandIcon = willPopDetail(); + state.forceExpandIcon = willPopDialog(); } else { state.state = Tile.STATE_UNAVAILABLE; String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi); @@ -279,7 +258,6 @@ public class CastTile extends QSTileImpl<BooleanState> { state.forceExpandIcon = false; } state.stateDescription = state.stateDescription + ", " + state.secondaryLabel; - mDetailAdapter.updateItems(devices); } @Override @@ -339,118 +317,4 @@ public class CastTile extends QSTileImpl<BooleanState> { refreshState(); } }; - - private final class CastDetailAdapter implements DetailAdapter, QSDetailItems.Callback { - private final LinkedHashMap<String, CastDevice> mVisibleOrder = new LinkedHashMap<>(); - - private QSDetailItems mItems; - - @Override - public CharSequence getTitle() { - return mContext.getString(R.string.quick_settings_cast_title); - } - - @Override - public Boolean getToggleState() { - return null; - } - - @Override - public Intent getSettingsIntent() { - return CAST_SETTINGS; - } - - @Override - public void setToggleState(boolean state) { - // noop - } - - @Override - public int getMetricsCategory() { - return MetricsEvent.QS_CAST_DETAILS; - } - - @Override - public View createDetailView(Context context, View convertView, ViewGroup parent) { - mItems = QSDetailItems.convertOrInflate(context, convertView, parent); - mItems.setTagSuffix("Cast"); - if (convertView == null) { - if (DEBUG) Log.d(TAG, "addOnAttachStateChangeListener"); - mItems.addOnAttachStateChangeListener(new OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - if (DEBUG) Log.d(TAG, "onViewAttachedToWindow"); - } - - @Override - public void onViewDetachedFromWindow(View v) { - if (DEBUG) Log.d(TAG, "onViewDetachedFromWindow"); - mVisibleOrder.clear(); - } - }); - } - mItems.setEmptyState(R.drawable.ic_qs_cast_detail_empty, - R.string.quick_settings_cast_detail_empty_text); - mItems.setCallback(this); - updateItems(mController.getCastDevices()); - mController.setDiscovering(true); - return mItems; - } - - private void updateItems(List<CastDevice> devices) { - if (mItems == null) return; - Item[] items = null; - if (devices != null && !devices.isEmpty()) { - // if we are connected, simply show that device - for (CastDevice device : devices) { - if (device.state == CastDevice.STATE_CONNECTED) { - final Item item = - new Item( - R.drawable.ic_cast_connected, - getDeviceName(device), - device); - item.line2 = mContext.getString(R.string.quick_settings_connected); - item.canDisconnect = true; - items = new Item[] { item }; - break; - } - } - // otherwise list all available devices, and don't move them around - if (items == null) { - for (CastDevice device : devices) { - mVisibleOrder.put(device.id, device); - } - items = new Item[devices.size()]; - int i = 0; - for (String id : mVisibleOrder.keySet()) { - final CastDevice device = mVisibleOrder.get(id); - if (!devices.contains(device)) continue; - final Item item = - new Item(R.drawable.ic_cast, getDeviceName(device), device); - if (device.state == CastDevice.STATE_CONNECTING) { - item.line2 = mContext.getString(R.string.quick_settings_connecting); - } - items[i++] = item; - } - } - } - mItems.setItems(items); - } - - @Override - public void onDetailItemClick(Item item) { - if (item == null || item.tag == null) return; - MetricsLogger.action(mContext, MetricsEvent.QS_CAST_SELECT); - final CastDevice device = (CastDevice) item.tag; - mController.startCasting(device); - } - - @Override - public void onDetailItemDisconnect(Item item) { - if (item == null || item.tag == null) return; - MetricsLogger.action(mContext, MetricsEvent.QS_CAST_DISCONNECT); - final CastDevice device = (CastDevice) item.tag; - mController.stopCasting(device); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java index 698a2538ee87..04a25fc193b3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java @@ -32,9 +32,7 @@ import android.service.quicksettings.Tile; import android.telephony.SubscriptionManager; import android.text.Html; import android.text.TextUtils; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.view.WindowManager.LayoutParams; import android.widget.Switch; @@ -49,7 +47,6 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile.SignalState; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -62,6 +59,7 @@ import com.android.systemui.statusbar.connectivity.MobileDataIndicators; import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.statusbar.policy.KeyguardStateController; import javax.inject.Inject; @@ -71,8 +69,7 @@ public class CellularTile extends QSTileImpl<SignalState> { private final NetworkController mController; private final DataUsageController mDataController; - private final CellularDetailAdapter mDetailAdapter; - + private final KeyguardStateController mKeyguard; private final CellSignalCallback mSignalCallback = new CellSignalCallback(); @Inject @@ -85,13 +82,15 @@ public class CellularTile extends QSTileImpl<SignalState> { StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, - NetworkController networkController + NetworkController networkController, + KeyguardStateController keyguardStateController + ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = networkController; + mKeyguard = keyguardStateController; mDataController = mController.getMobileDataController(); - mDetailAdapter = new CellularDetailAdapter(); mController.observe(getLifecycle(), mSignalCallback); } @@ -106,11 +105,6 @@ public class CellularTile extends QSTileImpl<SignalState> { } @Override - public DetailAdapter getDetailAdapter() { - return mDetailAdapter; - } - - @Override public Intent getLongClickIntent() { if (getState().state == Tile.STATE_UNAVAILABLE) { return new Intent(Settings.ACTION_WIRELESS_SETTINGS); @@ -155,18 +149,13 @@ public class CellularTile extends QSTileImpl<SignalState> { dialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG); SystemUIDialog.setShowForAllUsers(dialog, true); SystemUIDialog.registerDismissListener(dialog); - SystemUIDialog.setWindowOnTop(dialog); + SystemUIDialog.setWindowOnTop(dialog, mKeyguard.isShowing()); dialog.show(); } @Override protected void handleSecondaryClick(@Nullable View view) { - if (mDataController.isMobileDataSupported()) { - showDetail(true); - } else { - mActivityStarter - .postStartActivityDismissingKeyguard(getCellularSettingIntent(),0 /* delay */); - } + handleLongClick(view); } @Override @@ -298,11 +287,6 @@ public class CellularTile extends QSTileImpl<SignalState> { mInfo.airplaneModeEnabled = icon.visible; refreshState(mInfo); } - - @Override - public void setMobileDataEnabled(boolean enabled) { - mDetailAdapter.setMobileDataEnabled(enabled); - } } static Intent getCellularSettingIntent() { @@ -314,53 +298,4 @@ public class CellularTile extends QSTileImpl<SignalState> { } return intent; } - - private final class CellularDetailAdapter implements DetailAdapter { - - @Override - public CharSequence getTitle() { - return mContext.getString(R.string.quick_settings_cellular_detail_title); - } - - @Nullable - @Override - public Boolean getToggleState() { - return mDataController.isMobileDataSupported() - ? mDataController.isMobileDataEnabled() - : null; - } - - @Override - public Intent getSettingsIntent() { - return getCellularSettingIntent(); - } - - @Override - public void setToggleState(boolean state) { - MetricsLogger.action(mContext, MetricsEvent.QS_CELLULAR_TOGGLE, state); - mDataController.setMobileDataEnabled(state); - } - - @Override - public int getMetricsCategory() { - return MetricsEvent.QS_DATAUSAGEDETAIL; - } - - @Override - public View createDetailView(Context context, View convertView, ViewGroup parent) { - final DataUsageDetailView v = (DataUsageDetailView) (convertView != null - ? convertView - : LayoutInflater.from(mContext).inflate(R.layout.data_usage, parent, false)); - final DataUsageController.DataUsageInfo info = mDataController.getDataUsageInfo(); - if (info == null) return v; - v.bind(info); - v.findViewById(R.id.roaming_text).setVisibility(mSignalCallback.mInfo.roaming - ? View.VISIBLE : View.INVISIBLE); - return v; - } - - public void setMobileDataEnabled(boolean enabled) { - fireToggleStateChanged(enabled); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index a33650cd3d3f..6cff4cd03b0d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -25,8 +25,6 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.net.Uri; import android.os.Handler; import android.os.Looper; @@ -34,16 +32,10 @@ import android.os.UserManager; import android.provider.Settings; import android.provider.Settings.Global; import android.service.notification.ZenModeConfig; -import android.service.notification.ZenModeConfig.ZenRule; import android.service.quicksettings.Tile; import android.text.TextUtils; -import android.util.Slog; -import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnAttachStateChangeListener; -import android.view.ViewGroup; import android.widget.Switch; -import android.widget.Toast; import androidx.annotation.Nullable; @@ -52,13 +44,11 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.notification.EnableZenModeDialog; import com.android.systemui.Prefs; import com.android.systemui.R; -import com.android.systemui.SysUIToast; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -69,7 +59,6 @@ import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.settings.SecureSettings; -import com.android.systemui.volume.ZenModePanel; import javax.inject.Inject; @@ -83,14 +72,12 @@ public class DndTile extends QSTileImpl<BooleanState> { new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS); private final ZenModeController mController; - private final DndDetailAdapter mDetailAdapter; private final SharedPreferences mSharedPreferences; private final SettingObserver mSettingZenDuration; private final DialogLaunchAnimator mDialogLaunchAnimator; private final QSZenModeDialogMetricsLogger mQSZenDialogMetricsLogger; private boolean mListening; - private boolean mShowingDetail; @Inject public DndTile( @@ -111,7 +98,6 @@ public class DndTile extends QSTileImpl<BooleanState> { statusBarStateController, activityStarter, qsLogger); mController = zenModeController; mSharedPreferences = sharedPreferences; - mDetailAdapter = new DndDetailAdapter(); mController.observe(getLifecycle(), mZenCallback); mDialogLaunchAnimator = dialogLaunchAnimator; mSettingZenDuration = new SettingObserver(secureSettings, mUiHandler, @@ -142,11 +128,6 @@ public class DndTile extends QSTileImpl<BooleanState> { } @Override - public DetailAdapter getDetailAdapter() { - return mDetailAdapter; - } - - @Override public BooleanState newTileState() { return new BooleanState(); } @@ -225,28 +206,7 @@ public class DndTile extends QSTileImpl<BooleanState> { @Override protected void handleSecondaryClick(@Nullable View view) { - if (mController.isVolumeRestricted()) { - // Collapse the panels, so the user can see the toast. - mHost.collapsePanels(); - SysUIToast.makeText(mContext, mContext.getString( - com.android.internal.R.string.error_message_change_not_allowed), - Toast.LENGTH_LONG).show(); - return; - } - if (!mState.value) { - // Because of the complexity of the zen panel, it needs to be shown after - // we turn on zen below. - mController.addCallback(new ZenModeController.Callback() { - @Override - public void onZenChanged(int zen) { - mController.removeCallback(this); - showDetail(true); - } - }); - mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG); - } else { - showDetail(true); - } + handleLongClick(view); } @Override @@ -295,9 +255,6 @@ public class DndTile extends QSTileImpl<BooleanState> { R.string.accessibility_quick_settings_dnd); break; } - if (valueChanged) { - fireToggleStateChanged(state.value); - } state.dualLabelContentDescription = mContext.getResources().getString( R.string.accessibility_quick_settings_open_settings, getTileLabel()); state.expandedAccessibilityClassName = Switch.class.getName(); @@ -349,146 +306,6 @@ public class DndTile extends QSTileImpl<BooleanState> { private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { public void onZenChanged(int zen) { refreshState(zen); - if (isShowingDetail()) { - mDetailAdapter.updatePanel(); - } - } - - @Override - public void onConfigChanged(ZenModeConfig config) { - if (isShowingDetail()) { - mDetailAdapter.updatePanel(); - } - } - }; - - private final class DndDetailAdapter implements DetailAdapter, OnAttachStateChangeListener { - - @Nullable - private ZenModePanel mZenPanel; - private boolean mAuto; - - @Override - public CharSequence getTitle() { - return mContext.getString(R.string.quick_settings_dnd_label); - } - - @Override - public Boolean getToggleState() { - return mState.value; - } - - @Override - public Intent getSettingsIntent() { - return ZEN_SETTINGS; - } - - @Override - public void setToggleState(boolean state) { - MetricsLogger.action(mContext, MetricsEvent.QS_DND_TOGGLE, state); - if (!state) { - mController.setZen(ZEN_MODE_OFF, null, TAG); - mAuto = false; - } else { - mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG); - } - } - - @Override - public int getMetricsCategory() { - return MetricsEvent.QS_DND_DETAILS; - } - - @Override - public View createDetailView(Context context, View convertView, ViewGroup parent) { - mZenPanel = convertView != null ? (ZenModePanel) convertView - : (ZenModePanel) LayoutInflater.from(context).inflate( - R.layout.zen_mode_panel, parent, false); - if (convertView == null) { - mZenPanel.init(mController); - mZenPanel.addOnAttachStateChangeListener(this); - mZenPanel.setCallback(mZenModePanelCallback); - mZenPanel.setEmptyState(R.drawable.ic_qs_dnd_detail_empty, R.string.dnd_is_off); - } - updatePanel(); - return mZenPanel; - } - - private void updatePanel() { - if (mZenPanel == null) return; - mAuto = false; - if (mController.getZen() == ZEN_MODE_OFF) { - mZenPanel.setState(ZenModePanel.STATE_OFF); - } else { - ZenModeConfig config = mController.getConfig(); - String summary = ""; - if (config.manualRule != null && config.manualRule.enabler != null) { - summary = getOwnerCaption(config.manualRule.enabler); - } - for (ZenRule automaticRule : config.automaticRules.values()) { - if (automaticRule.isAutomaticActive()) { - if (summary.isEmpty()) { - summary = mContext.getString(R.string.qs_dnd_prompt_auto_rule, - automaticRule.name); - } else { - summary = mContext.getString(R.string.qs_dnd_prompt_auto_rule_app); - } - } - } - if (summary.isEmpty()) { - mZenPanel.setState(ZenModePanel.STATE_MODIFY); - } else { - mAuto = true; - mZenPanel.setState(ZenModePanel.STATE_AUTO_RULE); - mZenPanel.setAutoText(summary); - } - } - } - - private String getOwnerCaption(String owner) { - final PackageManager pm = mContext.getPackageManager(); - try { - final ApplicationInfo info = pm.getApplicationInfo(owner, 0); - if (info != null) { - final CharSequence seq = info.loadLabel(pm); - if (seq != null) { - final String str = seq.toString().trim(); - return mContext.getString(R.string.qs_dnd_prompt_app, str); - } - } - } catch (Throwable e) { - Slog.w(TAG, "Error loading owner caption", e); - } - return ""; - } - - @Override - public void onViewAttachedToWindow(View v) { - mShowingDetail = true; - } - - @Override - public void onViewDetachedFromWindow(View v) { - mShowingDetail = false; - mZenPanel = null; - } - } - - private final ZenModePanel.Callback mZenModePanelCallback = new ZenModePanel.Callback() { - @Override - public void onPrioritySettings() { - mActivityStarter.postStartActivityDismissingKeyguard( - ZEN_PRIORITY_SETTINGS, 0); - } - - @Override - public void onInteraction() { - // noop - } - - @Override - public void onExpanded(boolean expanded) { - // noop } }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 9df942d3260a..cd7021eb20aa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -411,10 +411,6 @@ public class InternetTile extends QSTileImpl<SignalState> { } boolean wifiConnected = cb.mEnabled && (cb.mWifiSignalIconId > 0) && (cb.mSsid != null); boolean wifiNotConnected = (cb.mWifiSignalIconId > 0) && (cb.mSsid == null); - boolean enabledChanging = state.value != cb.mEnabled; - if (enabledChanging) { - fireToggleStateChanged(cb.mEnabled); - } if (state.slash == null) { state.slash = new SlashState(); state.slash.rotation = 6; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java index 0be0619145b3..f4dd415cb678 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java @@ -36,7 +36,6 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -134,12 +133,6 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS return new Intent(Settings.ACTION_PRIVACY_SETTINGS); } - @Nullable - @Override - public DetailAdapter getDetailAdapter() { - return super.getDetailAdapter(); - } - @Override public void onSensorBlockedChanged(int sensor, boolean blocked) { if (sensor == getSensorId()) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java index 076ef3573dbe..5840a3ddf44d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java @@ -150,6 +150,6 @@ public class UserDetailItemView extends LinearLayout { } protected int getFontSizeDimen() { - return R.dimen.qs_detail_item_secondary_text_size; + return R.dimen.qs_tile_text_size; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java deleted file mode 100644 index db1b6e68640e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.qs.tiles; - -import android.content.Context; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.Looper; -import android.provider.Settings; -import android.util.Pair; -import android.view.View; - -import androidx.annotation.Nullable; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.DetailAdapter; -import com.android.systemui.plugins.qs.QSTile; -import com.android.systemui.plugins.qs.QSTile.State; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.qs.QSHost; -import com.android.systemui.qs.logging.QSLogger; -import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.statusbar.policy.UserInfoController; -import com.android.systemui.statusbar.policy.UserSwitcherController; - -import javax.inject.Inject; - -public class UserTile extends QSTileImpl<State> implements UserInfoController.OnUserInfoChangedListener { - - private final UserSwitcherController mUserSwitcherController; - private final UserInfoController mUserInfoController; - @Nullable - private Pair<String, Drawable> mLastUpdate; - - @Inject - public UserTile( - QSHost host, - @Background Looper backgroundLooper, - @Main Handler mainHandler, - FalsingManager falsingManager, - MetricsLogger metricsLogger, - StatusBarStateController statusBarStateController, - ActivityStarter activityStarter, - QSLogger qsLogger, - UserSwitcherController userSwitcherController, - UserInfoController userInfoController - ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, - statusBarStateController, activityStarter, qsLogger); - mUserSwitcherController = userSwitcherController; - mUserInfoController = userInfoController; - mUserInfoController.observe(getLifecycle(), this); - } - - @Override - public State newTileState() { - return new QSTile.State(); - } - - @Override - public Intent getLongClickIntent() { - return new Intent(Settings.ACTION_USER_SETTINGS); - } - - @Override - protected void handleClick(@Nullable View view) { - showDetail(true); - } - - @Override - public DetailAdapter getDetailAdapter() { - return mUserSwitcherController.mUserDetailAdapter; - } - - @Override - public int getMetricsCategory() { - return MetricsEvent.QS_USER_TILE; - } - - @Override - public CharSequence getTileLabel() { - return getState().label; - } - - @Override - protected void handleUpdateState(State state, Object arg) { - final Pair<String, Drawable> p = arg != null ? (Pair<String, Drawable>) arg : mLastUpdate; - if (p != null) { - state.label = p.first; - // TODO: Better content description. - state.contentDescription = p.first; - state.icon = new Icon() { - @Override - public Drawable getDrawable(Context context) { - return p.second; - } - }; - } else { - // TODO: Default state. - } - } - - @Override - public void onUserInfoChanged(String name, Drawable picture, String userAccount) { - mLastUpdate = new Pair<>(name, picture); - refreshState(mLastUpdate); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index c82ff341bb12..b2be56cca51e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -28,27 +28,22 @@ import android.service.quicksettings.Tile; import android.text.TextUtils; import android.util.Log; import android.view.View; -import android.view.ViewGroup; import android.widget.Switch; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settingslib.wifi.AccessPoint; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.SignalState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.AlphaControlledSignalTileView; -import com.android.systemui.qs.QSDetailItems; -import com.android.systemui.qs.QSDetailItems.Item; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSIconViewImpl; @@ -58,9 +53,6 @@ import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.connectivity.SignalCallback; import com.android.systemui.statusbar.connectivity.WifiIcons; import com.android.systemui.statusbar.connectivity.WifiIndicators; -import com.android.wifitrackerlib.WifiEntry; - -import java.util.List; import javax.inject.Inject; @@ -70,7 +62,6 @@ public class WifiTile extends QSTileImpl<SignalState> { protected final NetworkController mController; private final AccessPointController mWifiController; - private final WifiDetailAdapter mDetailAdapter; private final QSTile.SignalState mStateBeforeClick = newTileState(); protected final WifiSignalCallback mSignalCallback = new WifiSignalCallback(); @@ -93,7 +84,6 @@ public class WifiTile extends QSTileImpl<SignalState> { statusBarStateController, activityStarter, qsLogger); mController = networkController; mWifiController = accessPointController; - mDetailAdapter = (WifiDetailAdapter) createDetailAdapter(); mController.observe(getLifecycle(), mSignalCallback); mStateBeforeClick.spec = "wifi"; } @@ -104,25 +94,6 @@ public class WifiTile extends QSTileImpl<SignalState> { } @Override - public void setDetailListening(boolean listening) { - if (listening) { - mWifiController.addAccessPointCallback(mDetailAdapter); - } else { - mWifiController.removeAccessPointCallback(mDetailAdapter); - } - } - - @Override - public DetailAdapter getDetailAdapter() { - return mDetailAdapter; - } - - @Override - protected DetailAdapter createDetailAdapter() { - return new WifiDetailAdapter(); - } - - @Override public QSIconView createTileView(Context context) { return new AlphaControlledSignalTileView(context); } @@ -158,7 +129,6 @@ public class WifiTile extends QSTileImpl<SignalState> { new Intent(Settings.ACTION_WIFI_SETTINGS), 0); return; } - showDetail(true); if (!mState.value) { mController.setWifiEnabled(true); } @@ -185,11 +155,6 @@ public class WifiTile extends QSTileImpl<SignalState> { && (cb.ssid != null || cb.wifiSignalIconId != WifiIcons.QS_WIFI_NO_NETWORK); boolean wifiNotConnected = (cb.ssid == null) && (cb.wifiSignalIconId == WifiIcons.QS_WIFI_NO_NETWORK); - boolean enabledChanging = state.value != cb.enabled; - if (enabledChanging) { - mDetailAdapter.setItemsVisible(cb.enabled); - fireToggleStateChanged(cb.enabled); - } if (state.slash == null) { state.slash = new SlashState(); state.slash.rotation = 6; @@ -315,150 +280,7 @@ public class WifiTile extends QSTileImpl<SignalState> { mInfo.wifiSignalContentDescription = indicators.qsIcon.contentDescription; mInfo.isTransient = indicators.isTransient; mInfo.statusLabel = indicators.statusLabel; - if (isShowingDetail()) { - mDetailAdapter.updateItems(); - } refreshState(); } } - - protected class WifiDetailAdapter implements DetailAdapter, - AccessPointController.AccessPointCallback, QSDetailItems.Callback { - - @Nullable - private QSDetailItems mItems; - @Nullable - private WifiEntry[] mAccessPoints; - - @Override - public CharSequence getTitle() { - return mContext.getString(R.string.quick_settings_wifi_label); - } - - public Intent getSettingsIntent() { - return WIFI_SETTINGS; - } - - @Override - public Boolean getToggleState() { - return mState.value; - } - - @Override - public void setToggleState(boolean state) { - if (DEBUG) Log.d(TAG, "setToggleState " + state); - MetricsLogger.action(mContext, MetricsEvent.QS_WIFI_TOGGLE, state); - mController.setWifiEnabled(state); - } - - @Override - public int getMetricsCategory() { - return MetricsEvent.QS_WIFI_DETAILS; - } - - @Override - public View createDetailView(Context context, View convertView, ViewGroup parent) { - if (DEBUG) Log.d(TAG, "createDetailView convertView=" + (convertView != null)); - mAccessPoints = null; - mItems = QSDetailItems.convertOrInflate(context, convertView, parent); - mItems.setTagSuffix("Wifi"); - mItems.setCallback(this); - mWifiController.scanForAccessPoints(); // updates APs and items - setItemsVisible(mState.value); - return mItems; - } - - @Override - public void onAccessPointsChanged(final List<WifiEntry> accessPoints) { - mAccessPoints = filterUnreachableAPs(accessPoints); - - updateItems(); - } - - /** Filter unreachable APs from mAccessPoints */ - private WifiEntry[] filterUnreachableAPs(List<WifiEntry> unfiltered) { - int numReachable = 0; - for (WifiEntry ap : unfiltered) { - if (isWifiEntryReachable(ap)) numReachable++; - } - if (numReachable != unfiltered.size()) { - WifiEntry[] accessPoints = new WifiEntry[numReachable]; - int i = 0; - for (WifiEntry ap : unfiltered) { - if (isWifiEntryReachable(ap)) accessPoints[i++] = ap; - } - return accessPoints; - } - return unfiltered.toArray(new WifiEntry[0]); - } - - @Override - public void onSettingsActivityTriggered(Intent settingsIntent) { - mActivityStarter.postStartActivityDismissingKeyguard(settingsIntent, 0); - } - - @Override - public void onDetailItemClick(Item item) { - if (item == null || item.tag == null) return; - final WifiEntry ap = (WifiEntry) item.tag; - if (ap.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) { - if (mWifiController.connect(ap)) { - mHost.collapsePanels(); - } - } - showDetail(false); - } - - @Override - public void onDetailItemDisconnect(Item item) { - // noop - } - - public void setItemsVisible(boolean visible) { - if (mItems == null) return; - mItems.setItemsVisible(visible); - } - - private void updateItems() { - if (mItems == null) return; - if ((mAccessPoints != null && mAccessPoints.length > 0) - || !mSignalCallback.mInfo.enabled) { - fireScanStateChanged(false); - } else { - fireScanStateChanged(true); - } - - // Wi-Fi is off - if (!mSignalCallback.mInfo.enabled) { - mItems.setEmptyState(WifiIcons.QS_WIFI_NO_NETWORK, - R.string.wifi_is_off); - mItems.setItems(null); - return; - } - - // No available access points - mItems.setEmptyState(WifiIcons.QS_WIFI_NO_NETWORK, - R.string.quick_settings_wifi_detail_empty_text); - - // Build the list - Item[] items = null; - if (mAccessPoints != null) { - items = new Item[mAccessPoints.length]; - for (int i = 0; i < mAccessPoints.length; i++) { - final WifiEntry ap = mAccessPoints[i]; - final Item item = new Item(mWifiController.getIcon(ap), ap.getSsid(), ap); - item.line2 = ap.getSummary(); - item.icon2 = ap.getSecurity() != AccessPoint.SECURITY_NONE - ? R.drawable.qs_ic_wifi_lock - : -1; - items[i] = item; - } - } - mItems.setItems(items); - } - } - - private static boolean isWifiEntryReachable(WifiEntry ap) { - return ap.getLevel() != WifiEntry.WIFI_LEVEL_UNREACHABLE; - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 8e019426af14..8b6ddb44460b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -65,6 +65,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.wifitrackerlib.WifiEntry; import java.util.List; @@ -130,6 +131,7 @@ public class InternetDialog extends SystemUIDialog implements private Button mDoneButton; private Button mAirplaneModeButton; private Drawable mBackgroundOn; + private KeyguardStateController mKeyguard; @Nullable private Drawable mBackgroundOff = null; private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; @@ -159,7 +161,8 @@ public class InternetDialog extends SystemUIDialog implements public InternetDialog(Context context, InternetDialogFactory internetDialogFactory, InternetDialogController internetDialogController, boolean canConfigMobileData, boolean canConfigWifi, boolean aboveStatusBar, UiEventLogger uiEventLogger, - @Main Handler handler, @Background Executor executor) { + @Main Handler handler, @Background Executor executor, + KeyguardStateController keyguardStateController) { super(context); if (DEBUG) { Log.d(TAG, "Init InternetDialog"); @@ -177,6 +180,7 @@ public class InternetDialog extends SystemUIDialog implements mWifiManager = mInternetDialogController.getWifiManager(); mCanConfigMobileData = canConfigMobileData; mCanConfigWifi = canConfigWifi; + mKeyguard = keyguardStateController; mUiEventLogger = uiEventLogger; mAdapter = new InternetAdapter(mInternetDialogController); @@ -615,7 +619,7 @@ public class InternetDialog extends SystemUIDialog implements mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); SystemUIDialog.setShowForAllUsers(mAlertDialog, true); SystemUIDialog.registerDismissListener(mAlertDialog); - SystemUIDialog.setWindowOnTop(mAlertDialog); + SystemUIDialog.setWindowOnTop(mAlertDialog, mKeyguard.isShowing()); mAlertDialog.show(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt index 79f7ac3aad3d..4386169c8887 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt @@ -24,6 +24,7 @@ import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.policy.KeyguardStateController import java.util.concurrent.Executor import javax.inject.Inject @@ -40,7 +41,8 @@ class InternetDialogFactory @Inject constructor( private val internetDialogController: InternetDialogController, private val context: Context, private val uiEventLogger: UiEventLogger, - private val dialogLaunchAnimator: DialogLaunchAnimator + private val dialogLaunchAnimator: DialogLaunchAnimator, + private val keyguardStateController: KeyguardStateController ) { companion object { var internetDialog: InternetDialog? = null @@ -61,7 +63,7 @@ class InternetDialogFactory @Inject constructor( } else { internetDialog = InternetDialog(context, this, internetDialogController, canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler, - executor) + executor, keyguardStateController) if (view != null) { dialogLaunchAnimator.showFromView(internetDialog!!, view, animateBackgroundBoundsChange = true) diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt index 7c8f4b15d3a3..8c8c5c8c0097 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt @@ -24,11 +24,13 @@ import android.provider.Settings import android.view.LayoutInflater import android.view.View import androidx.annotation.VisibleForTesting +import com.android.internal.logging.UiEventLogger import com.android.systemui.R import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager +import com.android.systemui.qs.QSUserSwitcherEvent import com.android.systemui.qs.tiles.UserDetailView import com.android.systemui.statusbar.phone.SystemUIDialog import javax.inject.Inject @@ -43,6 +45,7 @@ class UserSwitchDialogController @VisibleForTesting constructor( private val activityStarter: ActivityStarter, private val falsingManager: FalsingManager, private val dialogLaunchAnimator: DialogLaunchAnimator, + private val uiEventLogger: UiEventLogger, private val dialogFactory: (Context) -> SystemUIDialog ) { @@ -51,12 +54,14 @@ class UserSwitchDialogController @VisibleForTesting constructor( userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>, activityStarter: ActivityStarter, falsingManager: FalsingManager, - dialogLaunchAnimator: DialogLaunchAnimator + dialogLaunchAnimator: DialogLaunchAnimator, + uiEventLogger: UiEventLogger ) : this( userDetailViewAdapterProvider, activityStarter, falsingManager, dialogLaunchAnimator, + uiEventLogger, { SystemUIDialog(it) } ) @@ -76,10 +81,13 @@ class UserSwitchDialogController @VisibleForTesting constructor( setCanceledOnTouchOutside(true) setTitle(R.string.qs_user_switch_dialog_title) - setPositiveButton(R.string.quick_settings_done, null) + setPositiveButton(R.string.quick_settings_done) { _, _ -> + uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE) + } setNeutralButton(R.string.quick_settings_more_user_settings) { _, _ -> if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations() + uiEventLogger.log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS) activityStarter.postStartActivityDismissingKeyguard( USER_SETTINGS_INTENT, 0 @@ -95,6 +103,7 @@ class UserSwitchDialogController @VisibleForTesting constructor( adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid)) dialogLaunchAnimator.showFromView(this, view) + uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN) adapter.injectDialogShower(DialogShowerImpl(this, dialogLaunchAnimator)) } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 30456a8c4cc7..50765f227554 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -51,7 +51,9 @@ import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; import android.hardware.display.DisplayManager; -import android.media.MediaActionSound; +import android.media.AudioAttributes; +import android.media.AudioSystem; +import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -93,6 +95,7 @@ import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; import com.google.common.util.concurrent.ListenableFuture; +import java.io.File; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; @@ -248,7 +251,7 @@ public class ScreenshotController { private final WindowManager mWindowManager; private final WindowManager.LayoutParams mWindowLayoutParams; private final AccessibilityManager mAccessibilityManager; - private final MediaActionSound mCameraSound; + private final MediaPlayer mCameraSound; private final ScrollCaptureClient mScrollCaptureClient; private final PhoneWindow mWindow; private final DisplayManager mDisplayManager; @@ -331,8 +334,13 @@ public class ScreenshotController { reloadAssets(); // Setup the Camera shutter sound - mCameraSound = new MediaActionSound(); - mCameraSound.load(MediaActionSound.SHUTTER_CLICK); + mCameraSound = MediaPlayer.create(mContext, + Uri.fromFile(new File(mContext.getResources().getString( + com.android.internal.R.string.config_cameraShutterSound))), null, + new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .build(), AudioSystem.newAudioSessionId()); mCopyBroadcastReceiver = new BroadcastReceiver() { @Override @@ -430,7 +438,9 @@ public class ScreenshotController { void releaseContext() { mContext.unregisterReceiver(mCopyBroadcastReceiver); mContext.release(); - mCameraSound.release(); + if (mCameraSound != null) { + mCameraSound.release(); + } mBgExecutor.shutdownNow(); } @@ -806,7 +816,9 @@ public class ScreenshotController { */ private void saveScreenshotAndToast(Consumer<Uri> finisher) { // Play the shutter sound to notify that we've taken a screenshot - mCameraSound.play(MediaActionSound.SHUTTER_CLICK); + if (mCameraSound != null) { + mCameraSound.start(); + } saveScreenshotInWorkerThread( /* onComplete */ finisher, @@ -840,7 +852,9 @@ public class ScreenshotController { mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash); // Play the shutter sound to notify that we've taken a screenshot - mCameraSound.play(MediaActionSound.SHUTTER_CLICK); + if (mCameraSound != null) { + mCameraSound.start(); + } if (DEBUG_ANIM) { Log.d(TAG, "starting post-screenshot animation"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 2f5eaa621283..b355b0551abe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -453,7 +453,9 @@ public class CommandQueue extends IStatusBar.Stub implements /** @see IStatusBar#updateMediaTapToTransferReceiverDisplay */ default void updateMediaTapToTransferReceiverDisplay( @StatusBarManager.MediaTransferReceiverState int displayState, - @NonNull MediaRoute2Info routeInfo) {} + @NonNull MediaRoute2Info routeInfo, + @Nullable Icon appIcon, + @Nullable CharSequence appName) {} } public CommandQueue(Context context) { @@ -1208,10 +1210,14 @@ public class CommandQueue extends IStatusBar.Stub implements @Override public void updateMediaTapToTransferReceiverDisplay( int displayState, - MediaRoute2Info routeInfo) { + @NonNull MediaRoute2Info routeInfo, + @Nullable Icon appIcon, + @Nullable CharSequence appName) { SomeArgs args = SomeArgs.obtain(); args.arg1 = displayState; args.arg2 = routeInfo; + args.arg3 = appIcon; + args.arg4 = appName; mHandler.obtainMessage(MSG_MEDIA_TRANSFER_RECEIVER_STATE, args).sendToTarget(); } @@ -1629,9 +1635,11 @@ public class CommandQueue extends IStatusBar.Stub implements args = (SomeArgs) msg.obj; int receiverDisplayState = (int) args.arg1; MediaRoute2Info receiverRouteInfo = (MediaRoute2Info) args.arg2; + Icon appIcon = (Icon) args.arg3; + appName = (CharSequence) args.arg4; for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).updateMediaTapToTransferReceiverDisplay( - receiverDisplayState, receiverRouteInfo); + receiverDisplayState, receiverRouteInfo, appIcon, appName); } args.recycle(); break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java index 8e6cf36f8e74..4d933d9ad21e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java @@ -31,6 +31,8 @@ import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry.OnSensitivityChangedListener; +import java.util.ArrayList; + /** * The view in the statusBar that contains part of the heads-up information @@ -161,8 +163,8 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout { return mIconDrawingRect; } - public void onDarkChanged(Rect area, float darkIntensity, int tint) { - mTextView.setTextColor(DarkIconDispatcher.getTint(area, this, tint)); + public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) { + mTextView.setTextColor(DarkIconDispatcher.getTint(areas, this, tint)); } public void setOnDrawingRectChangedListener(Runnable onDrawingRectChangedListener) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index e19fd7a44394..01bdb401e00c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -29,7 +29,9 @@ import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.util.Log; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.dagger.StatusBarModule; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins; @@ -42,10 +44,13 @@ import java.util.List; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.Executor; +import javax.inject.Inject; + /** * This class handles listening to notification updates and passing them along to * NotificationPresenter to be displayed to the user. */ +@SysUISingleton @SuppressLint("OverrideAbstract") public class NotificationListener extends NotificationListenerWithPlugins { private static final String TAG = "NotificationListener"; @@ -66,11 +71,14 @@ public class NotificationListener extends NotificationListenerWithPlugins { /** * Injected constructor. See {@link StatusBarModule}. */ + @Inject public NotificationListener( Context context, NotificationManager notificationManager, SystemClock systemClock, - @Main Executor mainExecutor) { + @Main Executor mainExecutor, + PluginManager pluginManager) { + super(pluginManager); mContext = context; mNotificationManager = notificationManager; mSystemClock = systemClock; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index ff9fc30768b1..3dd717d2f377 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar; -import static com.android.systemui.statusbar.phone.NotificationIconContainer.MAX_ICONS_ON_LOCKSCREEN; - import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -41,6 +39,7 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; +import com.android.systemui.statusbar.notification.row.NotificationBackgroundView; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; @@ -48,7 +47,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.notification.stack.ViewState; import com.android.systemui.statusbar.phone.NotificationIconContainer; -import com.android.systemui.util.Utils; /** * A notification shelf view that is placed inside the notification scroller. It manages the @@ -85,7 +83,7 @@ public class NotificationShelf extends ActivatableNotificationView implements private int mIndexOfFirstViewInShelf = -1; private float mCornerAnimationDistance; private NotificationShelfController mController; - private int mActualWidth = -1; + private float mActualWidth = -1; /** Fraction of lockscreen to shade animation (on lockscreen swipe down). */ private float mFractionToShade; @@ -211,10 +209,6 @@ public class NotificationShelf extends ActivatableNotificationView implements final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight(); viewState.yTranslation = stackEnd - viewState.height; - - final int shortestWidth = mShelfIcons.calculateWidthFor(MAX_ICONS_ON_LOCKSCREEN); - final float fraction = Interpolators.STANDARD.getInterpolation(mFractionToShade); - updateStateWidth(viewState, fraction, shortestWidth); } else { viewState.hidden = true; viewState.location = ExpandableViewState.LOCATION_GONE; @@ -223,15 +217,23 @@ public class NotificationShelf extends ActivatableNotificationView implements } /** - * @param shelfState View state for NotificationShelf - * @param fraction Fraction of lockscreen to shade transition + * @param fractionToShade Fraction of lockscreen to shade transition * @param shortestWidth Shortest width to use for lockscreen shelf */ @VisibleForTesting - public void updateStateWidth(ShelfState shelfState, float fraction, int shortestWidth) { - shelfState.actualWidth = mAmbientState.isOnKeyguard() - ? (int) MathUtils.lerp(shortestWidth, getWidth(), fraction) + public void updateActualWidth(float fractionToShade, float shortestWidth) { + final float actualWidth = mAmbientState.isOnKeyguard() + ? MathUtils.lerp(shortestWidth, getWidth(), fractionToShade) : getWidth(); + ActivatableNotificationView anv = (ActivatableNotificationView) this; + NotificationBackgroundView bg = anv.getBackgroundNormal(); + if (bg != null) { + anv.getBackgroundNormal().setActualWidth((int) actualWidth); + } + if (mShelfIcons != null) { + mShelfIcons.setActualLayoutWidth((int) actualWidth); + } + mActualWidth = actualWidth; } /** @@ -245,7 +247,7 @@ public class NotificationShelf extends ActivatableNotificationView implements * @return Actual width of shelf, accounting for possible ongoing width animation */ public int getActualWidth() { - return mActualWidth > -1 ? mActualWidth : getWidth(); + return mActualWidth > -1 ? (int) mActualWidth : getWidth(); } /** @@ -412,6 +414,10 @@ public class NotificationShelf extends ActivatableNotificationView implements || !mShowNotificationShelf || numViewsInShelf < 1f; + final float fractionToShade = Interpolators.STANDARD.getInterpolation(mFractionToShade); + final float shortestWidth = mShelfIcons.calculateWidthFor(numViewsInShelf); + updateActualWidth(fractionToShade, shortestWidth); + // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa. setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE); setBackgroundTop(backgroundTop); @@ -921,30 +927,17 @@ public class NotificationShelf extends ActivatableNotificationView implements public class ShelfState extends ExpandableViewState { private boolean hasItemsInStableShelf; private ExpandableView firstViewInShelf; - public int actualWidth = -1; - - private void updateShelfWidth(View view) { - if (actualWidth < 0) { - return; - } - mActualWidth = actualWidth; - ActivatableNotificationView anv = (ActivatableNotificationView) view; - anv.getBackgroundNormal().setActualWidth(actualWidth); - mShelfIcons.setActualLayoutWidth(actualWidth); - } @Override public void applyToView(View view) { if (!mShowNotificationShelf) { return; } - super.applyToView(view); setIndexOfFirstViewInShelf(firstViewInShelf); updateAppearance(); setHasItemsInStableShelf(hasItemsInStableShelf); mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); - updateShelfWidth(view); } @Override @@ -952,13 +945,11 @@ public class NotificationShelf extends ActivatableNotificationView implements if (!mShowNotificationShelf) { return; } - super.animateTo(view, properties); setIndexOfFirstViewInShelf(firstViewInShelf); updateAppearance(); setHasItemsInStableShelf(hasItemsInStableShelf); mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); - updateShelfWidth(view); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 4a7606c316e2..72c4ce8afe9b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -37,6 +37,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Parcelable; +import android.os.Trace; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.text.TextUtils; @@ -60,6 +61,7 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.util.drawable.DrawableSize; import java.text.NumberFormat; +import java.util.ArrayList; import java.util.Arrays; public class StatusBarIconView extends AnimatedImageView implements StatusIconDisplayable { @@ -370,10 +372,13 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi } Drawable drawable; try { + Trace.beginSection("StatusBarIconView#updateDrawable()"); drawable = getIcon(mIcon); } catch (OutOfMemoryError e) { Log.w(TAG, "OOM while inflating " + mIcon.icon + " for slot " + mSlot); return false; + } finally { + Trace.endSection(); } if (drawable == null) { @@ -961,8 +966,8 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi } @Override - public void onDarkChanged(Rect area, float darkIntensity, int tint) { - int areaTint = getTint(area, this, tint); + public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) { + int areaTint = getTint(areas, this, tint); ColorStateList color = ColorStateList.valueOf(areaTint); setImageTintList(color); setDecorColor(areaTint); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java index 68dcdd9ff49f..465ab93132f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java @@ -17,7 +17,7 @@ package com.android.systemui.statusbar; import static com.android.systemui.plugins.DarkIconDispatcher.getTint; -import static com.android.systemui.plugins.DarkIconDispatcher.isInArea; +import static com.android.systemui.plugins.DarkIconDispatcher.isInAreas; import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT; import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN; import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON; @@ -40,6 +40,8 @@ import com.android.systemui.R; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; +import java.util.ArrayList; + public class StatusBarMobileView extends FrameLayout implements DarkReceiver, StatusIconDisplayable { private static final String TAG = "StatusBarMobileView"; @@ -222,11 +224,11 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver, } @Override - public void onDarkChanged(Rect area, float darkIntensity, int tint) { - float intensity = isInArea(area, this) ? darkIntensity : 0; + public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) { + float intensity = isInAreas(areas, this) ? darkIntensity : 0; mMobileDrawable.setTintList( ColorStateList.valueOf(mDualToneHandler.getSingleColor(intensity))); - ColorStateList color = ColorStateList.valueOf(getTint(area, this, tint)); + ColorStateList color = ColorStateList.valueOf(getTint(areas, this, tint)); mIn.setImageTintList(color); mOut.setImageTintList(color); mMobileType.setImageTintList(color); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java index 6dbcc44e385b..a6986d797833 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar; import static com.android.systemui.plugins.DarkIconDispatcher.getTint; -import static com.android.systemui.plugins.DarkIconDispatcher.isInArea; import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT; import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN; import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON; @@ -37,6 +36,8 @@ import com.android.systemui.R; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; +import java.util.ArrayList; + /** * Start small: StatusBarWifiView will be able to layout from a WifiIconState */ @@ -235,8 +236,8 @@ public class StatusBarWifiView extends FrameLayout implements DarkReceiver, } @Override - public void onDarkChanged(Rect area, float darkIntensity, int tint) { - int areaTint = getTint(area, this, tint); + public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) { + int areaTint = getTint(areas, this, tint); ColorStateList color = ColorStateList.valueOf(areaTint); mWifiIcon.setImageTintList(color); mIn.setImageTintList(color); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartStatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartStatusBarModule.kt new file mode 100644 index 000000000000..46c1abb859b3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartStatusBarModule.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.dagger + +import com.android.systemui.CoreStartable +import com.android.systemui.statusbar.phone.StatusBar +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +interface StartStatusBarModule { + /** Start the StatusBar */ + @Binds + @IntoMap + @ClassKey(StatusBar::class) + abstract fun bindsStatusBar(statusBar: StatusBar): CoreStartable +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index e3d0d9802b8d..c687e8282c7c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.dagger; import android.app.IActivityManager; -import android.app.NotificationManager; import android.content.Context; import android.os.Handler; import android.service.dreams.IDreamManager; @@ -38,7 +37,6 @@ import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.MediaArtworkProcessor; import com.android.systemui.statusbar.NotificationClickNotifier; -import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -167,18 +165,6 @@ public interface StatusBarDependenciesModule { /** */ @SysUISingleton @Provides - static NotificationListener provideNotificationListener( - Context context, - NotificationManager notificationManager, - SystemClock systemClock, - @Main Executor mainExecutor) { - return new NotificationListener( - context, notificationManager, systemClock, mainExecutor); - } - - /** */ - @SysUISingleton - @Provides static SmartReplyController provideSmartReplyController( DumpManager dumpManager, NotificationVisibilityProvider visibilityProvider, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt index 962c7fa6aea7..140142394c24 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt @@ -92,6 +92,8 @@ class PrivacyDotViewController @Inject constructor( private val views: Sequence<View> get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl) + private var showingListener: ShowingListener? = null + init { contentInsetsProvider.addCallback(object : StatusBarContentInsetsChangedListener { override fun onStatusBarContentInsetsChanged() { @@ -132,6 +134,10 @@ class PrivacyDotViewController @Inject constructor( uiExecutor = e } + fun setShowingListener(l: ShowingListener) { + showingListener = l + } + fun setQsExpanded(expanded: Boolean) { dlog("setQsExpanded $expanded") synchronized(lock) { @@ -176,15 +182,20 @@ class PrivacyDotViewController @Inject constructor( .setDuration(DURATION) .setInterpolator(Interpolators.ALPHA_OUT) .alpha(0f) - .withEndAction { dot.visibility = View.INVISIBLE } + .withEndAction { + dot.visibility = View.INVISIBLE + showingListener?.onPrivacyDotHidden(dot) + } .start() } else { dot.visibility = View.INVISIBLE + showingListener?.onPrivacyDotHidden(dot) } } @UiThread private fun showDotView(dot: View, animate: Boolean) { + showingListener?.onPrivacyDotShown(dot) dot.clearAnimation() if (animate) { dot.visibility = View.VISIBLE @@ -320,6 +331,7 @@ class PrivacyDotViewController @Inject constructor( @UiThread private fun updateDesignatedCorner(newCorner: View?, shouldShowDot: Boolean) { if (shouldShowDot) { + showingListener?.onPrivacyDotShown(newCorner) newCorner?.apply { clearAnimation() visibility = View.VISIBLE @@ -336,6 +348,11 @@ class PrivacyDotViewController @Inject constructor( private fun setCornerVisibilities(vis: Int) { views.forEach { corner -> corner.visibility = vis + if (vis == View.VISIBLE) { + showingListener?.onPrivacyDotShown(corner) + } else { + showingListener?.onPrivacyDotHidden(corner) + } } } @@ -555,6 +572,11 @@ class PrivacyDotViewController @Inject constructor( ) } } + + public interface ShowingListener { + fun onPrivacyDotShown(v: View?) + fun onPrivacyDotHidden(v: View?) + } } private fun dlog(s: String) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index e739b9f056f0..e3ebef99f45f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -204,7 +204,7 @@ public interface NotificationsModule { static VisualStabilityManager provideVisualStabilityManager( NotificationEntryManager notificationEntryManager, VisualStabilityProvider visualStabilityProvider, - Handler handler, + @Main Handler handler, StatusBarStateController statusBarStateController, WakefulnessLifecycle wakefulnessLifecycle, DumpManager dumpManager) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 6a78370c7dab..d5d1ceada88a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.phone; import static android.app.StatusBarManager.SESSION_KEYGUARD; import android.annotation.IntDef; -import android.content.Context; import android.content.res.Resources; import android.hardware.biometrics.BiometricSourceType; import android.hardware.fingerprint.FingerprintManager; @@ -45,6 +44,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; import com.android.systemui.Dumpable; +import com.android.systemui.R; import com.android.systemui.biometrics.AuthController; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; @@ -163,7 +163,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private final KeyguardStateController mKeyguardStateController; private final NotificationShadeWindowController mNotificationShadeWindowController; private final SessionTracker mSessionTracker; - private final Context mContext; + private final int mConsecutiveFpFailureThreshold; private final int mWakeUpDelay; private int mMode; private BiometricSourceType mBiometricType; @@ -266,7 +266,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Inject - public BiometricUnlockController(Context context, DozeScrimController dozeScrimController, + public BiometricUnlockController(DozeScrimController dozeScrimController, KeyguardViewMediator keyguardViewMediator, ScrimController scrimController, ShadeController shadeController, NotificationShadeWindowController notificationShadeWindowController, @@ -284,7 +284,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp KeyguardUnlockAnimationController keyguardUnlockAnimationController, SessionTracker sessionTracker, LatencyTracker latencyTracker) { - mContext = context; mPowerManager = powerManager; mShadeController = shadeController; mUpdateMonitor = keyguardUpdateMonitor; @@ -302,6 +301,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mKeyguardStateController = keyguardStateController; mHandler = handler; mWakeUpDelay = resources.getInteger(com.android.internal.R.integer.config_wakeUpDelayDoze); + mConsecutiveFpFailureThreshold = resources.getInteger( + R.integer.fp_consecutive_failure_time_ms); mKeyguardBypassController = keyguardBypassController; mKeyguardBypassController.setUnlockController(this); mMetricsLogger = metricsLogger; @@ -666,8 +667,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp if (biometricSourceType == BiometricSourceType.FINGERPRINT && mUpdateMonitor.isUdfpsSupported()) { long currUptimeMillis = SystemClock.uptimeMillis(); - if (currUptimeMillis - mLastFpFailureUptimeMillis - < (mStatusBarStateController.isDozing() ? 3500 : 2000)) { + if (currUptimeMillis - mLastFpFailureUptimeMillis < mConsecutiveFpFailureThreshold) { mNumConsecutiveFpFailures += 1; } else { mNumConsecutiveFpFailures = 1; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java index d06de75056d2..150da1687b39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java @@ -30,6 +30,7 @@ import com.android.systemui.statusbar.CommandQueue; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import javax.inject.Inject; @@ -40,7 +41,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, LightBarTransitionsController.DarkIntensityApplier { private final LightBarTransitionsController mTransitionsController; - private final Rect mTintArea = new Rect(); + private final ArrayList<Rect> mTintAreas = new ArrayList<>(); private final ArrayMap<Object, DarkReceiver> mReceivers = new ArrayMap<>(); private int mIconTint = DEFAULT_ICON_TINT; @@ -69,14 +70,14 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, public void addDarkReceiver(DarkReceiver receiver) { mReceivers.put(receiver, receiver); - receiver.onDarkChanged(mTintArea, mDarkIntensity, mIconTint); + receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint); } public void addDarkReceiver(ImageView imageView) { DarkReceiver receiver = (area, darkIntensity, tint) -> imageView.setImageTintList( - ColorStateList.valueOf(getTint(mTintArea, imageView, mIconTint))); + ColorStateList.valueOf(getTint(mTintAreas, imageView, mIconTint))); mReceivers.put(imageView, receiver); - receiver.onDarkChanged(mTintArea, mDarkIntensity, mIconTint); + receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint); } public void removeDarkReceiver(DarkReceiver object) { @@ -88,23 +89,23 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, } public void applyDark(DarkReceiver object) { - mReceivers.get(object).onDarkChanged(mTintArea, mDarkIntensity, mIconTint); + mReceivers.get(object).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint); } /** * Sets the dark area so {@link #applyDark} only affects the icons in the specified area. * - * @param darkArea the area in which icons should change it's tint, in logical screen - * coordinates + * @param darkAreas the areas in which icons should change it's tint, in logical screen + * coordinates */ - public void setIconsDarkArea(Rect darkArea) { - if (darkArea == null && mTintArea.isEmpty()) { + public void setIconsDarkArea(ArrayList<Rect> darkAreas) { + if (darkAreas == null && mTintAreas.isEmpty()) { return; } - if (darkArea == null) { - mTintArea.setEmpty(); - } else { - mTintArea.set(darkArea); + + mTintAreas.clear(); + if (darkAreas != null) { + mTintAreas.addAll(darkAreas); } applyIconTint(); } @@ -124,7 +125,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, private void applyIconTint() { for (int i = 0; i < mReceivers.size(); i++) { - mReceivers.valueAt(i).onDarkChanged(mTintArea, mDarkIntensity, mIconTint); + mReceivers.valueAt(i).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint); } } @@ -133,6 +134,6 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, pw.println("DarkIconDispatcher: "); pw.println(" mIconTint: 0x" + Integer.toHexString(mIconTint)); pw.println(" mDarkIntensity: " + mDarkIntensity + "f"); - pw.println(" mTintArea: " + mTintArea); + pw.println(" mTintAreas: " + mTintAreas); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java index ee51efb090dd..6dbbf0d53246 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java @@ -315,14 +315,14 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da } @Override - public void onDarkChanged(Rect area, float darkIntensity, int tint) { - setColor(DarkIconDispatcher.getTint(area, mStatusIcons, tint)); + public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) { + setColor(DarkIconDispatcher.getTint(areas, mStatusIcons, tint)); if (mWifiView != null) { - mWifiView.onDarkChanged(area, darkIntensity, tint); + mWifiView.onDarkChanged(areas, darkIntensity, tint); } for (StatusBarMobileView view : mMobileViews) { - view.onDarkChanged(area, darkIntensity, tint); + view.onDarkChanged(areas, darkIntensity, tint); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index f421d23bd0d8..9863a0ed1ce0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -16,34 +16,37 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.OPERATOR_NAME_FRAME_VIEW; + import android.graphics.Rect; import android.view.View; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ViewClippingUtil; -import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.HeadsUpStatusBarView; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope; +import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.util.ViewController; +import java.util.Optional; +import java.util.ArrayList; import java.util.function.BiConsumer; import java.util.function.Consumer; import javax.inject.Inject; +import javax.inject.Named; /** * Controls the appearance of heads up notifications in the icon area and the header itself. @@ -69,8 +72,8 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar private final CommandQueue mCommandQueue; private final NotificationWakeUpCoordinator mWakeUpCoordinator; - private View mClockView; - private View mOperatorNameView; + private final View mClockView; + private final Optional<View> mOperatorNameViewOptional; @VisibleForTesting float mExpandedHeight; @@ -86,45 +89,24 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar } }; private boolean mAnimationsEnabled = true; - private KeyguardStateController mKeyguardStateController; - - @Inject - public HeadsUpAppearanceController( - NotificationIconAreaController notificationIconAreaController, - HeadsUpManagerPhone headsUpManager, - NotificationStackScrollLayoutController notificationStackScrollLayoutController, - SysuiStatusBarStateController statusBarStateController, - KeyguardBypassController keyguardBypassController, - KeyguardStateController keyguardStateController, - NotificationWakeUpCoordinator wakeUpCoordinator, CommandQueue commandQueue, - NotificationPanelViewController notificationPanelViewController, - @RootView PhoneStatusBarView statusBarView) { - this(notificationIconAreaController, headsUpManager, statusBarStateController, - keyguardBypassController, wakeUpCoordinator, keyguardStateController, - commandQueue, notificationStackScrollLayoutController, - notificationPanelViewController, - // TODO(b/205609837): We should have the StatusBarFragmentComponent provide these - // four views, and then we can delete this constructor and just use the one below - // (which also removes the undesirable @VisibleForTesting). - statusBarView.findViewById(R.id.heads_up_status_bar_view), - statusBarView.findViewById(R.id.clock), - statusBarView.findViewById(R.id.operator_name_frame)); - } + private final KeyguardStateController mKeyguardStateController; @VisibleForTesting + @Inject public HeadsUpAppearanceController( NotificationIconAreaController notificationIconAreaController, HeadsUpManagerPhone headsUpManager, StatusBarStateController stateController, KeyguardBypassController bypassController, NotificationWakeUpCoordinator wakeUpCoordinator, + DarkIconDispatcher darkIconDispatcher, KeyguardStateController keyguardStateController, CommandQueue commandQueue, NotificationStackScrollLayoutController stackScrollerController, NotificationPanelViewController notificationPanelViewController, HeadsUpStatusBarView headsUpStatusBarView, - View clockView, - View operatorNameView) { + Clock clockView, + @Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) { super(headsUpStatusBarView); mNotificationIconAreaController = notificationIconAreaController; mHeadsUpManager = headsUpManager; @@ -141,8 +123,8 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar mNotificationPanelViewController = notificationPanelViewController; mStackScrollerController.setHeadsUpAppearanceController(this); mClockView = clockView; - mOperatorNameView = operatorNameView; - mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class); + mOperatorNameViewOptional = operatorNameViewOptional; + mDarkIconDispatcher = darkIconDispatcher; mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override @@ -232,14 +214,10 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar mView.setVisibility(View.VISIBLE); show(mView); hide(mClockView, View.INVISIBLE); - if (mOperatorNameView != null) { - hide(mOperatorNameView, View.INVISIBLE); - } + mOperatorNameViewOptional.ifPresent(view -> hide(view, View.INVISIBLE)); } else { show(mClockView); - if (mOperatorNameView != null) { - show(mOperatorNameView); - } + mOperatorNameViewOptional.ifPresent(this::show); hide(mView, View.GONE, () -> { updateParentClipping(true /* shouldClip */); }); @@ -392,8 +370,8 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar } @Override - public void onDarkChanged(Rect area, float darkIntensity, int tint) { - mView.onDarkChanged(area, darkIntensity, tint); + public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) { + mView.onDarkChanged(areas, darkIntensity, tint); } public void onStateChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 565b2d333d4c..95a2a6e75e7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -41,6 +41,7 @@ import com.android.keyguard.ViewMediatorCallback; import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.DejankUtils; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -115,7 +116,7 @@ public class KeyguardBouncer { BouncerExpansionCallback expansionCallback, KeyguardStateController keyguardStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, - KeyguardBypassController keyguardBypassController, Handler handler, + KeyguardBypassController keyguardBypassController, @Main Handler handler, KeyguardSecurityModel keyguardSecurityModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) { mContext = context; @@ -647,7 +648,7 @@ public class KeyguardBouncer { DismissCallbackRegistry dismissCallbackRegistry, FalsingCollector falsingCollector, KeyguardStateController keyguardStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, - KeyguardBypassController keyguardBypassController, Handler handler, + KeyguardBypassController keyguardBypassController, @Main Handler handler, KeyguardSecurityModel keyguardSecurityModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) { mContext = context; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java index 817b86bf643e..9bdefcd98422 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java @@ -20,7 +20,6 @@ import static com.android.systemui.statusbar.phone.StatusBar.MULTIUSER_DEBUG; import android.service.notification.StatusBarNotification; import android.util.Log; -import com.android.systemui.Dependency; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment; @@ -33,13 +32,15 @@ public class KeyguardEnvironmentImpl implements KeyguardEnvironment { private static final String TAG = "KeyguardEnvironmentImpl"; - private final NotificationLockscreenUserManager mLockscreenUserManager = - Dependency.get(NotificationLockscreenUserManager.class); - private final DeviceProvisionedController mDeviceProvisionedController = - Dependency.get(DeviceProvisionedController.class); + private final NotificationLockscreenUserManager mLockscreenUserManager; + private final DeviceProvisionedController mDeviceProvisionedController; @Inject - public KeyguardEnvironmentImpl() { + public KeyguardEnvironmentImpl( + NotificationLockscreenUserManager notificationLockscreenUserManager, + DeviceProvisionedController deviceProvisionedController) { + mLockscreenUserManager = notificationLockscreenUserManager; + mDeviceProvisionedController = deviceProvisionedController; } @Override // NotificationEntryManager.KeyguardEnvironment diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index b8e9875be7e2..65173a230871 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -50,6 +50,7 @@ import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; /** * The header group on Keyguard. @@ -60,7 +61,7 @@ public class KeyguardStatusBarView extends RelativeLayout { private static final int LAYOUT_CUTOUT = 1; private static final int LAYOUT_NO_CUTOUT = 2; - private final Rect mEmptyRect = new Rect(0, 0, 0, 0); + private final ArrayList<Rect> mEmptyTintRect = new ArrayList<>(); private boolean mShowPercentAvailable; private boolean mBatteryCharging; @@ -476,14 +477,14 @@ public class KeyguardStatusBarView extends RelativeLayout { iconManager.setTint(iconColor); } - applyDarkness(R.id.battery, mEmptyRect, intensity, iconColor); - applyDarkness(R.id.clock, mEmptyRect, intensity, iconColor); + applyDarkness(R.id.battery, mEmptyTintRect, intensity, iconColor); + applyDarkness(R.id.clock, mEmptyTintRect, intensity, iconColor); } - private void applyDarkness(int id, Rect tintArea, float intensity, int color) { + private void applyDarkness(int id, ArrayList<Rect> tintAreas, float intensity, int color) { View v = findViewById(id); if (v instanceof DarkReceiver) { - ((DarkReceiver) v).onDarkChanged(tintArea, intensity, color); + ((DarkReceiver) v).onDarkChanged(tintAreas, intensity, color); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index 88ae0db5bad0..4082db7b6bef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -25,6 +25,7 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARE import android.content.Context; import android.graphics.Color; +import android.graphics.Rect; import android.view.InsetsFlags; import android.view.ViewDebug; import android.view.WindowInsetsController.Appearance; @@ -41,6 +42,7 @@ import com.android.systemui.statusbar.policy.BatteryController; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import javax.inject.Inject; @@ -214,27 +216,23 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private void updateStatus() { final int numStacks = mAppearanceRegions.length; - int numLightStacks = 0; - - // We can only have maximum one light stack. - int indexLightStack = -1; + final ArrayList<Rect> lightBarBounds = new ArrayList<>(); for (int i = 0; i < numStacks; i++) { - if (isLight(mAppearanceRegions[i].getAppearance(), mStatusBarMode, - APPEARANCE_LIGHT_STATUS_BARS)) { - numLightStacks++; - indexLightStack = i; + final AppearanceRegion ar = mAppearanceRegions[i]; + if (isLight(ar.getAppearance(), mStatusBarMode, APPEARANCE_LIGHT_STATUS_BARS)) { + lightBarBounds.add(ar.getBounds()); } } // If no one is light, all icons become white. - if (numLightStacks == 0) { + if (lightBarBounds.isEmpty()) { mStatusBarIconController.getTransitionsController().setIconsDark( false, animateChange()); } // If all stacks are light, all icons get dark. - else if (numLightStacks == numStacks) { + else if (lightBarBounds.size() == numStacks) { mStatusBarIconController.setIconsDarkArea(null); mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange()); @@ -242,8 +240,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC // Not the same for every stack, magic! else { - mStatusBarIconController.setIconsDarkArea( - mAppearanceRegions[indexLightStack].getBounds()); + mStatusBarIconController.setIconsDarkArea(lightBarBounds); mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange()); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java index 357a12b09b0d..324d47eed1db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java @@ -29,9 +29,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.qs.FooterActionsView; -import com.android.systemui.qs.QSDetailDisplayer; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.user.UserSwitchDialogController; import com.android.systemui.statusbar.policy.UserSwitcherController; @@ -44,7 +42,6 @@ import javax.inject.Inject; public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { private final UserManager mUserManager; private final UserSwitcherController mUserSwitcherController; - private final QSDetailDisplayer mQsDetailDisplayer; private final FalsingManager mFalsingManager; private final UserSwitchDialogController mUserSwitchDialogController; private final ActivityStarter mActivityStarter; @@ -66,17 +63,8 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { mActivityStarter.startActivity(intent, true /* dismissShade */, ActivityLaunchAnimator.Controller.fromView(v, null), true /* showOverlockscreenwhenlocked */); - } else if (mFeatureFlags.isEnabled(Flags.NEW_USER_SWITCHER)) { - mUserSwitchDialogController.showDialog(v); } else { - View center = mView.getChildCount() > 0 ? mView.getChildAt(0) : mView; - - int[] tmpInt = new int[2]; - center.getLocationInWindow(tmpInt); - tmpInt[0] += center.getWidth() / 2; - tmpInt[1] += center.getHeight() / 2; - - mQsDetailDisplayer.showDetailAdapter(getUserDetailAdapter(), tmpInt[0], tmpInt[1]); + mUserSwitchDialogController.showDialog(v); } } }; @@ -85,7 +73,6 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { public static class Factory { private final UserManager mUserManager; private final UserSwitcherController mUserSwitcherController; - private final QSDetailDisplayer mQsDetailDisplayer; private final FalsingManager mFalsingManager; private final UserSwitchDialogController mUserSwitchDialogController; private final ActivityStarter mActivityStarter; @@ -93,12 +80,11 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { @Inject public Factory(UserManager userManager, UserSwitcherController userSwitcherController, - QSDetailDisplayer qsDetailDisplayer, FalsingManager falsingManager, + FalsingManager falsingManager, UserSwitchDialogController userSwitchDialogController, FeatureFlags featureFlags, ActivityStarter activityStarter) { mUserManager = userManager; mUserSwitcherController = userSwitcherController; - mQsDetailDisplayer = qsDetailDisplayer; mFalsingManager = falsingManager; mUserSwitchDialogController = userSwitchDialogController; mActivityStarter = activityStarter; @@ -107,20 +93,19 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { public MultiUserSwitchController create(FooterActionsView view) { return new MultiUserSwitchController(view.findViewById(R.id.multi_user_switch), - mUserManager, mUserSwitcherController, mQsDetailDisplayer, + mUserManager, mUserSwitcherController, mFalsingManager, mUserSwitchDialogController, mFeatureFlags, mActivityStarter); } } private MultiUserSwitchController(MultiUserSwitch view, UserManager userManager, - UserSwitcherController userSwitcherController, QSDetailDisplayer qsDetailDisplayer, + UserSwitcherController userSwitcherController, FalsingManager falsingManager, UserSwitchDialogController userSwitchDialogController, FeatureFlags featureFlags, ActivityStarter activityStarter) { super(view); mUserManager = userManager; mUserSwitcherController = userSwitcherController; - mQsDetailDisplayer = qsDetailDisplayer; mFalsingManager = falsingManager; mUserSwitchDialogController = userSwitchDialogController; mFeatureFlags = featureFlags; @@ -143,10 +128,6 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { mView.setOnClickListener(null); } - protected DetailAdapter getUserDetailAdapter() { - return mUserSwitcherController.mUserDetailAdapter; - } - private void registerListener() { if (mUserManager.isUserSwitcherEnabled() && mUserListener == null) { 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 8bababfb9d5e..ca6e67ec4a83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -28,7 +28,7 @@ import android.util.ArrayMap; import android.util.Log; import com.android.internal.statusbar.NotificationVisibility; -import com.android.systemui.Dependency; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.statusbar.notification.NotificationEntryListener; @@ -48,11 +48,14 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import javax.inject.Inject; + /** * A helper class dealing with the alert interactions between {@link NotificationGroupManagerLegacy} * and {@link HeadsUpManager}. In particular, this class deals with keeping * the correct notification in a group alerting based off the group suppression and alertOverride. */ +@SysUISingleton public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener, StateListener { @@ -74,8 +77,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private HeadsUpManager mHeadsUpManager; private final RowContentBindStage mRowContentBindStage; - private final NotificationGroupManagerLegacy mGroupManager = - Dependency.get(NotificationGroupManagerLegacy.class); + private final NotificationGroupManagerLegacy mGroupManager; private NotificationEntryManager mEntryManager; @@ -84,9 +86,14 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis /** * Injected constructor. See {@link StatusBarPhoneModule}. */ - public NotificationGroupAlertTransferHelper(RowContentBindStage bindStage) { - Dependency.get(StatusBarStateController.class).addCallback(this); + @Inject + public NotificationGroupAlertTransferHelper( + RowContentBindStage bindStage, + StatusBarStateController statusBarStateController, + NotificationGroupManagerLegacy notificationGroupManagerLegacy) { mRowContentBindStage = bindStage; + mGroupManager = notificationGroupManagerLegacy; + statusBarStateController.addCallback(this); } /** Causes the TransferHelper to register itself as a listener to the appropriate classes. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index c36130073765..e70c81de81af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -83,7 +83,7 @@ public class NotificationIconAreaController implements private NotificationIconContainer mNotificationIcons; private NotificationIconContainer mShelfIcons; private NotificationIconContainer mAodIcons; - private final Rect mTintArea = new Rect(); + private final ArrayList<Rect> mTintAreas = new ArrayList<>(); private Context mContext; private final DemoModeController mDemoModeController; @@ -240,17 +240,14 @@ public class NotificationIconAreaController implements * See {@link com.android.systemui.statusbar.policy.DarkIconDispatcher#setIconsDarkArea}. * Sets the color that should be used to tint any icons in the notification area. * - * @param tintArea the area in which to tint the icons, specified in screen coordinates + * @param tintAreas the areas in which to tint the icons, specified in screen coordinates * @param darkIntensity */ - public void onDarkChanged(Rect tintArea, float darkIntensity, int iconTint) { - if (tintArea == null) { - mTintArea.setEmpty(); - } else { - mTintArea.set(tintArea); - } + public void onDarkChanged(ArrayList<Rect> tintAreas, float darkIntensity, int iconTint) { + mTintAreas.clear(); + mTintAreas.addAll(tintAreas); - if (DarkIconDispatcher.isInArea(tintArea, mNotificationIconArea)) { + if (DarkIconDispatcher.isInAreas(tintAreas, mNotificationIconArea)) { mIconTint = iconTint; } @@ -489,7 +486,7 @@ public class NotificationIconAreaController implements int color = StatusBarIconView.NO_COLOR; boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mContrastColorUtil); if (colorize) { - color = DarkIconDispatcher.getTint(mTintArea, v, tint); + color = DarkIconDispatcher.getTint(mTintAreas, v, tint); } v.setStaticDrawableColor(color); v.setDecorColor(tint); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index ebfed1a689cf..034b751d1e61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -387,16 +387,18 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } /** - * @return Width of shelf for the given number of icons and overflow dot + * @return Width of shelf for the given number of icons */ - public int calculateWidthFor(int numMaxIcons) { + public float calculateWidthFor(float numIcons) { if (getChildCount() == 0) { - return 0; - } - return (int) (getActualPaddingStart() - + numMaxIcons * mIconSize - + mOverflowWidth - + getActualPaddingEnd()); + return 0f; + } + final float contentWidth = numIcons <= MAX_ICONS_ON_LOCKSCREEN + 1 + ? numIcons * mIconSize + : MAX_ICONS_ON_LOCKSCREEN * mIconSize + (float) mOverflowWidth; + return getActualPaddingStart() + + contentWidth + + getActualPaddingEnd(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java index c68d39b97355..3811689a2295 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java @@ -22,7 +22,6 @@ import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; -import com.android.systemui.Dependency; import com.android.systemui.plugins.NotificationListenerController; import com.android.systemui.plugins.NotificationListenerController.NotificationProvider; import com.android.systemui.plugins.PluginListener; @@ -30,6 +29,8 @@ import com.android.systemui.shared.plugins.PluginManager; import java.util.ArrayList; +import javax.inject.Inject; + /** * A version of NotificationListenerService that passes all info to * any plugins connected. Also allows those plugins the chance to cancel @@ -40,19 +41,25 @@ public class NotificationListenerWithPlugins extends NotificationListenerService private ArrayList<NotificationListenerController> mPlugins = new ArrayList<>(); private boolean mConnected; + private PluginManager mPluginManager; + + @Inject + public NotificationListenerWithPlugins(PluginManager pluginManager) { + super(); + mPluginManager = pluginManager; + } @Override public void registerAsSystemService(Context context, ComponentName componentName, int currentUser) throws RemoteException { super.registerAsSystemService(context, componentName, currentUser); - Dependency.get(PluginManager.class).addPluginListener(this, - NotificationListenerController.class); + mPluginManager.addPluginListener(this, NotificationListenerController.class); } @Override public void unregisterAsSystemService() throws RemoteException { super.unregisterAsSystemService(); - Dependency.get(PluginManager.class).removePluginListener(this); + mPluginManager.removePluginListener(this); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 62a96ad81434..d93c013bd034 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -113,7 +113,6 @@ import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.DejankUtils; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.Interpolators; @@ -147,12 +146,10 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.FalsingManager.FalsingTapListener; -import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.qrcodescanner.controller.QRCodeScannerController; -import com.android.systemui.qs.QSDetailDisplayer; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.CommandQueue; @@ -326,7 +323,6 @@ public class NotificationPanelViewController extends PanelViewController private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory; private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; private final IdleViewComponent.Factory mIdleViewComponentFactory; - private final QSDetailDisplayer mQSDetailDisplayer; private final FragmentService mFragmentService; private final ScrimController mScrimController; private final PrivacyDotViewController mPrivacyDotViewController; @@ -774,7 +770,6 @@ public class NotificationPanelViewController extends PanelViewController CommunalViewComponent.Factory communalViewComponentFactory, IdleViewComponent.Factory idleViewComponentFactory, LockscreenShadeTransitionController lockscreenShadeTransitionController, - QSDetailDisplayer qsDetailDisplayer, NotificationGroupManagerLegacy groupManager, NotificationIconAreaController notificationIconAreaController, AuthController authController, @@ -804,6 +799,7 @@ public class NotificationPanelViewController extends PanelViewController ControlsComponent controlsComponent, InteractionJankMonitor interactionJankMonitor, QsFrameTranslateController qsFrameTranslateController, + SysUiState sysUiState, KeyguardUnlockAnimationController keyguardUnlockAnimationController) { super(view, falsingManager, @@ -848,7 +844,6 @@ public class NotificationPanelViewController extends PanelViewController mContentResolver = contentResolver; mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory; mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory; - mQSDetailDisplayer = qsDetailDisplayer; mFragmentService = fragmentService; mSettingsChangeObserver = new SettingsChangeObserver(handler); mShouldUseSplitNotificationShade = @@ -876,8 +871,7 @@ public class NotificationPanelViewController extends PanelViewController mUiExecutor = uiExecutor; mSecureSettings = secureSettings; mInteractionJankMonitor = interactionJankMonitor; - // TODO: inject via dagger instead of Dependency - mSysUiState = Dependency.get(SysUiState.class); + mSysUiState = sysUiState; pulseExpansionHandler.setPulseExpandAbortListener(() -> { if (mQs != null) { mQs.animateHeaderSlidingOut(); @@ -1137,7 +1131,6 @@ public class NotificationPanelViewController extends PanelViewController mKeyguardQsUserSwitchComponentFactory.build(userAvatarView); mKeyguardQsUserSwitchController = userSwitcherComponent.getKeyguardQsUserSwitchController(); - mKeyguardQsUserSwitchController.setNotificationPanelViewController(this); mKeyguardQsUserSwitchController.init(); mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(true); } else if (keyguardUserSwitcherView != null) { @@ -1849,18 +1842,6 @@ public class NotificationPanelViewController extends PanelViewController } } - public void expandWithQsDetail(DetailAdapter qsDetailAdapter) { - traceQsJank(true /* startTracing */, false /* wasCancelled */); - flingSettings(0 /* velocity */, FLING_EXPAND); - // When expanding with a panel, there's no meaningful touch point to correspond to. Set the - // origin to somewhere above the screen. This is used for animations. - int x = mQsFrame.getWidth() / 2; - mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, x, -getHeight()); - if (mAccessibilityManager.isEnabled()) { - mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle()); - } - } - public void expandWithoutQs() { if (isQsExpanded()) { flingSettings(0 /* velocity */, FLING_COLLAPSE); 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 716e8c728070..f13334e55555 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1110,6 +1110,12 @@ public class StatusBar extends CoreStartable implements } private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) { + Trace.beginSection("StatusBar#onFoldedStateChanged"); + onFoldedStateChangedInternal(isFolded, willGoToSleep); + Trace.endSection(); + } + + private void onFoldedStateChangedInternal(boolean isFolded, boolean willGoToSleep) { // Folded state changes are followed by a screen off event. // By default turning off the screen also closes the shade. // We want to make sure that the shade status is kept after @@ -3672,6 +3678,7 @@ public class StatusBar extends CoreStartable implements @Override public void onScreenTurnedOff() { + Trace.beginSection("StatusBar#onScreenTurnedOff"); mFalsingCollector.onScreenOff(); mScrimController.onScreenTurnedOff(); if (mCloseQsBeforeScreenOff) { @@ -3679,6 +3686,7 @@ public class StatusBar extends CoreStartable implements mCloseQsBeforeScreenOff = false; } updateIsKeyguard(); + Trace.endSection(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java index cf9a5dba0320..40819623c212 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java @@ -235,11 +235,6 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { // Settings are not available in setup if (!mDeviceProvisionedController.isCurrentUserSetup()) return; - - QSPanelController qsPanelController = mStatusBar.getQSPanelController(); - if (subPanel != null && qsPanelController != null) { - qsPanelController.openDetails(subPanel); - } mNotificationPanelViewController.expandWithQs(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java index 88a7dc7bcd75..c5d3937173e7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -27,7 +27,6 @@ import android.util.Log; import android.view.ViewGroup; import com.android.internal.statusbar.StatusBarIcon; -import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; @@ -74,17 +73,19 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu Context context, CommandQueue commandQueue, DemoModeController demoModeController, + ConfigurationController configurationController, + TunerService tunerService, DumpManager dumpManager) { super(context.getResources().getStringArray( com.android.internal.R.array.config_statusBarIcons)); - Dependency.get(ConfigurationController.class).addCallback(this); + configurationController.addCallback(this); mContext = context; loadDimens(); commandQueue.addCallback(this); - Dependency.get(TunerService.class).addTunable(this, ICON_HIDE_LIST); + tunerService.addTunable(this, ICON_HIDE_LIST); demoModeController.addCallback(this); dumpManager.registerDumpable(getClass().getSimpleName(), this); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index d6fc0a426590..6e1ec9ce703a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -43,7 +43,6 @@ import androidx.annotation.Nullable; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.statusbar.policy.KeyguardStateController; /** * Base class for dialogs that should appear over panels and keyguard. @@ -220,10 +219,13 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh } } - public static void setWindowOnTop(Dialog dialog) { + /** + * Ensure the window type is set properly to show over all other screens + */ + public static void setWindowOnTop(Dialog dialog, boolean isKeyguardShowing) { final Window window = dialog.getWindow(); window.setType(LayoutParams.TYPE_STATUS_BAR_SUB_PANEL); - if (Dependency.get(KeyguardStateController.class).isShowing()) { + if (isKeyguardShowing) { window.getAttributes().setFitInsetsTypes( window.getAttributes().getFitInsetsTypes() & ~Type.statusBars()); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java deleted file mode 100644 index 79d72b3d0f65..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneDependenciesModule.java +++ /dev/null @@ -1,42 +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.phone.dagger; - -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.statusbar.notification.row.RowContentBindStage; -import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper; -import com.android.systemui.statusbar.phone.StatusBar; - -import dagger.Module; -import dagger.Provides; - -/** - * This module provides instances needed to construct {@link StatusBar}. These are moved to this - * separate from {@link StatusBarPhoneModule} module so that components that wish to build their own - * version of StatusBar can include just dependencies, without injecting StatusBar itself. - */ -@Module -public interface StatusBarPhoneDependenciesModule { - - /** */ - @SysUISingleton - @Provides - static NotificationGroupAlertTransferHelper provideNotificationGroupAlertTransferHelper( - RowContentBindStage bindStage) { - return new NotificationGroupAlertTransferHelper(bindStage); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index d3ff4a78c893..83bdd1b7884c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -129,7 +129,7 @@ import dagger.Provides; /** * Dagger Module providing {@link StatusBar}. */ -@Module(includes = {StatusBarPhoneDependenciesModule.class}) +@Module public interface StatusBarPhoneModule { /** * Provides our instance of StatusBar which is considered optional. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java index e2dc9057e49d..d5f5362eaf3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java @@ -21,6 +21,7 @@ import android.view.View; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.dagger.qualifiers.RootView; +import com.android.systemui.statusbar.HeadsUpStatusBarView; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarView; @@ -32,6 +33,8 @@ import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherCo import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.window.StatusBarWindowController; +import java.util.Optional; + import javax.inject.Named; import dagger.Binds; @@ -44,6 +47,7 @@ public interface StatusBarFragmentModule { String LIGHTS_OUT_NOTIF_VIEW = "lights_out_notif_view"; String OPERATOR_NAME_VIEW = "operator_name_view"; + String OPERATOR_NAME_FRAME_VIEW = "operator_name_frame_view"; /** */ @Provides @@ -80,6 +84,14 @@ public interface StatusBarFragmentModule { /** */ @Provides @StatusBarFragmentScope + @Named(OPERATOR_NAME_FRAME_VIEW) + static Optional<View> provideOperatorFrameNameView(@RootView PhoneStatusBarView view) { + return Optional.ofNullable(view.findViewById(R.id.operator_name_frame)); + } + + /** */ + @Provides + @StatusBarFragmentScope static Clock provideClock(@RootView PhoneStatusBarView view) { return view.findViewById(R.id.clock); } @@ -119,4 +131,11 @@ public interface StatusBarFragmentModule { ) { return new PhoneStatusBarTransitions(view, statusBarWindowController.getBackgroundView()); } + + /** */ + @Provides + @StatusBarFragmentScope + static HeadsUpStatusBarView providesHeasdUpStatusBarView(@RootView PhoneStatusBarView view) { + return view.findViewById(R.id.heads_up_status_bar_view); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt index 2dbc19c653f7..b0f762994c0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt @@ -19,12 +19,19 @@ package com.android.systemui.statusbar.phone.userswitcher import android.graphics.drawable.Drawable import android.os.UserManager -import com.android.systemui.DejankUtils.whitelistIpcs +import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.policy.CallbackController import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener +import java.io.FileDescriptor +import java.io.PrintWriter +import java.util.concurrent.Executor + import javax.inject.Inject /** @@ -34,8 +41,11 @@ import javax.inject.Inject @SysUISingleton class StatusBarUserInfoTracker @Inject constructor( private val userInfoController: UserInfoController, - private val userManager: UserManager -) : CallbackController<CurrentUserChipInfoUpdatedListener> { + private val userManager: UserManager, + private val dumpManager: DumpManager, + @Main private val mainExecutor: Executor, + @Background private val backgroundExecutor: Executor +) : CallbackController<CurrentUserChipInfoUpdatedListener>, Dumpable { var currentUserName: String? = null private set var currentUserAvatar: Drawable? = null @@ -53,7 +63,7 @@ class StatusBarUserInfoTracker @Inject constructor( } init { - startListening() + dumpManager.registerDumpable(TAG, this) } override fun addCallback(listener: CurrentUserChipInfoUpdatedListener) { @@ -96,27 +106,33 @@ class StatusBarUserInfoTracker @Inject constructor( userInfoController.removeCallback(userInfoChangedListener) } - private fun checkUserSwitcherEnabled() { - whitelistIpcs { - userSwitcherEnabled = userManager.isUserSwitcherEnabled - } - } - /** * Force a check to [UserManager.isUserSwitcherEnabled], and update listeners if the value has * changed */ fun checkEnabled() { - val wasEnabled = userSwitcherEnabled - checkUserSwitcherEnabled() + backgroundExecutor.execute { + // Check on a background thread to avoid main thread Binder calls + val wasEnabled = userSwitcherEnabled + userSwitcherEnabled = userManager.isUserSwitcherEnabled - if (wasEnabled != userSwitcherEnabled) { - notifyListenersSettingChanged() + if (wasEnabled != userSwitcherEnabled) { + mainExecutor.execute { + notifyListenersSettingChanged() + } + } } } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { + pw.println(" userSwitcherEnabled=$userSwitcherEnabled") + pw.println(" listening=$listening") + } } interface CurrentUserChipInfoUpdatedListener { fun onCurrentUserChipInfoUpdated() fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {} } + +private const val TAG = "StatusBarUserInfoTracker" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt index a1247539c660..909261f0eb7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt @@ -16,9 +16,15 @@ package com.android.systemui.statusbar.phone.userswitcher +import android.content.Intent import android.view.View +import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.user.UserSwitchDialogController +import com.android.systemui.user.UserSwitcherActivity import com.android.systemui.util.ViewController import javax.inject.Inject @@ -30,7 +36,9 @@ class StatusBarUserSwitcherControllerImpl @Inject constructor( view: StatusBarUserSwitcherContainer, private val tracker: StatusBarUserInfoTracker, private val featureController: StatusBarUserSwitcherFeatureController, - private val userSwitcherDialogController: UserSwitchDialogController + private val userSwitcherDialogController: UserSwitchDialogController, + private val featureFlags: FeatureFlags, + private val activityStarter: ActivityStarter ) : ViewController<StatusBarUserSwitcherContainer>(view), StatusBarUserSwitcherController { private val listener = object : CurrentUserChipInfoUpdatedListener { @@ -52,8 +60,17 @@ class StatusBarUserSwitcherControllerImpl @Inject constructor( override fun onViewAttached() { tracker.addCallback(listener) featureController.addCallback(featureFlagListener) - mView.setOnClickListener { - userSwitcherDialogController.showDialog(it) + mView.setOnClickListener { view: View -> + if (featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) { + val intent = Intent(context, UserSwitcherActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + + activityStarter.startActivity(intent, true /* dismissShade */, + ActivityLaunchAnimator.Controller.fromView(view, null), + true /* showOverlockscreenwhenlocked */) + } else { + userSwitcherDialogController.showDialog(view) + } } updateEnabled() 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 97d344ad6b63..562816fbe21f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -56,6 +56,7 @@ import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Calendar; import java.util.Locale; import java.util.TimeZone; @@ -314,8 +315,8 @@ public class Clock extends TextView implements } @Override - public void onDarkChanged(Rect area, float darkIntensity, int tint) { - mNonAdaptedColor = DarkIconDispatcher.getTint(area, this, tint); + public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) { + mNonAdaptedColor = DarkIconDispatcher.getTint(areas, this, tint); setTextColor(mNonAdaptedColor); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java index d90363957aa9..1d414745e6ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java @@ -21,6 +21,7 @@ import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED import android.annotation.Nullable; import android.hardware.devicestate.DeviceStateManager; +import android.os.Trace; import android.util.Log; import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; @@ -117,11 +118,16 @@ public final class DeviceStateRotationLockSettingController private void updateDeviceState(int state) { Log.v(TAG, "updateDeviceState [state=" + state + "]"); - if (mDeviceState == state) { - return; - } + Trace.beginSection("updateDeviceState [state=" + state + "]"); + try { + if (mDeviceState == state) { + return; + } - readPersistedSetting(state); + readPersistedSetting(state); + } finally { + Trace.endSection(); + } } private void readPersistedSetting(int state) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index b591545ecb77..7e2488f1dfab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -37,12 +37,9 @@ import com.android.settingslib.drawable.CircleFramedDrawable; import com.android.systemui.R; import com.android.systemui.communal.CommunalStateController; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.qs.tiles.UserDetailView; import com.android.systemui.qs.user.UserSwitchDialogController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.AnimatableProperty; @@ -51,13 +48,11 @@ import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.UserAvatarView; import com.android.systemui.util.ViewController; import javax.inject.Inject; -import javax.inject.Provider; /** * Manages the user switch on the Keyguard that is used for opening the QS user panel. @@ -81,11 +76,8 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> protected final SysuiStatusBarStateController mStatusBarStateController; private final ConfigurationController mConfigurationController; private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; - private final KeyguardUserDetailAdapter mUserDetailAdapter; - private final FeatureFlags mFeatureFlags; private final UserSwitchDialogController mUserSwitchDialogController; private final UiEventLogger mUiEventLogger; - private NotificationPanelViewController mNotificationPanelViewController; private UserAvatarView mUserAvatarView; UserSwitcherController.UserRecord mCurrentUser; @@ -133,9 +125,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> ConfigurationController configurationController, SysuiStatusBarStateController statusBarStateController, DozeParameters dozeParameters, - Provider<UserDetailView.Adapter> userDetailViewAdapterProvider, ScreenOffAnimationController screenOffAnimationController, - FeatureFlags featureFlags, UserSwitchDialogController userSwitchDialogController, UiEventLogger uiEventLogger) { super(view); @@ -152,8 +142,6 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ false, /* visibleOnCommunal= */ false); - mUserDetailAdapter = new KeyguardUserDetailAdapter(context, userDetailViewAdapterProvider); - mFeatureFlags = featureFlags; mUserSwitchDialogController = userSwitchDialogController; mUiEventLogger = uiEventLogger; } @@ -182,11 +170,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> mUiEventLogger.log( LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP); - if (mFeatureFlags.isEnabled(Flags.NEW_USER_SWITCHER)) { - mUserSwitchDialogController.showDialog(mView); - } else { - openQsUserPanel(); - } + mUserSwitchDialogController.showDialog(mView); }); mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() { @@ -331,40 +315,4 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> private boolean isListAnimating() { return mKeyguardVisibilityHelper.isVisibilityAnimating(); } - - private void openQsUserPanel() { - mNotificationPanelViewController.expandWithQsDetail(mUserDetailAdapter); - } - - public void setNotificationPanelViewController( - NotificationPanelViewController notificationPanelViewController) { - mNotificationPanelViewController = notificationPanelViewController; - } - - class KeyguardUserDetailAdapter extends UserSwitcherController.UserDetailAdapter { - KeyguardUserDetailAdapter(Context context, - Provider<UserDetailView.Adapter> userDetailViewAdapterProvider) { - super(context, userDetailViewAdapterProvider); - } - - @Override - public boolean shouldAnimate() { - return false; - } - - @Override - public int getDoneText() { - return R.string.quick_settings_close_user_panel; - } - - @Override - public boolean onDoneButtonClicked() { - if (mNotificationPanelViewController != null) { - mNotificationPanelViewController.animateCloseQs(true /* animateAway */); - return true; - } else { - return false; - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 3ece240bc576..f4e53e22b02c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -25,7 +25,6 @@ import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; import android.app.IActivityManager; -import android.app.IActivityTaskManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -39,18 +38,17 @@ import android.graphics.ColorFilter; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.drawable.Drawable; -import android.os.AsyncTask; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.telephony.TelephonyCallback; +import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.View; -import android.view.ViewGroup; import android.view.WindowManagerGlobal; import android.widget.BaseAdapter; @@ -59,7 +57,6 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.LatencyTracker; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.systemui.Dumpable; @@ -76,16 +73,17 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.qs.QSUserSwitcherEvent; -import com.android.systemui.qs.tiles.UserDetailView; import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.CreateUserActivity; import com.android.systemui.util.settings.SecureSettings; +import dagger.Lazy; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -95,7 +93,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; -import javax.inject.Provider; /** * Keeps a list of all users on the device for user switching. @@ -129,10 +126,10 @@ public class UserSwitcherController implements Dumpable { private final ActivityStarter mActivityStarter; private final BroadcastDispatcher mBroadcastDispatcher; private final TelephonyListenerManager mTelephonyListenerManager; - private final IActivityTaskManager mActivityTaskManager; private final InteractionJankMonitor mInteractionJankMonitor; private final LatencyTracker mLatencyTracker; private final DialogLaunchAnimator mDialogLaunchAnimator; + private final Lazy<ShadeController> mShadeController; private ArrayList<UserRecord> mUsers = new ArrayList<>(); @VisibleForTesting @@ -152,13 +149,14 @@ public class UserSwitcherController implements Dumpable { private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2); private final UiEventLogger mUiEventLogger; private final IActivityManager mActivityManager; - public final DetailAdapter mUserDetailAdapter; private final Executor mBgExecutor; + private final Executor mUiExecutor; private final boolean mGuestUserAutoCreated; private final AtomicBoolean mGuestIsResetting; private final AtomicBoolean mGuestCreationScheduled; private FalsingManager mFalsingManager; private View mView; + private String mCreateSupervisedUserPackage; @Inject public UserSwitcherController(Context context, @@ -174,28 +172,27 @@ public class UserSwitcherController implements Dumpable { UiEventLogger uiEventLogger, FalsingManager falsingManager, TelephonyListenerManager telephonyListenerManager, - IActivityTaskManager activityTaskManager, - UserDetailAdapter userDetailAdapter, SecureSettings secureSettings, @Background Executor bgExecutor, + @Main Executor uiExecutor, InteractionJankMonitor interactionJankMonitor, LatencyTracker latencyTracker, DumpManager dumpManager, + Lazy<ShadeController> shadeController, DialogLaunchAnimator dialogLaunchAnimator) { mContext = context; mActivityManager = activityManager; mUserTracker = userTracker; mBroadcastDispatcher = broadcastDispatcher; mTelephonyListenerManager = telephonyListenerManager; - mActivityTaskManager = activityTaskManager; mUiEventLogger = uiEventLogger; mFalsingManager = falsingManager; mInteractionJankMonitor = interactionJankMonitor; mLatencyTracker = latencyTracker; mGuestResumeSessionReceiver = new GuestResumeSessionReceiver( this, mUserTracker, mUiEventLogger, secureSettings); - mUserDetailAdapter = userDetailAdapter; mBgExecutor = bgExecutor; + mUiExecutor = uiExecutor; if (!UserManager.isGuestUserEphemeral()) { mGuestResumeSessionReceiver.register(mBroadcastDispatcher); } @@ -210,6 +207,7 @@ public class UserSwitcherController implements Dumpable { mActivityStarter = activityStarter; mUserManager = userManager; mDialogLaunchAnimator = dialogLaunchAnimator; + mShadeController = shadeController; IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_ADDED); @@ -255,6 +253,9 @@ public class UserSwitcherController implements Dumpable { keyguardStateController.addCallback(mCallback); listenForCallState(); + mCreateSupervisedUserPackage = mContext.getString( + com.android.internal.R.string.config_supervisedUserCreationPackage); + dumpManager.registerDumpable(getClass().getSimpleName(), this); refreshUsers(UserHandle.USER_NULL); @@ -292,115 +293,134 @@ public class UserSwitcherController implements Dumpable { mForcePictureLoadForUserId.clear(); final boolean addUsersWhenLocked = mAddUsersFromLockScreen; - new AsyncTask<SparseArray<Bitmap>, Void, ArrayList<UserRecord>>() { - @SuppressWarnings("unchecked") - @Override - protected ArrayList<UserRecord> doInBackground(SparseArray<Bitmap>... params) { - final SparseArray<Bitmap> bitmaps = params[0]; - List<UserInfo> infos = mUserManager.getAliveUsers(); - if (infos == null) { - return null; - } - ArrayList<UserRecord> records = new ArrayList<>(infos.size()); - int currentId = mUserTracker.getUserId(); - // Check user switchability of the foreground user since SystemUI is running in - // User 0 - boolean canSwitchUsers = mUserManager.getUserSwitchability( - UserHandle.of(mUserTracker.getUserId())) == SWITCHABILITY_STATUS_OK; - UserInfo currentUserInfo = null; - UserRecord guestRecord = null; - - for (UserInfo info : infos) { - boolean isCurrent = currentId == info.id; - if (isCurrent) { - currentUserInfo = info; - } - boolean switchToEnabled = canSwitchUsers || isCurrent; - if (info.isEnabled()) { - if (info.isGuest()) { - // Tapping guest icon triggers remove and a user switch therefore - // the icon shouldn't be enabled even if the user is current - guestRecord = new UserRecord(info, null /* picture */, - true /* isGuest */, isCurrent, false /* isAddUser */, - false /* isRestricted */, canSwitchUsers); - } else if (info.supportsSwitchToByUser()) { - Bitmap picture = bitmaps.get(info.id); - if (picture == null) { - picture = mUserManager.getUserIcon(info.id); - - if (picture != null) { - int avatarSize = mContext.getResources() - .getDimensionPixelSize(R.dimen.max_avatar_size); - picture = Bitmap.createScaledBitmap( - picture, avatarSize, avatarSize, true); - } + mBgExecutor.execute(() -> { + List<UserInfo> infos = mUserManager.getAliveUsers(); + if (infos == null) { + return; + } + ArrayList<UserRecord> records = new ArrayList<>(infos.size()); + int currentId = mUserTracker.getUserId(); + // Check user switchability of the foreground user since SystemUI is running in + // User 0 + boolean canSwitchUsers = mUserManager.getUserSwitchability( + UserHandle.of(mUserTracker.getUserId())) == SWITCHABILITY_STATUS_OK; + UserRecord guestRecord = null; + + for (UserInfo info : infos) { + boolean isCurrent = currentId == info.id; + boolean switchToEnabled = canSwitchUsers || isCurrent; + if (info.isEnabled()) { + if (info.isGuest()) { + // Tapping guest icon triggers remove and a user switch therefore + // the icon shouldn't be enabled even if the user is current + guestRecord = new UserRecord(info, null /* picture */, + true /* isGuest */, isCurrent, false /* isAddUser */, + false /* isRestricted */, canSwitchUsers, + false /* isAddSupervisedUser */); + } else if (info.supportsSwitchToByUser()) { + Bitmap picture = bitmaps.get(info.id); + if (picture == null) { + picture = mUserManager.getUserIcon(info.id); + + if (picture != null) { + int avatarSize = mContext.getResources() + .getDimensionPixelSize(R.dimen.max_avatar_size); + picture = Bitmap.createScaledBitmap( + picture, avatarSize, avatarSize, true); } - records.add(new UserRecord(info, picture, false /* isGuest */, - isCurrent, false /* isAddUser */, false /* isRestricted */, - switchToEnabled)); } + records.add(new UserRecord(info, picture, false /* isGuest */, + isCurrent, false /* isAddUser */, false /* isRestricted */, + switchToEnabled, false /* isAddSupervisedUser */)); } } - if (records.size() > 1 || guestRecord != null) { - Prefs.putBoolean(mContext, Key.SEEN_MULTI_USER, true); - } + } + if (records.size() > 1 || guestRecord != null) { + Prefs.putBoolean(mContext, Key.SEEN_MULTI_USER, true); + } - boolean systemCanCreateUsers = !mUserManager.hasBaseUserRestriction( - UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM); - boolean currentUserCanCreateUsers = currentUserInfo != null - && (currentUserInfo.isAdmin() - || currentUserInfo.id == UserHandle.USER_SYSTEM) - && systemCanCreateUsers; - boolean anyoneCanCreateUsers = systemCanCreateUsers && addUsersWhenLocked; - boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers) - && guestRecord == null; - boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers) - && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY); - boolean createIsRestricted = !addUsersWhenLocked; - - if (guestRecord == null) { - if (mGuestUserAutoCreated) { - // If mGuestIsResetting=true, the switch should be disabled since - // we will just use it as an indicator for "Resetting guest...". - // Otherwise, default to canSwitchUsers. - boolean isSwitchToGuestEnabled = - !mGuestIsResetting.get() && canSwitchUsers; - guestRecord = new UserRecord(null /* info */, null /* picture */, - true /* isGuest */, false /* isCurrent */, - false /* isAddUser */, false /* isRestricted */, - isSwitchToGuestEnabled); - checkIfAddUserDisallowedByAdminOnly(guestRecord); - records.add(guestRecord); - } else if (canCreateGuest) { - guestRecord = new UserRecord(null /* info */, null /* picture */, - true /* isGuest */, false /* isCurrent */, - false /* isAddUser */, createIsRestricted, canSwitchUsers); - checkIfAddUserDisallowedByAdminOnly(guestRecord); - records.add(guestRecord); - } - } else { + if (guestRecord == null) { + if (mGuestUserAutoCreated) { + // If mGuestIsResetting=true, the switch should be disabled since + // we will just use it as an indicator for "Resetting guest...". + // Otherwise, default to canSwitchUsers. + boolean isSwitchToGuestEnabled = !mGuestIsResetting.get() && canSwitchUsers; + guestRecord = new UserRecord(null /* info */, null /* picture */, + true /* isGuest */, false /* isCurrent */, + false /* isAddUser */, false /* isRestricted */, + isSwitchToGuestEnabled, false /* isAddSupervisedUser */); + checkIfAddUserDisallowedByAdminOnly(guestRecord); + records.add(guestRecord); + } else if (canCreateGuest(guestRecord != null)) { + guestRecord = new UserRecord(null /* info */, null /* picture */, + true /* isGuest */, false /* isCurrent */, + false /* isAddUser */, createIsRestricted(), canSwitchUsers, + false /* isAddSupervisedUser */); + checkIfAddUserDisallowedByAdminOnly(guestRecord); records.add(guestRecord); } + } else { + records.add(guestRecord); + } - if (canCreateUser) { - UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */, - false /* isGuest */, false /* isCurrent */, true /* isAddUser */, - createIsRestricted, canSwitchUsers); - checkIfAddUserDisallowedByAdminOnly(addUserRecord); - records.add(addUserRecord); - } + if (canCreateUser()) { + UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */, + false /* isGuest */, false /* isCurrent */, true /* isAddUser */, + createIsRestricted(), canSwitchUsers, + false /* isAddSupervisedUser */); + checkIfAddUserDisallowedByAdminOnly(addUserRecord); + records.add(addUserRecord); + } - return records; + if (canCreateSupervisedUser()) { + UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */, + false /* isGuest */, false /* isCurrent */, false /* isAddUser */, + createIsRestricted(), canSwitchUsers, true /* isAddSupervisedUser */); + checkIfAddUserDisallowedByAdminOnly(addUserRecord); + records.add(addUserRecord); } - @Override - protected void onPostExecute(ArrayList<UserRecord> userRecords) { - if (userRecords != null) { - mUsers = userRecords; + mUiExecutor.execute(() -> { + if (records != null) { + mUsers = records; notifyAdapters(); } - } - }.execute((SparseArray) bitmaps); + }); + }); + } + + boolean systemCanCreateUsers() { + return !mUserManager.hasBaseUserRestriction( + UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM); + } + + boolean currentUserCanCreateUsers() { + UserInfo currentUser = mUserTracker.getUserInfo(); + return currentUser != null + && (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM) + && systemCanCreateUsers(); + } + + boolean anyoneCanCreateUsers() { + return systemCanCreateUsers() && mAddUsersFromLockScreen; + } + + boolean canCreateGuest(boolean hasExistingGuest) { + return (currentUserCanCreateUsers() || anyoneCanCreateUsers()) + && !hasExistingGuest; + } + + boolean canCreateUser() { + return (currentUserCanCreateUsers() || anyoneCanCreateUsers()) + && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY); + } + + boolean createIsRestricted() { + return !mAddUsersFromLockScreen; + } + + boolean canCreateSupervisedUser() { + return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser(); } private void pauseRefreshUsers() { @@ -485,6 +505,9 @@ public class UserSwitcherController implements Dumpable { } else if (record.isAddUser) { showAddUserDialog(dialogShower); return; + } else if (record.isAddSupervisedUser) { + startSupervisedUserActivity(); + return; } else { id = record.info.id; } @@ -561,6 +584,22 @@ public class UserSwitcherController implements Dumpable { } } + private void startSupervisedUserActivity() { + final Intent intent = new Intent() + .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER) + .setPackage(mCreateSupervisedUserPackage) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // TODO(b/209659998): [to-be-removed] fallback activity for supervised user creation. + if (mContext.getPackageManager().resolveActivity(intent, 0) == null) { + intent.setPackage(null) + .setClassName("com.android.settings", + "com.android.settings.users.AddSupervisedUserActivity"); + } + + mContext.startActivity(intent); + } + private void listenForCallState() { mTelephonyListenerManager.addCallStateListener(mPhoneStateListener); } @@ -941,6 +980,8 @@ public class UserSwitcherController implements Dumpable { } } else if (item.isAddUser) { return context.getString(R.string.user_add_user); + } else if (item.isAddSupervisedUser) { + return context.getString(R.string.add_user_supervised); } else { return item.info.name; } @@ -955,9 +996,11 @@ public class UserSwitcherController implements Dumpable { protected static Drawable getIconDrawable(Context context, UserRecord item) { int iconRes; if (item.isAddUser) { - iconRes = R.drawable.ic_add_circle; + iconRes = R.drawable.ic_account_circle; } else if (item.isGuest) { - iconRes = R.drawable.ic_avatar_guest_user; + iconRes = R.drawable.ic_account_circle_filled; + } else if (item.isAddSupervisedUser) { + iconRes = R.drawable.ic_add_supervised_user; } else { iconRes = R.drawable.ic_avatar_user; } @@ -1000,6 +1043,7 @@ public class UserSwitcherController implements Dumpable { public final boolean isGuest; public final boolean isCurrent; public final boolean isAddUser; + public final boolean isAddSupervisedUser; /** If true, the record is only visible to the owner and only when unlocked. */ public final boolean isRestricted; public boolean isDisabledByAdmin; @@ -1007,7 +1051,8 @@ public class UserSwitcherController implements Dumpable { public boolean isSwitchToEnabled; public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent, - boolean isAddUser, boolean isRestricted, boolean isSwitchToEnabled) { + boolean isAddUser, boolean isRestricted, boolean isSwitchToEnabled, + boolean isAddSupervisedUser) { this.info = info; this.picture = picture; this.isGuest = isGuest; @@ -1015,11 +1060,12 @@ public class UserSwitcherController implements Dumpable { this.isAddUser = isAddUser; this.isRestricted = isRestricted; this.isSwitchToEnabled = isSwitchToEnabled; + this.isAddSupervisedUser = isAddSupervisedUser; } public UserRecord copyWithIsCurrent(boolean _isCurrent) { return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted, - isSwitchToEnabled); + isSwitchToEnabled, isAddSupervisedUser); } public int resolveId() { @@ -1043,6 +1089,7 @@ public class UserSwitcherController implements Dumpable { } if (isGuest) sb.append(" <isGuest>"); if (isAddUser) sb.append(" <isAddUser>"); + if (isAddSupervisedUser) sb.append(" <isAddSupervisedUser>"); if (isCurrent) sb.append(" <isCurrent>"); if (picture != null) sb.append(" <hasPicture>"); if (isRestricted) sb.append(" <isRestricted>"); @@ -1058,77 +1105,6 @@ public class UserSwitcherController implements Dumpable { } } - public static class UserDetailAdapter implements DetailAdapter { - private final Intent USER_SETTINGS_INTENT = new Intent(Settings.ACTION_USER_SETTINGS); - - private final Context mContext; - private final Provider<UserDetailView.Adapter> mUserDetailViewAdapterProvider; - - @Inject - UserDetailAdapter(Context context, - Provider<UserDetailView.Adapter> userDetailViewAdapterProvider) { - mContext = context; - mUserDetailViewAdapterProvider = userDetailViewAdapterProvider; - } - - @Override - public CharSequence getTitle() { - return mContext.getString(R.string.quick_settings_user_title); - } - - @Override - public View createDetailView(Context context, View convertView, ViewGroup parent) { - UserDetailView v; - if (!(convertView instanceof UserDetailView)) { - v = UserDetailView.inflate(context, parent, false); - v.setAdapter(mUserDetailViewAdapterProvider.get()); - } else { - v = (UserDetailView) convertView; - } - v.refreshAdapter(); - return v; - } - - @Override - public Intent getSettingsIntent() { - return USER_SETTINGS_INTENT; - } - - @Override - public int getSettingsText() { - return R.string.quick_settings_more_user_settings; - } - - @Override - public Boolean getToggleState() { - return null; - } - - @Override - public void setToggleState(boolean state) { - } - - @Override - public int getMetricsCategory() { - return MetricsEvent.QS_USERDETAIL; - } - - @Override - public UiEventLogger.UiEventEnum openDetailEvent() { - return QSUserSwitcherEvent.QS_USER_DETAIL_OPEN; - } - - @Override - public UiEventLogger.UiEventEnum closeDetailEvent() { - return QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE; - } - - @Override - public UiEventLogger.UiEventEnum moreSettingsEvent() { - return QSUserSwitcherEvent.QS_USER_MORE_SETTINGS; - } - }; - private final KeyguardStateController.Callback mCallback = new KeyguardStateController.Callback() { @Override @@ -1178,7 +1154,7 @@ public class UserSwitcherController implements Dumpable { context.getString(mGuestUserAutoCreated ? com.android.settingslib.R.string.guest_reset_guest_confirm_button : R.string.guest_exit_guest_dialog_remove), this); - SystemUIDialog.setWindowOnTop(this); + SystemUIDialog.setWindowOnTop(this, mKeyguardStateController.isShowing()); setCanceledOnTouchOutside(false); mGuestId = guestId; mTargetId = targetId; @@ -1213,7 +1189,7 @@ public class UserSwitcherController implements Dumpable { context.getString(android.R.string.cancel), this); setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok), this); - SystemUIDialog.setWindowOnTop(this); + SystemUIDialog.setWindowOnTop(this, mKeyguardStateController.isShowing()); } @Override @@ -1230,33 +1206,8 @@ public class UserSwitcherController implements Dumpable { if (ActivityManager.isUserAMonkey()) { return; } - Intent intent = CreateUserActivity.createIntentForStart(getContext()); - - // There are some differences between ActivityStarter and ActivityTaskManager in - // terms of how they start an activity. ActivityStarter hides the notification bar - // before starting the activity to make sure nothing is in front of the new - // activity. ActivityStarter also tries to unlock the device if it's locked. - // When locked with PIN/pattern/password then it shows the prompt, if there are no - // security steps then it dismisses the keyguard and then starts the activity. - // ActivityTaskManager doesn't hide the notification bar or unlocks the device, but - // it can start an activity on top of the locked screen. - if (!mKeyguardStateController.isUnlocked() - && !mKeyguardStateController.canDismissLockScreen()) { - // Device is locked and can't be unlocked without a PIN/pattern/password so we - // need to use ActivityTaskManager to start the activity on top of the locked - // screen. - try { - mActivityTaskManager.startActivity(null, - mContext.getBasePackageName(), mContext.getAttributionTag(), intent, - intent.resolveTypeIfNeeded(mContext.getContentResolver()), null, - null, 0, 0, null, null); - } catch (RemoteException e) { - e.printStackTrace(); - Log.e(TAG, "Couldn't start create user activity", e); - } - } else { - mActivityStarter.startActivity(intent, true); - } + mShadeController.get().collapsePanel(); + getContext().startActivity(CreateUserActivity.createIntentForStart(getContext())); } } } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java index 52b58d451063..71355bbb23e1 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java @@ -23,6 +23,7 @@ public class TunerSwitch extends SwitchPreference implements Tunable { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TunerSwitch); mDefault = a.getBoolean(R.styleable.TunerSwitch_defValue, false); mAction = a.getInt(R.styleable.TunerSwitch_metricsAction, -1); + a.recycle(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java deleted file mode 100644 index 4d95969a63b5..000000000000 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java +++ /dev/null @@ -1,147 +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. - */ - -package com.android.systemui.tuner; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.Intent; -import android.provider.Settings; -import android.provider.Settings.Global; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.Checkable; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.systemui.Prefs; -import com.android.systemui.R; -import com.android.systemui.statusbar.policy.ZenModeController; -import com.android.systemui.volume.ZenModePanel; -import com.android.systemui.volume.ZenModePanel.Callback; - -public class TunerZenModePanel extends LinearLayout implements OnClickListener { - private static final String TAG = "TunerZenModePanel"; - - private Callback mCallback; - private ZenModePanel mZenModePanel; - private View mHeaderSwitch; - private int mZenMode; - private ZenModeController mController; - private View mButtons; - private View mMoreSettings; - private View mDone; - private OnClickListener mDoneListener; - private boolean mEditing; - - public TunerZenModePanel(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - public void init(ZenModeController zenModeController) { - mController = zenModeController; - mHeaderSwitch = findViewById(R.id.tuner_zen_switch); - mHeaderSwitch.setVisibility(View.VISIBLE); - mHeaderSwitch.setOnClickListener(this); - ((TextView) mHeaderSwitch.findViewById(android.R.id.title)).setText( - R.string.quick_settings_dnd_label); - mZenModePanel = (ZenModePanel) findViewById(R.id.zen_mode_panel); - mZenModePanel.init(zenModeController); - mButtons = findViewById(R.id.tuner_zen_buttons); - mMoreSettings = mButtons.findViewById(android.R.id.button2); - mMoreSettings.setOnClickListener(this); - ((TextView) mMoreSettings).setText(R.string.quick_settings_more_settings); - mDone = mButtons.findViewById(android.R.id.button1); - mDone.setOnClickListener(this); - ((TextView) mDone).setText(R.string.quick_settings_done); - // Hide the resizing space because it causes issues in the volume panel. - ViewGroup detail_header = findViewById(R.id.tuner_zen_switch); - detail_header.getChildAt(0).setVisibility(View.GONE); - // No background so it can blend with volume panel. - findViewById(R.id.edit_container).setBackground(null); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mEditing = false; - } - - public void setCallback(Callback zenPanelCallback) { - mCallback = zenPanelCallback; - mZenModePanel.setCallback(zenPanelCallback); - } - - @Override - public void onClick(View v) { - if (v == mHeaderSwitch) { - mEditing = true; - if (mZenMode == Global.ZEN_MODE_OFF) { - mZenMode = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, - Global.ZEN_MODE_ALARMS); - mController.setZen(mZenMode, null, TAG); - postUpdatePanel(); - } else { - mZenMode = Global.ZEN_MODE_OFF; - mController.setZen(Global.ZEN_MODE_OFF, null, TAG); - postUpdatePanel(); - } - } else if (v == mMoreSettings) { - Intent intent = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - getContext().startActivity(intent); - } else if (v == mDone) { - mEditing = false; - setVisibility(View.GONE); - mDoneListener.onClick(v); - } - } - - public boolean isEditing() { - return mEditing; - } - - public void setZenState(int zenMode) { - mZenMode = zenMode; - postUpdatePanel(); - } - - private void postUpdatePanel() { - // The complicated structure from reusing the same ZenPanel has resulted in some - // unstableness/flickering from callbacks coming in quickly. To solve this just - // post the UI updates a little bit. - removeCallbacks(mUpdate); - postDelayed(mUpdate, 40); - } - - public void setDoneListener(OnClickListener onClickListener) { - mDoneListener = onClickListener; - } - - private void updatePanel() { - boolean zenOn = mZenMode != Global.ZEN_MODE_OFF; - ((Checkable) mHeaderSwitch.findViewById(android.R.id.toggle)).setChecked(zenOn); - mZenModePanel.setVisibility(zenOn ? View.VISIBLE : View.GONE); - mButtons.setVisibility(zenOn ? View.VISIBLE : View.GONE); - } - - private final Runnable mUpdate = new Runnable() { - @Override - public void run() { - updatePanel(); - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt new file mode 100644 index 000000000000..ad8dc825dbcb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.tv + +import com.android.systemui.CoreStartable +import com.android.systemui.SliceBroadcastRelayHandler +import com.android.systemui.accessibility.WindowMagnification +import com.android.systemui.dagger.qualifiers.PerUser +import com.android.systemui.globalactions.GlobalActionsComponent +import com.android.systemui.keyboard.KeyboardUI +import com.android.systemui.media.RingtonePlayer +import com.android.systemui.media.systemsounds.HomeSoundEffectController +import com.android.systemui.power.PowerUI +import com.android.systemui.privacy.television.TvOngoingPrivacyChip +import com.android.systemui.shortcut.ShortcutKeyDispatcher +import com.android.systemui.statusbar.notification.InstantAppNotifier +import com.android.systemui.statusbar.tv.TvStatusBar +import com.android.systemui.statusbar.tv.VpnStatusObserver +import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler +import com.android.systemui.statusbar.tv.notifications.TvNotificationPanel +import com.android.systemui.toast.ToastUI +import com.android.systemui.usb.StorageNotification +import com.android.systemui.util.NotificationChannels +import com.android.systemui.volume.VolumeUI +import com.android.systemui.wmshell.WMShell +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +/** + * Collection of {@link CoreStartable}s that should be run on TV. + */ +@Module +abstract class TVSystemUICoreStartableModule { + /** Inject into GlobalActionsComponent. */ + @Binds + @IntoMap + @ClassKey(GlobalActionsComponent::class) + abstract fun bindGlobalActionsComponent(sysui: GlobalActionsComponent): CoreStartable + + /** Inject into HomeSoundEffectController. */ + @Binds + @IntoMap + @ClassKey(HomeSoundEffectController::class) + abstract fun bindHomeSoundEffectController(sysui: HomeSoundEffectController): CoreStartable + + /** Inject into InstantAppNotifier. */ + @Binds + @IntoMap + @ClassKey(InstantAppNotifier::class) + abstract fun bindInstantAppNotifier(sysui: InstantAppNotifier): CoreStartable + + /** Inject into KeyboardUI. */ + @Binds + @IntoMap + @ClassKey(KeyboardUI::class) + abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable + + /** Inject into NotificationChannels. */ + @Binds + @IntoMap + @ClassKey(NotificationChannels::class) + @PerUser + abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable + + /** Inject into PowerUI. */ + @Binds + @IntoMap + @ClassKey(PowerUI::class) + abstract fun bindPowerUI(sysui: PowerUI): CoreStartable + + /** Inject into RingtonePlayer. */ + @Binds + @IntoMap + @ClassKey(RingtonePlayer::class) + abstract fun bind(sysui: RingtonePlayer): CoreStartable + + /** Inject into ShortcutKeyDispatcher. */ + @Binds + @IntoMap + @ClassKey(ShortcutKeyDispatcher::class) + abstract fun bindShortcutKeyDispatcher(sysui: ShortcutKeyDispatcher): CoreStartable + + /** Inject into SliceBroadcastRelayHandler. */ + @Binds + @IntoMap + @ClassKey(SliceBroadcastRelayHandler::class) + abstract fun bindSliceBroadcastRelayHandler(sysui: SliceBroadcastRelayHandler): CoreStartable + + /** Inject into StorageNotification. */ + @Binds + @IntoMap + @ClassKey(StorageNotification::class) + abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable + + /** Inject into ToastUI. */ + @Binds + @IntoMap + @ClassKey(ToastUI::class) + abstract fun bindToastUI(service: ToastUI): CoreStartable + + /** Inject into TvNotificationHandler. */ + @Binds + @IntoMap + @ClassKey(TvNotificationHandler::class) + abstract fun bindTvNotificationHandler(sysui: TvNotificationHandler): CoreStartable + + /** Inject into TvNotificationPanel. */ + @Binds + @IntoMap + @ClassKey(TvNotificationPanel::class) + abstract fun bindTvNotificationPanel(sysui: TvNotificationPanel): CoreStartable + + /** Inject into TvOngoingPrivacyChip. */ + @Binds + @IntoMap + @ClassKey(TvOngoingPrivacyChip::class) + abstract fun bindTvOngoingPrivacyChip(sysui: TvOngoingPrivacyChip): CoreStartable + + /** Inject into TvStatusBar. */ + @Binds + @IntoMap + @ClassKey(TvStatusBar::class) + abstract fun bindTvStatusBar(sysui: TvStatusBar): CoreStartable + + /** Inject into VolumeUI. */ + @Binds + @IntoMap + @ClassKey(VolumeUI::class) + abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable + + /** Inject into VpnStatusObserver. */ + @Binds + @IntoMap + @ClassKey(VpnStatusObserver::class) + abstract fun bindVpnStatusObserver(sysui: VpnStatusObserver): CoreStartable + + /** Inject into WindowMagnification. */ + @Binds + @IntoMap + @ClassKey(WindowMagnification::class) + abstract fun bindWindowMagnification(sysui: WindowMagnification): CoreStartable + + /** Inject into WMShell. */ + @Binds + @IntoMap + @ClassKey(WMShell::class) + abstract fun bindWMShell(sysui: WMShell): CoreStartable +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java index bef05ebb724e..6fdce1ae4ec2 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java @@ -34,6 +34,7 @@ import dagger.Subcomponent; DependencyProvider.class, SystemUIBinder.class, SystemUIModule.class, + TVSystemUICoreStartableModule.class, TvSystemUIModule.class, TvSystemUIBinder.class}) public interface TvSysUIComponent extends SysUIComponent { diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java index d0fb91c9342a..23f37ec8dc69 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java @@ -16,28 +16,13 @@ package com.android.systemui.tv; -import com.android.systemui.CoreStartable; import com.android.systemui.dagger.GlobalRootComponent; -import com.android.systemui.statusbar.tv.VpnStatusObserver; -import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler; import dagger.Binds; import dagger.Module; -import dagger.multibindings.ClassKey; -import dagger.multibindings.IntoMap; @Module interface TvSystemUIBinder { @Binds GlobalRootComponent bindGlobalRootComponent(TvGlobalRootComponent globalRootComponent); - - @Binds - @IntoMap - @ClassKey(TvNotificationHandler.class) - CoreStartable bindTvNotificationHandler(TvNotificationHandler systemui); - - @Binds - @IntoMap - @ClassKey(VpnStatusObserver.class) - CoreStartable bindVpnStatusObserver(VpnStatusObserver systemui); } diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index cf361ec304e5..345fc99f8a54 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -46,10 +46,15 @@ import com.android.internal.R; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.CoreStartable; import com.android.systemui.SystemUIApplication; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.util.NotificationChannels; import java.util.List; +import javax.inject.Inject; + +/** */ +@SysUISingleton public class StorageNotification extends CoreStartable { private static final String TAG = "StorageNotification"; @@ -61,6 +66,7 @@ public class StorageNotification extends CoreStartable { private NotificationManager mNotificationManager; private StorageManager mStorageManager; + @Inject public StorageNotification(Context context) { super(context); } diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java index 890ee5f45309..c9de966080c5 100644 --- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java +++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java @@ -44,7 +44,9 @@ public class CreateUserActivity extends Activity { * Creates an intent to start this activity. */ public static Intent createIntentForStart(Context context) { - return new Intent(context, CreateUserActivity.class); + Intent intent = new Intent(context, CreateUserActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + return intent; } private static final String TAG = "CreateUserActivity"; diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt index d6a8ab270b84..14585fb0f58e 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt @@ -27,11 +27,10 @@ import android.graphics.drawable.InsetDrawable import android.graphics.drawable.LayerDrawable import android.os.Bundle import android.os.UserManager +import android.provider.Settings import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.WindowInsets -import android.view.WindowInsets.Type import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.ImageView @@ -45,6 +44,7 @@ import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.FalsingManager.LOW_PENALTY +import com.android.systemui.statusbar.phone.ShadeController import com.android.systemui.statusbar.policy.UserSwitcherController import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord @@ -64,15 +64,26 @@ class UserSwitcherActivity @Inject constructor( private val broadcastDispatcher: BroadcastDispatcher, private val layoutInflater: LayoutInflater, private val falsingManager: FalsingManager, - private val userManager: UserManager + private val userManager: UserManager, + private val shadeController: ShadeController ) : LifecycleActivity() { private lateinit var parent: ViewGroup private lateinit var broadcastReceiver: BroadcastReceiver private var popupMenu: UserSwitcherPopupMenu? = null private lateinit var addButton: View - private var addUserItem: UserRecord? = null - private var addGuestItem: UserRecord? = null + private var addUserRecords = mutableListOf<UserRecord>() + // When the add users options become available, insert another option to manage users + private val manageUserRecord = UserRecord( + null /* info */, + null /* picture */, + false /* isGuest */, + false /* isCurrent */, + false /* isAddUser */, + false /* isRestricted */, + false /* isSwitchToEnabled */, + false /* isAddSupervisedUser */ + ) private val adapter = object : BaseUserAdapter(userSwitcherController) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { @@ -104,7 +115,18 @@ class UserSwitcherActivity @Inject constructor( return view } + override fun getName(context: Context, item: UserRecord): String { + return if (item == manageUserRecord) { + getString(R.string.manage_users) + } else { + super.getName(context, item) + } + } + fun findUserIcon(item: UserRecord): Drawable { + if (item == manageUserRecord) { + return getDrawable(R.drawable.ic_manage_users) + } if (item.info == null) { return getIconDrawable(this@UserSwitcherActivity, item) } @@ -169,20 +191,11 @@ class UserSwitcherActivity @Inject constructor( super.onCreate(savedInstanceState) setContentView(R.layout.user_switcher_fullscreen) + window.decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE + or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) - parent = requireViewById<ViewGroup>(R.id.user_switcher_root).apply { - setOnApplyWindowInsetsListener { - v: View, insets: WindowInsets -> - v.apply { - val l = getPaddingLeft() - val t = getPaddingTop() - val r = getPaddingRight() - setPadding(l, t, r, insets.getInsets(Type.systemBars()).bottom) - } - - WindowInsets.CONSUMED - } - } + parent = requireViewById<ViewGroup>(R.id.user_switcher_root) requireViewById<View>(R.id.cancel).apply { setOnClickListener { @@ -203,15 +216,19 @@ class UserSwitcherActivity @Inject constructor( private fun showPopupMenu() { val items = mutableListOf<UserRecord>() - addUserItem?.let { items.add(it) } - addGuestItem?.let { items.add(it) } + addUserRecords.forEach { items.add(it) } var popupMenuAdapter = ItemAdapter( this, R.layout.user_switcher_fullscreen_popup_item, layoutInflater, { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item) }, - { item: UserRecord -> adapter.findUserIcon(item) } + { item: UserRecord -> adapter.findUserIcon(item).mutate().apply { + setTint(resources.getColor( + R.color.user_switcher_fullscreen_popup_item_tint, + getTheme() + )) + } } ) popupMenuAdapter.addAll(items) @@ -225,10 +242,19 @@ class UserSwitcherActivity @Inject constructor( } // -1 for the header val item = popupMenuAdapter.getItem(pos - 1) - adapter.onUserListItemClicked(item) + if (item == manageUserRecord) { + val i = Intent().setAction(Settings.ACTION_USER_SETTINGS) + this@UserSwitcherActivity.startActivity(i) + } else { + adapter.onUserListItemClicked(item) + } dismiss() popupMenu = null + + if (!item.isAddUser) { + this@UserSwitcherActivity.finish() + } } show() @@ -245,14 +271,15 @@ class UserSwitcherActivity @Inject constructor( } } parent.removeViews(start, count) + addUserRecords.clear() val flow = requireViewById<Flow>(R.id.flow) for (i in 0 until adapter.getCount()) { val item = adapter.getItem(i) - if (item.isAddUser) { - addUserItem = item - } else if (item.isGuest && item.info == null) { - addGuestItem = item + if (item.isAddUser || + item.isAddSupervisedUser || + item.isGuest && item.info == null) { + addUserRecords.add(item) } else { val userView = adapter.getView(i, null, parent) userView.setId(View.generateViewId()) @@ -273,7 +300,8 @@ class UserSwitcherActivity @Inject constructor( } } - if (addUserItem != null || addGuestItem != null) { + if (!addUserRecords.isEmpty()) { + addUserRecords.add(manageUserRecord) addButton.visibility = View.VISIBLE } else { addButton.visibility = View.GONE diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt index 896354737e46..754a9342bfb0 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt @@ -42,7 +42,7 @@ class UserSwitcherPopupMenu( setBackgroundDrawable( res.getDrawable(R.drawable.bouncer_user_switcher_popup_bg, context.getTheme()) ) - setModal(true) + setModal(false) setOverlapAnchor(true) } diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java index ce7e4cf82081..76dfcb182e97 100644 --- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java +++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java @@ -29,6 +29,9 @@ import com.android.wm.shell.pip.tv.TvPipNotificationController; import java.util.Arrays; +import javax.inject.Inject; + +// NOT Singleton. Started per-user. public class NotificationChannels extends CoreStartable { public static String ALERTS = "ALR"; public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP"; @@ -38,6 +41,7 @@ public class NotificationChannels extends CoreStartable { public static String TVPIP = TvPipNotificationController.NOTIFICATION_CHANNEL; // "TVPIP" public static String HINTS = "HNT"; + @Inject public NotificationChannels(Context context) { super(context); } diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java index 0bbf56caaaf1..db35437e77b9 100644 --- a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java +++ b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java @@ -36,6 +36,7 @@ public abstract class Condition implements CallbackController<Condition.Callback private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); private boolean mIsConditionMet = false; private boolean mStarted = false; + private boolean mOverriding = false; /** * Starts monitoring the condition. @@ -48,6 +49,21 @@ public abstract class Condition implements CallbackController<Condition.Callback protected abstract void stop(); /** + * Sets whether this condition's value overrides others in determining the overall state. + */ + public void setOverriding(boolean overriding) { + mOverriding = overriding; + updateCondition(mIsConditionMet); + } + + /** + * Returns whether the current condition overrides + */ + public boolean isOverridingCondition() { + return mOverriding; + } + + /** * Registers a callback to receive updates once started. This should be called before * {@link #start()}. Also triggers the callback immediately if already started. */ @@ -57,7 +73,7 @@ public abstract class Condition implements CallbackController<Condition.Callback mCallbacks.add(new WeakReference<>(callback)); if (mStarted) { - callback.onConditionChanged(this, mIsConditionMet); + callback.onConditionChanged(this); return; } @@ -107,11 +123,15 @@ public abstract class Condition implements CallbackController<Condition.Callback if (cb == null) { iterator.remove(); } else { - cb.onConditionChanged(this, mIsConditionMet); + cb.onConditionChanged(this); } } } + public boolean isConditionMet() { + return mIsConditionMet; + } + private boolean shouldLog() { return Log.isLoggable(mTag, Log.DEBUG); } @@ -124,8 +144,7 @@ public abstract class Condition implements CallbackController<Condition.Callback * Called when the fulfillment of the condition changes. * * @param condition The condition in question. - * @param isConditionMet True if the condition has been fulfilled. False otherwise. */ - void onConditionChanged(Condition condition, boolean isConditionMet); + void onConditionChanged(Condition condition); } } diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java index 8b6e982be55b..d3c6e9aa0da9 100644 --- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java +++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java @@ -18,15 +18,17 @@ package com.android.systemui.util.condition; import android.util.Log; -import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.statusbar.policy.CallbackController; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collection; +import java.util.HashSet; import java.util.Iterator; import java.util.Set; +import java.util.concurrent.Executor; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -41,9 +43,7 @@ public class Monitor implements CallbackController<Monitor.Callback> { // Set of all conditions that need to be monitored. private final Set<Condition> mConditions; - - // Map of values of each condition. - private final HashMap<Condition, Boolean> mConditionsMap = new HashMap<>(); + private final Executor mExecutor; // Whether all conditions have been met. private boolean mAllConditionsMet = false; @@ -52,10 +52,43 @@ public class Monitor implements CallbackController<Monitor.Callback> { private boolean mHaveConditionsStarted = false; // Callback for when each condition has been updated. - private final Condition.Callback mConditionCallback = (condition, isConditionMet) -> { - mConditionsMap.put(condition, isConditionMet); + private final Condition.Callback mConditionCallback = new Condition.Callback() { + @Override + public void onConditionChanged(Condition condition) { + mExecutor.execute(() -> updateConditionMetState()); + } + }; + + @Inject + public Monitor(Executor executor, Set<Condition> conditions, Set<Callback> callbacks) { + mConditions = new HashSet<>(); + mExecutor = executor; + + if (conditions != null) { + mConditions.addAll(conditions); + } - final boolean newAllConditionsMet = !mConditionsMap.containsValue(false); + if (callbacks == null) { + return; + } + + for (Callback callback : callbacks) { + addCallbackLocked(callback); + } + } + + private void updateConditionMetState() { + // Overriding conditions do not override each other + final Collection<Condition> overridingConditions = mConditions.stream() + .filter(Condition::isOverridingCondition).collect(Collectors.toSet()); + + final Collection<Condition> targetCollection = overridingConditions.isEmpty() + ? mConditions : overridingConditions; + + final boolean newAllConditionsMet = targetCollection.isEmpty() ? true : targetCollection + .stream() + .map(Condition::isConditionMet) + .allMatch(conditionMet -> conditionMet); if (newAllConditionsMet == mAllConditionsMet) { return; @@ -74,32 +107,48 @@ public class Monitor implements CallbackController<Monitor.Callback> { callback.onConditionsChanged(mAllConditionsMet); } } - }; + } - @Inject - public Monitor(Set<Condition> conditions, Set<Callback> callbacks) { - mConditions = conditions; + private void addConditionLocked(@NotNull Condition condition) { + mConditions.add(condition); - // If there is no condition, give green pass. - if (mConditions.isEmpty()) { - mAllConditionsMet = true; + if (!mHaveConditionsStarted) { return; } - // Initializes the conditions map and registers a callback for each condition. - mConditions.forEach((condition -> mConditionsMap.put(condition, false))); + condition.addCallback(mConditionCallback); + updateConditionMetState(); + } - if (callbacks == null) { - return; - } + /** + * Adds a condition for the monitor to listen to and consider when determining whether the + * overall condition state is met. + */ + public void addCondition(@NotNull Condition condition) { + mExecutor.execute(() -> addConditionLocked(condition)); + } - for (Callback callback : callbacks) { - addCallback(callback); - } + /** + * Removes a condition from further consideration. + */ + public void removeCondition(@NotNull Condition condition) { + mExecutor.execute(() -> { + mConditions.remove(condition); + + if (!mHaveConditionsStarted) { + return; + } + + condition.removeCallback(mConditionCallback); + updateConditionMetState(); + }); } - @Override - public void addCallback(@NotNull Callback callback) { + private void addCallbackLocked(@NotNull Callback callback) { + if (mCallbacks.contains(callback)) { + return; + } + if (shouldLog()) Log.d(mTag, "adding callback"); mCallbacks.add(callback); @@ -109,36 +158,36 @@ public class Monitor implements CallbackController<Monitor.Callback> { if (!mHaveConditionsStarted) { if (shouldLog()) Log.d(mTag, "starting all conditions"); mConditions.forEach(condition -> condition.addCallback(mConditionCallback)); + updateConditionMetState(); mHaveConditionsStarted = true; } } @Override + public void addCallback(@NotNull Callback callback) { + mExecutor.execute(() -> addCallbackLocked(callback)); + } + + @Override public void removeCallback(@NotNull Callback callback) { - if (shouldLog()) Log.d(mTag, "removing callback"); - final Iterator<Callback> iterator = mCallbacks.iterator(); - while (iterator.hasNext()) { - final Callback cb = iterator.next(); - if (cb == null || cb == callback) { - iterator.remove(); + mExecutor.execute(() -> { + if (shouldLog()) Log.d(mTag, "removing callback"); + final Iterator<Callback> iterator = mCallbacks.iterator(); + while (iterator.hasNext()) { + final Callback cb = iterator.next(); + if (cb == null || cb == callback) { + iterator.remove(); + } } - } - if (mCallbacks.isEmpty() && mHaveConditionsStarted) { - if (shouldLog()) Log.d(mTag, "stopping all conditions"); - mConditions.forEach(condition -> condition.removeCallback(mConditionCallback)); + if (mCallbacks.isEmpty() && mHaveConditionsStarted) { + if (shouldLog()) Log.d(mTag, "stopping all conditions"); + mConditions.forEach(condition -> condition.removeCallback(mConditionCallback)); - mAllConditionsMet = false; - mHaveConditionsStarted = false; - } - } - - /** - * Force updates each condition to the value provided. - */ - @VisibleForTesting - public void overrideAllConditionsMet(boolean value) { - mConditions.forEach(condition -> condition.updateCondition(value)); + mAllConditionsMet = false; + mHaveConditionsStarted = false; + } + }); } private boolean shouldLog() { diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt index b64d7bec184f..d8de07d185c6 100644 --- a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt @@ -21,6 +21,7 @@ import android.provider.Settings.Secure.CAMERA_AUTOROTATE import com.android.internal.view.RotationPolicy import com.android.internal.view.RotationPolicy.RotationPolicyListener import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.traceSection import javax.inject.Inject /** @@ -44,7 +45,9 @@ class RotationPolicyWrapperImpl @Inject constructor( RotationPolicyWrapper { override fun setRotationLock(enabled: Boolean) { - RotationPolicy.setRotationLock(context, enabled) + traceSection("RotationPolicyWrapperImpl#setRotationLock") { + RotationPolicy.setRotationLock(context, enabled) + } } override fun setRotationLockAtAngle(enabled: Boolean, rotation: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java index c083c14a3730..955d616f6729 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java @@ -22,6 +22,7 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.media.VolumePolicy; import android.os.Bundle; +import android.provider.Settings; import android.view.WindowManager.LayoutParams; import com.android.settingslib.applications.InterestingConfigChanges; @@ -59,6 +60,11 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = false; public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = false; + private static final Intent ZEN_SETTINGS = + new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); + private static final Intent ZEN_PRIORITY_SETTINGS = + new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS); + protected final Context mContext; private final VolumeDialogControllerImpl mController; private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( @@ -191,12 +197,12 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna private final VolumeDialogImpl.Callback mVolumeDialogCallback = new VolumeDialogImpl.Callback() { @Override public void onZenSettingsClicked() { - startSettings(ZenModePanel.ZEN_SETTINGS); + startSettings(ZEN_SETTINGS); } @Override public void onZenPrioritySettingsClicked() { - startSettings(ZenModePanel.ZEN_PRIORITY_SETTINGS); + startSettings(ZEN_PRIORITY_SETTINGS); } }; diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java index 7bb987ca7cf0..e69de29bb2d1 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java @@ -1,1077 +0,0 @@ -/** - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume; - -import android.animation.LayoutTransition; -import android.animation.LayoutTransition.TransitionListener; -import android.app.ActivityManager; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.content.res.Configuration; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.provider.Settings; -import android.provider.Settings.Global; -import android.service.notification.Condition; -import android.service.notification.ZenModeConfig; -import android.service.notification.ZenModeConfig.ZenRule; -import android.text.TextUtils; -import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.util.ArraySet; -import android.util.AttributeSet; -import android.util.Log; -import android.util.MathUtils; -import android.util.Slog; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.TextView; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.Prefs; -import com.android.systemui.R; -import com.android.systemui.statusbar.policy.ZenModeController; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.Arrays; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.Locale; -import java.util.Objects; - -public class ZenModePanel extends FrameLayout { - private static final String TAG = "ZenModePanel"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - public static final int STATE_MODIFY = 0; - public static final int STATE_AUTO_RULE = 1; - public static final int STATE_OFF = 2; - - private static final int SECONDS_MS = 1000; - private static final int MINUTES_MS = 60 * SECONDS_MS; - - private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS; - private static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0]; - private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1]; - private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60); - private static final int FOREVER_CONDITION_INDEX = 0; - private static final int COUNTDOWN_CONDITION_INDEX = 1; - private static final int COUNTDOWN_ALARM_CONDITION_INDEX = 2; - private static final int COUNTDOWN_CONDITION_COUNT = 2; - - public static final Intent ZEN_SETTINGS - = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); - public static final Intent ZEN_PRIORITY_SETTINGS - = new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS); - - private static final long TRANSITION_DURATION = 300; - - private final Context mContext; - protected final LayoutInflater mInflater; - private final H mHandler = new H(); - private final ZenPrefs mPrefs; - private final TransitionHelper mTransitionHelper = new TransitionHelper(); - private final Uri mForeverId; - private final ConfigurableTexts mConfigurableTexts; - - private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this)); - - protected SegmentedButtons mZenButtons; - private View mZenIntroduction; - private TextView mZenIntroductionMessage; - private View mZenIntroductionConfirm; - private TextView mZenIntroductionCustomize; - protected LinearLayout mZenConditions; - private TextView mZenAlarmWarning; - private RadioGroup mZenRadioGroup; - private LinearLayout mZenRadioGroupContent; - - private Callback mCallback; - private ZenModeController mController; - private Condition mExitCondition; - private int mBucketIndex = -1; - private boolean mExpanded; - private boolean mHidden; - private int mSessionZen; - private int mAttachedZen; - private boolean mAttached; - private Condition mSessionExitCondition; - private boolean mVoiceCapable; - - protected int mZenModeConditionLayoutId; - protected int mZenModeButtonLayoutId; - private View mEmpty; - private TextView mEmptyText; - private ImageView mEmptyIcon; - private View mAutoRule; - private TextView mAutoTitle; - private int mState = STATE_MODIFY; - private ViewGroup mEdit; - - public ZenModePanel(Context context, AttributeSet attrs) { - super(context, attrs); - mContext = context; - mPrefs = new ZenPrefs(); - mInflater = LayoutInflater.from(mContext); - mForeverId = Condition.newId(mContext).appendPath("forever").build(); - mConfigurableTexts = new ConfigurableTexts(mContext); - mVoiceCapable = Util.isVoiceCapable(mContext); - mZenModeConditionLayoutId = R.layout.zen_mode_condition; - mZenModeButtonLayoutId = R.layout.zen_mode_button; - if (DEBUG) Log.d(mTag, "new ZenModePanel"); - } - - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("ZenModePanel state:"); - pw.print(" mAttached="); pw.println(mAttached); - pw.print(" mHidden="); pw.println(mHidden); - pw.print(" mExpanded="); pw.println(mExpanded); - pw.print(" mSessionZen="); pw.println(mSessionZen); - pw.print(" mAttachedZen="); pw.println(mAttachedZen); - pw.print(" mConfirmedPriorityIntroduction="); - pw.println(mPrefs.mConfirmedPriorityIntroduction); - pw.print(" mConfirmedSilenceIntroduction="); - pw.println(mPrefs.mConfirmedSilenceIntroduction); - pw.print(" mVoiceCapable="); pw.println(mVoiceCapable); - mTransitionHelper.dump(fd, pw, args); - } - - protected void createZenButtons() { - mZenButtons = findViewById(R.id.zen_buttons); - mZenButtons.addButton(R.string.interruption_level_none_twoline, - R.string.interruption_level_none_with_warning, - Global.ZEN_MODE_NO_INTERRUPTIONS); - mZenButtons.addButton(R.string.interruption_level_alarms_twoline, - R.string.interruption_level_alarms, - Global.ZEN_MODE_ALARMS); - mZenButtons.addButton(R.string.interruption_level_priority_twoline, - R.string.interruption_level_priority, - Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); - mZenButtons.setCallback(mZenButtonsCallback); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - createZenButtons(); - mZenIntroduction = findViewById(R.id.zen_introduction); - mZenIntroductionMessage = findViewById(R.id.zen_introduction_message); - mZenIntroductionConfirm = findViewById(R.id.zen_introduction_confirm); - mZenIntroductionConfirm.setOnClickListener(v -> confirmZenIntroduction()); - mZenIntroductionCustomize = findViewById(R.id.zen_introduction_customize); - mZenIntroductionCustomize.setOnClickListener(v -> { - confirmZenIntroduction(); - if (mCallback != null) { - mCallback.onPrioritySettings(); - } - }); - mConfigurableTexts.add(mZenIntroductionCustomize, R.string.zen_priority_customize_button); - - mZenConditions = findViewById(R.id.zen_conditions); - mZenAlarmWarning = findViewById(R.id.zen_alarm_warning); - mZenRadioGroup = findViewById(R.id.zen_radio_buttons); - mZenRadioGroupContent = findViewById(R.id.zen_radio_buttons_content); - - mEdit = findViewById(R.id.edit_container); - - mEmpty = findViewById(android.R.id.empty); - mEmpty.setVisibility(INVISIBLE); - mEmptyText = mEmpty.findViewById(android.R.id.title); - mEmptyIcon = mEmpty.findViewById(android.R.id.icon); - - mAutoRule = findViewById(R.id.auto_rule); - mAutoTitle = mAutoRule.findViewById(android.R.id.title); - mAutoRule.setVisibility(INVISIBLE); - } - - public void setEmptyState(int icon, int text) { - mEmptyIcon.post(() -> { - mEmptyIcon.setImageResource(icon); - mEmptyText.setText(text); - }); - } - - public void setAutoText(CharSequence text) { - mAutoTitle.post(() -> mAutoTitle.setText(text)); - } - - public void setState(int state) { - if (mState == state) return; - transitionFrom(getView(mState), getView(state)); - mState = state; - } - - private void transitionFrom(View from, View to) { - from.post(() -> { - // TODO: Better transitions - to.setAlpha(0); - to.setVisibility(VISIBLE); - to.bringToFront(); - to.animate().cancel(); - to.animate().alpha(1) - .setDuration(TRANSITION_DURATION) - .withEndAction(() -> from.setVisibility(INVISIBLE)) - .start(); - }); - } - - private View getView(int state) { - switch (state) { - case STATE_AUTO_RULE: - return mAutoRule; - case STATE_OFF: - return mEmpty; - default: - return mEdit; - } - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mConfigurableTexts.update(); - if (mZenButtons != null) { - mZenButtons.update(); - } - } - - private void confirmZenIntroduction() { - final String prefKey = prefKeyForConfirmation(getSelectedZen(Global.ZEN_MODE_OFF)); - if (prefKey == null) return; - if (DEBUG) Log.d(TAG, "confirmZenIntroduction " + prefKey); - Prefs.putBoolean(mContext, prefKey, true); - mHandler.sendEmptyMessage(H.UPDATE_WIDGETS); - } - - private static String prefKeyForConfirmation(int zen) { - switch (zen) { - case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: - return Prefs.Key.DND_CONFIRMED_PRIORITY_INTRODUCTION; - case Global.ZEN_MODE_NO_INTERRUPTIONS: - return Prefs.Key.DND_CONFIRMED_SILENCE_INTRODUCTION; - case Global.ZEN_MODE_ALARMS: - return Prefs.Key.DND_CONFIRMED_ALARM_INTRODUCTION; - default: - return null; - } - } - - private void onAttach() { - setExpanded(true); - mAttachedZen = mController.getZen(); - ZenRule manualRule = mController.getManualRule(); - mExitCondition = manualRule != null ? manualRule.condition : null; - if (DEBUG) Log.d(mTag, "onAttach " + mAttachedZen + " " + manualRule); - handleUpdateManualRule(manualRule); - mZenButtons.setSelectedValue(mAttachedZen, false); - mSessionZen = mAttachedZen; - mTransitionHelper.clear(); - mController.addCallback(mZenCallback); - setSessionExitCondition(copy(mExitCondition)); - updateWidgets(); - setAttached(true); - } - - private void onDetach() { - if (DEBUG) Log.d(mTag, "onDetach"); - setExpanded(false); - checkForAttachedZenChange(); - setAttached(false); - mAttachedZen = -1; - mSessionZen = -1; - mController.removeCallback(mZenCallback); - setSessionExitCondition(null); - mTransitionHelper.clear(); - } - - @VisibleForTesting - void setAttached(boolean attached) { - mAttached = attached; - } - - @Override - public void onVisibilityAggregated(boolean isVisible) { - super.onVisibilityAggregated(isVisible); - if (isVisible == mAttached) return; - if (isVisible) { - onAttach(); - } else { - onDetach(); - } - } - - private void setSessionExitCondition(Condition condition) { - if (Objects.equals(condition, mSessionExitCondition)) return; - if (DEBUG) Log.d(mTag, "mSessionExitCondition=" + getConditionId(condition)); - mSessionExitCondition = condition; - } - - public void setHidden(boolean hidden) { - if (mHidden == hidden) return; - if (DEBUG) Log.d(mTag, "hidden=" + hidden); - mHidden = hidden; - updateWidgets(); - } - - private void checkForAttachedZenChange() { - final int selectedZen = getSelectedZen(-1); - if (DEBUG) Log.d(mTag, "selectedZen=" + selectedZen); - if (selectedZen != mAttachedZen) { - if (DEBUG) Log.d(mTag, "attachedZen: " + mAttachedZen + " -> " + selectedZen); - if (selectedZen == Global.ZEN_MODE_NO_INTERRUPTIONS) { - mPrefs.trackNoneSelected(); - } - } - } - - private void setExpanded(boolean expanded) { - if (expanded == mExpanded) return; - if (DEBUG) Log.d(mTag, "setExpanded " + expanded); - mExpanded = expanded; - updateWidgets(); - fireExpanded(); - } - - protected void addZenConditions(int count) { - for (int i = 0; i < count; i++) { - final View rb = mInflater.inflate(mZenModeButtonLayoutId, mEdit, false); - rb.setId(i); - mZenRadioGroup.addView(rb); - final View rbc = mInflater.inflate(mZenModeConditionLayoutId, mEdit, false); - rbc.setId(i + count); - mZenRadioGroupContent.addView(rbc); - } - } - - public void init(ZenModeController controller) { - mController = controller; - final int minConditions = 1 /*forever*/ + COUNTDOWN_CONDITION_COUNT; - addZenConditions(minConditions); - mSessionZen = getSelectedZen(-1); - handleUpdateManualRule(mController.getManualRule()); - if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition); - hideAllConditions(); - } - - private void setExitCondition(Condition exitCondition) { - if (Objects.equals(mExitCondition, exitCondition)) return; - mExitCondition = exitCondition; - if (DEBUG) Log.d(mTag, "mExitCondition=" + getConditionId(mExitCondition)); - updateWidgets(); - } - - private static Uri getConditionId(Condition condition) { - return condition != null ? condition.id : null; - } - - private Uri getRealConditionId(Condition condition) { - return isForever(condition) ? null : getConditionId(condition); - } - - private static Condition copy(Condition condition) { - return condition == null ? null : condition.copy(); - } - - public void setCallback(Callback callback) { - mCallback = callback; - } - - @VisibleForTesting - void handleUpdateManualRule(ZenRule rule) { - final int zen = rule != null ? rule.zenMode : Global.ZEN_MODE_OFF; - handleUpdateZen(zen); - final Condition c = rule == null ? null - : rule.condition != null ? rule.condition - : createCondition(rule.conditionId); - handleUpdateConditions(c); - setExitCondition(c); - } - - private Condition createCondition(Uri conditionId) { - if (ZenModeConfig.isValidCountdownToAlarmConditionId(conditionId)) { - long time = ZenModeConfig.tryParseCountdownConditionId(conditionId); - Condition c = ZenModeConfig.toNextAlarmCondition( - mContext, time, ActivityManager.getCurrentUser()); - return c; - } else if (ZenModeConfig.isValidCountdownConditionId(conditionId)) { - long time = ZenModeConfig.tryParseCountdownConditionId(conditionId); - int mins = (int) ((time - System.currentTimeMillis() + DateUtils.MINUTE_IN_MILLIS / 2) - / DateUtils.MINUTE_IN_MILLIS); - Condition c = ZenModeConfig.toTimeCondition(mContext, time, mins, - ActivityManager.getCurrentUser(), false); - return c; - } - // If there is a manual rule, but it has no condition listed then it is forever. - return forever(); - } - - private void handleUpdateZen(int zen) { - if (mSessionZen != -1 && mSessionZen != zen) { - mSessionZen = zen; - } - mZenButtons.setSelectedValue(zen, false /* fromClick */); - updateWidgets(); - } - - @VisibleForTesting - int getSelectedZen(int defValue) { - final Object zen = mZenButtons.getSelectedValue(); - return zen != null ? (Integer) zen : defValue; - } - - private void updateWidgets() { - if (mTransitionHelper.isTransitioning()) { - mTransitionHelper.pendingUpdateWidgets(); - return; - } - final int zen = getSelectedZen(Global.ZEN_MODE_OFF); - final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; - final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS; - final boolean zenAlarm = zen == Global.ZEN_MODE_ALARMS; - final boolean introduction = (zenImportant && !mPrefs.mConfirmedPriorityIntroduction - || zenNone && !mPrefs.mConfirmedSilenceIntroduction - || zenAlarm && !mPrefs.mConfirmedAlarmIntroduction); - - mZenButtons.setVisibility(mHidden ? GONE : VISIBLE); - mZenIntroduction.setVisibility(introduction ? VISIBLE : GONE); - if (introduction) { - int message = zenImportant - ? R.string.zen_priority_introduction - : zenAlarm - ? R.string.zen_alarms_introduction - : mVoiceCapable - ? R.string.zen_silence_introduction_voice - : R.string.zen_silence_introduction; - mConfigurableTexts.add(mZenIntroductionMessage, message); - mConfigurableTexts.update(); - mZenIntroductionCustomize.setVisibility(zenImportant ? VISIBLE : GONE); - } - final String warning = computeAlarmWarningText(zenNone); - mZenAlarmWarning.setVisibility(warning != null ? VISIBLE : GONE); - mZenAlarmWarning.setText(warning); - } - - private String computeAlarmWarningText(boolean zenNone) { - if (!zenNone) { - return null; - } - final long now = System.currentTimeMillis(); - final long nextAlarm = mController.getNextAlarm(); - if (nextAlarm < now) { - return null; - } - int warningRes = 0; - if (mSessionExitCondition == null || isForever(mSessionExitCondition)) { - warningRes = R.string.zen_alarm_warning_indef; - } else { - final long time = ZenModeConfig.tryParseCountdownConditionId(mSessionExitCondition.id); - if (time > now && nextAlarm < time) { - warningRes = R.string.zen_alarm_warning; - } - } - if (warningRes == 0) { - return null; - } - final boolean soon = (nextAlarm - now) < 24 * 60 * 60 * 1000; - final boolean is24 = DateFormat.is24HourFormat(mContext, ActivityManager.getCurrentUser()); - final String skeleton = soon ? (is24 ? "Hm" : "hma") : (is24 ? "EEEHm" : "EEEhma"); - final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); - final CharSequence formattedTime = DateFormat.format(pattern, nextAlarm); - final int templateRes = soon ? R.string.alarm_template : R.string.alarm_template_far; - final String template = getResources().getString(templateRes, formattedTime); - return getResources().getString(warningRes, template); - } - - @VisibleForTesting - void handleUpdateConditions(Condition c) { - if (mTransitionHelper.isTransitioning()) { - return; - } - // forever - bind(forever(), mZenRadioGroupContent.getChildAt(FOREVER_CONDITION_INDEX), - FOREVER_CONDITION_INDEX); - if (c == null) { - bindGenericCountdown(); - bindNextAlarm(getTimeUntilNextAlarmCondition()); - } else if (isForever(c)) { - - getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true); - bindGenericCountdown(); - bindNextAlarm(getTimeUntilNextAlarmCondition()); - } else { - if (isAlarm(c)) { - bindGenericCountdown(); - bindNextAlarm(c); - getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.setChecked(true); - } else if (isCountdown(c)) { - bindNextAlarm(getTimeUntilNextAlarmCondition()); - bind(c, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX), - COUNTDOWN_CONDITION_INDEX); - getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true); - } else { - Slog.wtf(TAG, "Invalid manual condition: " + c); - } - } - mZenConditions.setVisibility(mSessionZen != Global.ZEN_MODE_OFF ? View.VISIBLE : View.GONE); - } - - private void bindGenericCountdown() { - mBucketIndex = DEFAULT_BUCKET_INDEX; - Condition countdown = ZenModeConfig.toTimeCondition(mContext, - MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser()); - // don't change the hour condition while the user is viewing the panel - if (!mAttached || getConditionTagAt(COUNTDOWN_CONDITION_INDEX).condition == null) { - bind(countdown, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX), - COUNTDOWN_CONDITION_INDEX); - } - } - - private void bindNextAlarm(Condition c) { - View alarmContent = mZenRadioGroupContent.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX); - ConditionTag tag = (ConditionTag) alarmContent.getTag(); - // Don't change the alarm condition while the user is viewing the panel - if (c != null && (!mAttached || tag == null || tag.condition == null)) { - bind(c, alarmContent, COUNTDOWN_ALARM_CONDITION_INDEX); - } - - tag = (ConditionTag) alarmContent.getTag(); - boolean showAlarm = tag != null && tag.condition != null; - mZenRadioGroup.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX).setVisibility( - showAlarm ? View.VISIBLE : View.INVISIBLE); - alarmContent.setVisibility(showAlarm ? View.VISIBLE : View.INVISIBLE); - } - - private Condition forever() { - return new Condition(mForeverId, foreverSummary(mContext), "", "", 0 /*icon*/, - Condition.STATE_TRUE, 0 /*flags*/); - } - - private static String foreverSummary(Context context) { - return context.getString(com.android.internal.R.string.zen_mode_forever); - } - - // Returns a time condition if the next alarm is within the next week. - private Condition getTimeUntilNextAlarmCondition() { - GregorianCalendar weekRange = new GregorianCalendar(); - setToMidnight(weekRange); - weekRange.add(Calendar.DATE, 6); - final long nextAlarmMs = mController.getNextAlarm(); - if (nextAlarmMs > 0) { - GregorianCalendar nextAlarm = new GregorianCalendar(); - nextAlarm.setTimeInMillis(nextAlarmMs); - setToMidnight(nextAlarm); - - if (weekRange.compareTo(nextAlarm) >= 0) { - return ZenModeConfig.toNextAlarmCondition(mContext, nextAlarmMs, - ActivityManager.getCurrentUser()); - } - } - return null; - } - - private void setToMidnight(Calendar calendar) { - calendar.set(Calendar.HOUR_OF_DAY, 0); - calendar.set(Calendar.MINUTE, 0); - calendar.set(Calendar.SECOND, 0); - calendar.set(Calendar.MILLISECOND, 0); - } - - @VisibleForTesting - ConditionTag getConditionTagAt(int index) { - return (ConditionTag) mZenRadioGroupContent.getChildAt(index).getTag(); - } - - @VisibleForTesting - int getVisibleConditions() { - int rt = 0; - final int N = mZenRadioGroupContent.getChildCount(); - for (int i = 0; i < N; i++) { - rt += mZenRadioGroupContent.getChildAt(i).getVisibility() == VISIBLE ? 1 : 0; - } - return rt; - } - - private void hideAllConditions() { - final int N = mZenRadioGroupContent.getChildCount(); - for (int i = 0; i < N; i++) { - mZenRadioGroupContent.getChildAt(i).setVisibility(GONE); - } - } - - private static boolean isAlarm(Condition c) { - return c != null && ZenModeConfig.isValidCountdownToAlarmConditionId(c.id); - } - - private static boolean isCountdown(Condition c) { - return c != null && ZenModeConfig.isValidCountdownConditionId(c.id); - } - - private boolean isForever(Condition c) { - return c != null && mForeverId.equals(c.id); - } - - private void bind(final Condition condition, final View row, final int rowId) { - if (condition == null) throw new IllegalArgumentException("condition must not be null"); - final boolean enabled = condition.state == Condition.STATE_TRUE; - final ConditionTag tag = - row.getTag() != null ? (ConditionTag) row.getTag() : new ConditionTag(); - row.setTag(tag); - final boolean first = tag.rb == null; - if (tag.rb == null) { - tag.rb = (RadioButton) mZenRadioGroup.getChildAt(rowId); - } - tag.condition = condition; - final Uri conditionId = getConditionId(tag.condition); - if (DEBUG) Log.d(mTag, "bind i=" + mZenRadioGroupContent.indexOfChild(row) + " first=" - + first + " condition=" + conditionId); - tag.rb.setEnabled(enabled); - tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (mExpanded && isChecked) { - tag.rb.setChecked(true); - if (DEBUG) Log.d(mTag, "onCheckedChanged " + conditionId); - MetricsLogger.action(mContext, MetricsEvent.QS_DND_CONDITION_SELECT); - select(tag.condition); - announceConditionSelection(tag); - } - } - }); - - if (tag.lines == null) { - tag.lines = row.findViewById(android.R.id.content); - } - if (tag.line1 == null) { - tag.line1 = (TextView) row.findViewById(android.R.id.text1); - mConfigurableTexts.add(tag.line1); - } - if (tag.line2 == null) { - tag.line2 = (TextView) row.findViewById(android.R.id.text2); - mConfigurableTexts.add(tag.line2); - } - final String line1 = !TextUtils.isEmpty(condition.line1) ? condition.line1 - : condition.summary; - final String line2 = condition.line2; - tag.line1.setText(line1); - if (TextUtils.isEmpty(line2)) { - tag.line2.setVisibility(GONE); - } else { - tag.line2.setVisibility(VISIBLE); - tag.line2.setText(line2); - } - tag.lines.setEnabled(enabled); - tag.lines.setAlpha(enabled ? 1 : .4f); - - final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1); - button1.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - onClickTimeButton(row, tag, false /*down*/, rowId); - } - }); - - final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2); - button2.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - onClickTimeButton(row, tag, true /*up*/, rowId); - } - }); - tag.lines.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - tag.rb.setChecked(true); - } - }); - - final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId); - if (rowId != COUNTDOWN_ALARM_CONDITION_INDEX && time > 0) { - button1.setVisibility(VISIBLE); - button2.setVisibility(VISIBLE); - if (mBucketIndex > -1) { - button1.setEnabled(mBucketIndex > 0); - button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1); - } else { - final long span = time - System.currentTimeMillis(); - button1.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS); - final Condition maxCondition = ZenModeConfig.toTimeCondition(mContext, - MAX_BUCKET_MINUTES, ActivityManager.getCurrentUser()); - button2.setEnabled(!Objects.equals(condition.summary, maxCondition.summary)); - } - - button1.setAlpha(button1.isEnabled() ? 1f : .5f); - button2.setAlpha(button2.isEnabled() ? 1f : .5f); - } else { - button1.setVisibility(GONE); - button2.setVisibility(GONE); - } - // wire up interaction callbacks for newly-added condition rows - if (first) { - Interaction.register(tag.rb, mInteractionCallback); - Interaction.register(tag.lines, mInteractionCallback); - Interaction.register(button1, mInteractionCallback); - Interaction.register(button2, mInteractionCallback); - } - row.setVisibility(VISIBLE); - } - - private void announceConditionSelection(ConditionTag tag) { - final int zen = getSelectedZen(Global.ZEN_MODE_OFF); - String modeText; - switch(zen) { - case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: - modeText = mContext.getString(R.string.interruption_level_priority); - break; - case Global.ZEN_MODE_NO_INTERRUPTIONS: - modeText = mContext.getString(R.string.interruption_level_none); - break; - case Global.ZEN_MODE_ALARMS: - modeText = mContext.getString(R.string.interruption_level_alarms); - break; - default: - return; - } - announceForAccessibility(mContext.getString(R.string.zen_mode_and_condition, modeText, - tag.line1.getText())); - } - - private void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) { - MetricsLogger.action(mContext, MetricsEvent.QS_DND_TIME, up); - Condition newCondition = null; - final int N = MINUTE_BUCKETS.length; - if (mBucketIndex == -1) { - // not on a known index, search for the next or prev bucket by time - final Uri conditionId = getConditionId(tag.condition); - final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId); - final long now = System.currentTimeMillis(); - for (int i = 0; i < N; i++) { - int j = up ? i : N - 1 - i; - final int bucketMinutes = MINUTE_BUCKETS[j]; - final long bucketTime = now + bucketMinutes * MINUTES_MS; - if (up && bucketTime > time || !up && bucketTime < time) { - mBucketIndex = j; - newCondition = ZenModeConfig.toTimeCondition(mContext, - bucketTime, bucketMinutes, ActivityManager.getCurrentUser(), - false /*shortVersion*/); - break; - } - } - if (newCondition == null) { - mBucketIndex = DEFAULT_BUCKET_INDEX; - newCondition = ZenModeConfig.toTimeCondition(mContext, - MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser()); - } - } else { - // on a known index, simply increment or decrement - mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1))); - newCondition = ZenModeConfig.toTimeCondition(mContext, - MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser()); - } - bind(newCondition, row, rowId); - tag.rb.setChecked(true); - select(newCondition); - announceConditionSelection(tag); - } - - private void select(final Condition condition) { - if (DEBUG) Log.d(mTag, "select " + condition); - if (mSessionZen == -1 || mSessionZen == Global.ZEN_MODE_OFF) { - if (DEBUG) Log.d(mTag, "Ignoring condition selection outside of manual zen"); - return; - } - final Uri realConditionId = getRealConditionId(condition); - if (mController != null) { - AsyncTask.execute(new Runnable() { - @Override - public void run() { - mController.setZen(mSessionZen, realConditionId, TAG + ".selectCondition"); - } - }); - } - setExitCondition(condition); - if (realConditionId == null) { - mPrefs.setMinuteIndex(-1); - } else if ((isAlarm(condition) || isCountdown(condition)) && mBucketIndex != -1) { - mPrefs.setMinuteIndex(mBucketIndex); - } - setSessionExitCondition(copy(condition)); - } - - private void fireInteraction() { - if (mCallback != null) { - mCallback.onInteraction(); - } - } - - private void fireExpanded() { - if (mCallback != null) { - mCallback.onExpanded(mExpanded); - } - } - - private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { - @Override - public void onManualRuleChanged(ZenRule rule) { - mHandler.obtainMessage(H.MANUAL_RULE_CHANGED, rule).sendToTarget(); - } - }; - - private final class H extends Handler { - private static final int MANUAL_RULE_CHANGED = 2; - private static final int UPDATE_WIDGETS = 3; - - private H() { - super(Looper.getMainLooper()); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MANUAL_RULE_CHANGED: handleUpdateManualRule((ZenRule) msg.obj); break; - case UPDATE_WIDGETS: updateWidgets(); break; - } - } - } - - public interface Callback { - void onPrioritySettings(); - void onInteraction(); - void onExpanded(boolean expanded); - } - - // used as the view tag on condition rows - @VisibleForTesting - static class ConditionTag { - RadioButton rb; - View lines; - TextView line1; - TextView line2; - Condition condition; - } - - private final class ZenPrefs implements OnSharedPreferenceChangeListener { - private final int mNoneDangerousThreshold; - - private int mMinuteIndex; - private int mNoneSelected; - private boolean mConfirmedPriorityIntroduction; - private boolean mConfirmedSilenceIntroduction; - private boolean mConfirmedAlarmIntroduction; - - private ZenPrefs() { - mNoneDangerousThreshold = mContext.getResources() - .getInteger(R.integer.zen_mode_alarm_warning_threshold); - Prefs.registerListener(mContext, this); - updateMinuteIndex(); - updateNoneSelected(); - updateConfirmedPriorityIntroduction(); - updateConfirmedSilenceIntroduction(); - updateConfirmedAlarmIntroduction(); - } - - public void trackNoneSelected() { - mNoneSelected = clampNoneSelected(mNoneSelected + 1); - if (DEBUG) Log.d(mTag, "Setting none selected: " + mNoneSelected + " threshold=" - + mNoneDangerousThreshold); - Prefs.putInt(mContext, Prefs.Key.DND_NONE_SELECTED, mNoneSelected); - } - - public int getMinuteIndex() { - return mMinuteIndex; - } - - public void setMinuteIndex(int minuteIndex) { - minuteIndex = clampIndex(minuteIndex); - if (minuteIndex == mMinuteIndex) return; - mMinuteIndex = clampIndex(minuteIndex); - if (DEBUG) Log.d(mTag, "Setting favorite minute index: " + mMinuteIndex); - Prefs.putInt(mContext, Prefs.Key.DND_FAVORITE_BUCKET_INDEX, mMinuteIndex); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { - updateMinuteIndex(); - updateNoneSelected(); - updateConfirmedPriorityIntroduction(); - updateConfirmedSilenceIntroduction(); - updateConfirmedAlarmIntroduction(); - } - - private void updateMinuteIndex() { - mMinuteIndex = clampIndex(Prefs.getInt(mContext, - Prefs.Key.DND_FAVORITE_BUCKET_INDEX, DEFAULT_BUCKET_INDEX)); - if (DEBUG) Log.d(mTag, "Favorite minute index: " + mMinuteIndex); - } - - private int clampIndex(int index) { - return MathUtils.constrain(index, -1, MINUTE_BUCKETS.length - 1); - } - - private void updateNoneSelected() { - mNoneSelected = clampNoneSelected(Prefs.getInt(mContext, - Prefs.Key.DND_NONE_SELECTED, 0)); - if (DEBUG) Log.d(mTag, "None selected: " + mNoneSelected); - } - - private int clampNoneSelected(int noneSelected) { - return MathUtils.constrain(noneSelected, 0, Integer.MAX_VALUE); - } - - private void updateConfirmedPriorityIntroduction() { - final boolean confirmed = Prefs.getBoolean(mContext, - Prefs.Key.DND_CONFIRMED_PRIORITY_INTRODUCTION, false); - if (confirmed == mConfirmedPriorityIntroduction) return; - mConfirmedPriorityIntroduction = confirmed; - if (DEBUG) Log.d(mTag, "Confirmed priority introduction: " - + mConfirmedPriorityIntroduction); - } - - private void updateConfirmedSilenceIntroduction() { - final boolean confirmed = Prefs.getBoolean(mContext, - Prefs.Key.DND_CONFIRMED_SILENCE_INTRODUCTION, false); - if (confirmed == mConfirmedSilenceIntroduction) return; - mConfirmedSilenceIntroduction = confirmed; - if (DEBUG) Log.d(mTag, "Confirmed silence introduction: " - + mConfirmedSilenceIntroduction); - } - - private void updateConfirmedAlarmIntroduction() { - final boolean confirmed = Prefs.getBoolean(mContext, - Prefs.Key.DND_CONFIRMED_ALARM_INTRODUCTION, false); - if (confirmed == mConfirmedAlarmIntroduction) return; - mConfirmedAlarmIntroduction = confirmed; - if (DEBUG) Log.d(mTag, "Confirmed alarm introduction: " - + mConfirmedAlarmIntroduction); - } - } - - protected final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() { - @Override - public void onSelected(final Object value, boolean fromClick) { - if (value != null && mZenButtons.isShown() && isAttachedToWindow()) { - final int zen = (Integer) value; - if (fromClick) { - MetricsLogger.action(mContext, MetricsEvent.QS_DND_ZEN_SELECT, zen); - } - if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + zen); - final Uri realConditionId = getRealConditionId(mSessionExitCondition); - AsyncTask.execute(new Runnable() { - @Override - public void run() { - mController.setZen(zen, realConditionId, TAG + ".selectZen"); - if (zen != Global.ZEN_MODE_OFF) { - Prefs.putInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, zen); - } - } - }); - } - } - - @Override - public void onInteraction() { - fireInteraction(); - } - }; - - private final Interaction.Callback mInteractionCallback = new Interaction.Callback() { - @Override - public void onInteraction() { - fireInteraction(); - } - }; - - private final class TransitionHelper implements TransitionListener, Runnable { - private final ArraySet<View> mTransitioningViews = new ArraySet<View>(); - - private boolean mTransitioning; - private boolean mPendingUpdateWidgets; - - public void clear() { - mTransitioningViews.clear(); - mPendingUpdateWidgets = false; - } - - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println(" TransitionHelper state:"); - pw.print(" mPendingUpdateWidgets="); pw.println(mPendingUpdateWidgets); - pw.print(" mTransitioning="); pw.println(mTransitioning); - pw.print(" mTransitioningViews="); pw.println(mTransitioningViews); - } - - public void pendingUpdateWidgets() { - mPendingUpdateWidgets = true; - } - - public boolean isTransitioning() { - return !mTransitioningViews.isEmpty(); - } - - @Override - public void startTransition(LayoutTransition transition, - ViewGroup container, View view, int transitionType) { - mTransitioningViews.add(view); - updateTransitioning(); - } - - @Override - public void endTransition(LayoutTransition transition, - ViewGroup container, View view, int transitionType) { - mTransitioningViews.remove(view); - updateTransitioning(); - } - - @Override - public void run() { - if (DEBUG) Log.d(mTag, "TransitionHelper run" - + " mPendingUpdateWidgets=" + mPendingUpdateWidgets); - if (mPendingUpdateWidgets) { - updateWidgets(); - } - mPendingUpdateWidgets = false; - } - - private void updateTransitioning() { - final boolean transitioning = isTransitioning(); - if (mTransitioning == transitioning) return; - mTransitioning = transitioning; - if (DEBUG) Log.d(mTag, "TransitionHelper mTransitioning=" + mTransitioning); - if (!mTransitioning) { - if (mPendingUpdateWidgets) { - mHandler.post(this); - } else { - mPendingUpdateWidgets = false; - } - } - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 24b01e079b42..6736bfd21740 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -307,7 +307,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { UserInfo info = new UserInfo(i /* id */, "Name: " + i, null /* iconPath */, 0 /* flags */); users.add(new UserRecord(info, null, false /* isGuest */, false /* isCurrent */, - false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */)); + false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */)); } return users; } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java index 9f8f6c1940ef..06082b61ec26 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java @@ -32,6 +32,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -66,6 +67,11 @@ public class KeyguardSliceViewControllerTest extends SysuiTestCase { mController.setupUri(KeyguardSliceProvider.KEYGUARD_SLICE_URI); } + @After + public void tearDown() { + mController.onViewDetached(); + } + @Test public void refresh_replacesSliceContentAndNotifiesListener() { mController.refresh(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 08d881ff96aa..f71dd24a22eb 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -32,7 +32,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -58,7 +57,6 @@ import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorProperties; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintManager; -import android.media.AudioManager; import android.nfc.NfcAdapter; import android.os.Bundle; import android.os.Handler; @@ -74,9 +72,6 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.Observer; - import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.telephony.TelephonyIntents; @@ -92,7 +87,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.telephony.TelephonyListenerManager; -import com.android.systemui.util.RingerModeTracker; import org.junit.After; import org.junit.Assert; @@ -101,7 +95,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; -import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; @@ -161,10 +154,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private TelephonyManager mTelephonyManager; @Mock - private RingerModeTracker mRingerModeTracker; - @Mock - private LiveData<Integer> mRingerModeLiveData; - @Mock private StatusBarStateController mStatusBarStateController; @Mock private AuthController mAuthController; @@ -242,8 +231,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mSpiedContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager); mSpiedContext.addMockSystemService(TelephonyManager.class, mTelephonyManager); - when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); - mMockitoSession = ExtendedMockito.mockitoSession() .spyStatic(SubscriptionManager.class).startMocking(); ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID) @@ -866,29 +853,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testRingerModeChange() { - ArgumentCaptor<Observer<Integer>> captor = ArgumentCaptor.forClass(Observer.class); - verify(mRingerModeLiveData).observeForever(captor.capture()); - Observer<Integer> observer = captor.getValue(); - - KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); - - mKeyguardUpdateMonitor.registerCallback(callback); - - observer.onChanged(AudioManager.RINGER_MODE_NORMAL); - observer.onChanged(AudioManager.RINGER_MODE_SILENT); - observer.onChanged(AudioManager.RINGER_MODE_VIBRATE); - - mTestableLooper.processAllMessages(); - - InOrder orderVerify = inOrder(callback); - orderVerify.verify(callback).onRingerModeChanged(anyInt()); // Initial update on register - orderVerify.verify(callback).onRingerModeChanged(AudioManager.RINGER_MODE_NORMAL); - orderVerify.verify(callback).onRingerModeChanged(AudioManager.RINGER_MODE_SILENT); - orderVerify.verify(callback).onRingerModeChanged(AudioManager.RINGER_MODE_VIBRATE); - } - - @Test public void testRegisterAuthControllerCallback() { assertThat(mKeyguardUpdateMonitor.isUdfpsEnrolled()).isFalse(); @@ -1120,7 +1084,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { super(context, TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(), mBroadcastDispatcher, mDumpManager, - mRingerModeTracker, mBackgroundExecutor, mMainExecutor, + mBackgroundExecutor, mMainExecutor, mStatusBarStateController, mLockPatternUtils, mAuthController, mTelephonyListenerManager, mInteractionJankMonitor, mLatencyTracker); diff --git a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt new file mode 100644 index 000000000000..e62b4e63e3d5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui + +import android.graphics.Canvas +import android.graphics.Insets +import android.graphics.Path +import android.graphics.Rect +import android.graphics.RectF +import android.graphics.Region +import android.testing.AndroidTestingRunner +import android.view.Display +import android.view.DisplayCutout +import android.view.DisplayInfo +import android.view.View +import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.never +import com.android.internal.R +import com.android.systemui.util.mockito.eq +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class DisplayCutoutBaseViewTest : SysuiTestCase() { + + @Mock private lateinit var mockCanvas: Canvas + @Mock private lateinit var mockRootView: View + @Mock private lateinit var mockDisplay: Display + + private lateinit var cutoutBaseView: DisplayCutoutBaseView + private val cutout: DisplayCutout = DisplayCutout.Builder() + .setSafeInsets(Insets.of(0, 2, 0, 0)) + .setBoundingRectTop(Rect(1, 0, 2, 2)) + .build() + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun testBoundingRectsToRegion() { + setupDisplayCutoutBaseView(true /* fillCutout */, true /* hasCutout */) + val rect = Rect(1, 2, 3, 4) + assertThat(cutoutBaseView.rectsToRegion(listOf(rect)).bounds).isEqualTo(rect) + } + + @Test + fun testDrawCutout_fillCutout() { + setupDisplayCutoutBaseView(true /* fillCutout */, true /* hasCutout */) + cutoutBaseView.onDraw(mockCanvas) + + verify(cutoutBaseView).drawCutouts(mockCanvas) + } + + @Test + fun testDrawCutout_notFillCutout() { + setupDisplayCutoutBaseView(false /* fillCutout */, true /* hasCutout */) + cutoutBaseView.onDraw(mockCanvas) + + verify(cutoutBaseView, never()).drawCutouts(mockCanvas) + } + + @Test + fun testShouldInterceptTouch_hasCutout() { + setupDisplayCutoutBaseView(true /* fillCutout */, true /* hasCutout */) + cutoutBaseView.updateCutout() + + assertThat(cutoutBaseView.shouldInterceptTouch()).isTrue() + } + + @Test + fun testShouldInterceptTouch_noCutout() { + setupDisplayCutoutBaseView(true /* fillCutout */, false /* hasCutout */) + cutoutBaseView.updateCutout() + + assertThat(cutoutBaseView.shouldInterceptTouch()).isFalse() + } + + @Test + fun testGetInterceptRegion_hasCutout() { + setupDisplayCutoutBaseView(true /* fillCutout */, true /* hasCutout */) + whenever(mockRootView.left).thenReturn(0) + whenever(mockRootView.top).thenReturn(0) + whenever(mockRootView.right).thenReturn(100) + whenever(mockRootView.bottom).thenReturn(200) + + val expect = Region() + expect.op(cutout.boundingRectTop, Region.Op.UNION) + expect.op(0, 0, 100, 200, Region.Op.INTERSECT) + + cutoutBaseView.updateCutout() + + assertThat(cutoutBaseView.interceptRegion).isEqualTo(expect) + } + + @Test + fun testGetInterceptRegion_noCutout() { + setupDisplayCutoutBaseView(true /* fillCutout */, false /* hasCutout */) + cutoutBaseView.updateCutout() + + assertThat(cutoutBaseView.interceptRegion).isNull() + } + + @Test + fun testCutoutProtection() { + setupDisplayCutoutBaseView(true /* fillCutout */, false /* hasCutout */) + val bounds = Rect(0, 0, 10, 10) + val path = Path() + val pathBounds = RectF(bounds) + path.addRect(pathBounds, Path.Direction.CCW) + + context.mainExecutor.execute { + cutoutBaseView.setProtection(path, bounds) + cutoutBaseView.enableShowProtection(true) + } + waitForIdleSync() + + assertThat(cutoutBaseView.protectionPath.isRect(pathBounds)).isTrue() + assertThat(cutoutBaseView.protectionRect).isEqualTo(pathBounds) + } + + private fun setupDisplayCutoutBaseView(fillCutout: Boolean, hasCutout: Boolean) { + mContext.orCreateTestableResources.addOverride( + R.array.config_displayUniqueIdArray, arrayOf<String>()) + mContext.orCreateTestableResources.addOverride( + R.bool.config_fillMainBuiltInDisplayCutout, fillCutout) + + cutoutBaseView = spy(DisplayCutoutBaseView(mContext)) + whenever(cutoutBaseView.display).thenReturn(mockDisplay) + whenever(cutoutBaseView.rootView).thenReturn(mockRootView) + whenever(mockDisplay.getDisplayInfo(eq(cutoutBaseView.displayInfo)) + ).then { + val info = it.getArgument<DisplayInfo>(0) + info.displayCutout = if (hasCutout) cutout else null + return@then true + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 72d72c8c3b5e..70f325158624 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -14,7 +14,6 @@ package com.android.systemui; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM; import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT; import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH; @@ -22,7 +21,7 @@ import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT; import static android.view.DisplayCutout.BOUNDS_POSITION_TOP; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; -import static com.android.systemui.ScreenDecorations.rectsToRegion; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.google.common.truth.Truth.assertThat; @@ -32,7 +31,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.atLeastOnce; @@ -49,10 +47,12 @@ import android.annotation.IdRes; import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Insets; +import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.VectorDrawable; import android.hardware.display.DisplayManager; +import android.hardware.graphics.common.DisplayDecorationSupport; import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -89,7 +89,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; -import java.util.Collections; @RunWithLooper @RunWith(AndroidTestingRunner.class) @@ -106,6 +105,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { private FakeThreadFactory mThreadFactory; private ArrayList<DecorProvider> mDecorProviders; @Mock + private Display mDisplay; + @Mock private TunerService mTunerService; @Mock private BroadcastDispatcher mBroadcastDispatcher; @@ -140,14 +141,15 @@ public class ScreenDecorationsTest extends SysuiTestCase { .getMaximumWindowMetrics(); when(mWindowManager.getMaximumWindowMetrics()).thenReturn(metrics); mContext.addMockSystemService(WindowManager.class, mWindowManager); - mDisplayManager = mock(DisplayManager.class); - Display display = mContext.getSystemService(DisplayManager.class) - .getDisplay(DEFAULT_DISPLAY); - when(mDisplayManager.getDisplay(anyInt())).thenReturn(display); mContext.addMockSystemService(DisplayManager.class, mDisplayManager); - when(mMockTypedArray.length()).thenReturn(0); + spyOn(mContext); + when(mContext.getDisplay()).thenReturn(mDisplay); + // Not support hwc layer by default + doReturn(null).when(mDisplay).getDisplayDecorationSupport(); + + when(mMockTypedArray.length()).thenReturn(0); mPrivacyDotTopLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl( R.id.privacy_dot_top_left_container, DisplayCutout.BOUNDS_POSITION_TOP, @@ -975,12 +977,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { } @Test - public void testBoundingRectsToRegion() throws Exception { - Rect rect = new Rect(1, 2, 3, 4); - assertThat(rectsToRegion(Collections.singletonList(rect)).getBounds(), is(rect)); - } - - @Test public void testRegistration_From_NoOverlay_To_HasOverlays() { doReturn(false).when(mScreenDecorations).hasOverlays(); mScreenDecorations.start(); @@ -1029,6 +1025,114 @@ public class ScreenDecorationsTest extends SysuiTestCase { assertThat(mScreenDecorations.mIsRegistered, is(false)); } + @Test + public void testSupportHwcLayer_SwitchFrom_NotSupport() { + setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */, + 0 /* roundedPadding */, false /* multipleRadius */, + true /* fillCutout */, false /* privacyDot */); + + // top cutout + final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; + doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) + .when(mScreenDecorations).getCutout(); + + mScreenDecorations.start(); + // should only inflate mOverlays when the hwc doesn't support screen decoration + assertNull(mScreenDecorations.mScreenDecorHwcWindow); + assertNotNull(mScreenDecorations.mOverlays); + assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]); + assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]); + + final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); + decorationSupport.format = PixelFormat.R_8; + doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); + // Trigger the support hwc screen decoration change by changing the display unique id + mScreenDecorations.mDisplayUniqueId = "test"; + mScreenDecorations.mDisplayListener.onDisplayChanged(1); + + // should only inflate hwc layer when the hwc supports screen decoration + assertNotNull(mScreenDecorations.mScreenDecorHwcWindow); + assertNull(mScreenDecorations.mOverlays); + } + + @Test + public void testNotSupportHwcLayer_SwitchFrom_Support() { + setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */, + 0 /* roundedPadding */, false /* multipleRadius */, + true /* fillCutout */, false /* privacyDot */); + final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); + decorationSupport.format = PixelFormat.R_8; + doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); + + // top cutout + final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; + doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) + .when(mScreenDecorations).getCutout(); + + mScreenDecorations.start(); + // should only inflate hwc layer when the hwc supports screen decoration + assertNotNull(mScreenDecorations.mScreenDecorHwcWindow); + assertNull(mScreenDecorations.mOverlays); + + doReturn(null).when(mDisplay).getDisplayDecorationSupport(); + // Trigger the support hwc screen decoration change by changing the display unique id + mScreenDecorations.mDisplayUniqueId = "test"; + mScreenDecorations.mDisplayListener.onDisplayChanged(1); + + // should only inflate mOverlays when the hwc doesn't support screen decoration + assertNull(mScreenDecorations.mScreenDecorHwcWindow); + assertNotNull(mScreenDecorations.mOverlays); + assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]); + assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]); + } + + @Test + public void testHwcLayer_noPrivacyDot() { + setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */, + 0 /* roundedPadding */, false /* multipleRadius */, + true /* fillCutout */, false /* privacyDot */); + final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); + decorationSupport.format = PixelFormat.R_8; + doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); + + // top cutout + final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; + doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) + .when(mScreenDecorations).getCutout(); + + mScreenDecorations.start(); + + // Should only inflate hwc layer. + assertNotNull(mScreenDecorations.mScreenDecorHwcWindow); + assertNull(mScreenDecorations.mOverlays); + } + + @Test + public void testHwcLayer_PrivacyDot() { + setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */, + 0 /* roundedPadding */, false /* multipleRadius */, + true /* fillCutout */, true /* privacyDot */); + final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); + decorationSupport.format = PixelFormat.R_8; + doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); + + // top cutout + final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; + doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) + .when(mScreenDecorations).getCutout(); + + mScreenDecorations.start(); + + assertNotNull(mScreenDecorations.mScreenDecorHwcWindow); + // mOverlays are inflated but the visibility should be GONE. + assertNotNull(mScreenDecorations.mOverlays); + final View topOverlay = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView(); + final View botOverlay = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView(); + assertEquals(topOverlay.getVisibility(), View.INVISIBLE); + assertEquals(botOverlay.getVisibility(), View.INVISIBLE); + + } + private void setupResources(int radius, int radiusTop, int radiusBottom, int roundedPadding, boolean multipleRadius, boolean fillCutout, boolean privacyDot) { mContext.getOrCreateTestableResources().addOverride( diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java index 2d52c42fa75f..c5b1a1d8bac5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java @@ -16,7 +16,8 @@ package com.android.systemui.communal; -import static org.mockito.ArgumentMatchers.anyBoolean; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; @@ -59,7 +60,8 @@ public class CommunalSettingConditionTest extends SysuiTestCase { final Condition.Callback callback = mock(Condition.Callback.class); mCondition.addCallback(callback); - verify(callback).onConditionChanged(mCondition, true); + verify(callback).onConditionChanged(mCondition); + assertThat(mCondition.isConditionMet()).isTrue(); } @Test @@ -68,7 +70,7 @@ public class CommunalSettingConditionTest extends SysuiTestCase { final Condition.Callback callback = mock(Condition.Callback.class); mCondition.addCallback(callback); - verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean()); + verify(callback, never()).onConditionChanged(eq(mCondition)); } @Test @@ -80,7 +82,8 @@ public class CommunalSettingConditionTest extends SysuiTestCase { clearInvocations(callback); updateCommunalSetting(true); - verify(callback).onConditionChanged(mCondition, true); + verify(callback).onConditionChanged(mCondition); + assertThat(mCondition.isConditionMet()).isTrue(); } @Test @@ -92,7 +95,8 @@ public class CommunalSettingConditionTest extends SysuiTestCase { clearInvocations(callback); updateCommunalSetting(false); - verify(callback).onConditionChanged(mCondition, false); + verify(callback).onConditionChanged(mCondition); + assertThat(mCondition.isConditionMet()).isFalse(); } @Test @@ -104,7 +108,8 @@ public class CommunalSettingConditionTest extends SysuiTestCase { clearInvocations(callback); updateCommunalSetting(true); - verify(callback, never()).onConditionChanged(mCondition, true); + verify(callback, never()).onConditionChanged(mCondition); + assertThat(mCondition.isConditionMet()).isTrue(); } private void updateCommunalSetting(boolean value) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java index 61a5126fb139..500205c49ad2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java @@ -16,7 +16,8 @@ package com.android.systemui.communal; -import static org.mockito.ArgumentMatchers.anyBoolean; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.clearInvocations; @@ -49,6 +50,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @SmallTest @@ -89,7 +91,8 @@ public class CommunalTrustedNetworkConditionTest extends SysuiTestCase { networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1)); // Verifies that the callback is triggered. - verify(callback).onConditionChanged(mCondition, true); + verify(callback).onConditionChanged(mCondition); + assertThat(mCondition.isConditionMet()).isTrue(); } @Test @@ -110,7 +113,7 @@ public class CommunalTrustedNetworkConditionTest extends SysuiTestCase { networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi2)); // Verifies that the callback is not triggered. - verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean()); + verify(callback, never()).onConditionChanged(eq(mCondition)); } @Test @@ -126,11 +129,13 @@ public class CommunalTrustedNetworkConditionTest extends SysuiTestCase { networkCallback.onAvailable(network); networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1)); + Mockito.clearInvocations(callback); // Connected to non-trusted Wi-Fi network. networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities("random-wifi")); // Verifies that the callback is triggered. - verify(callback).onConditionChanged(mCondition, false); + verify(callback).onConditionChanged(mCondition); + assertThat(mCondition.isConditionMet()).isFalse(); } @Test @@ -151,7 +156,8 @@ public class CommunalTrustedNetworkConditionTest extends SysuiTestCase { networkCallback.onLost(network); // Verifies that the callback is triggered. - verify(callback).onConditionChanged(mCondition, false); + verify(callback).onConditionChanged(mCondition); + assertThat(mCondition.isConditionMet()).isFalse(); } // Captures and returns the network callback, assuming it is registered with the connectivity diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java index cdffaecadd77..7e1edd2ac193 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java @@ -35,6 +35,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Matchers.anyObject; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; @@ -306,4 +307,11 @@ public class DozeScreenStateTest extends SysuiTestCase { // THEN the display screen state will change assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState); } + + @Test + public void authCallbackRemovedOnDestroy() { + mScreen.destroy(); + + verify(mAuthController).removeCallback(anyObject()); + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java index 8adb55b8d6e2..58ffbfa76328 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -31,14 +31,13 @@ import android.testing.AndroidTestingRunner; import android.view.WindowManager; import android.view.WindowManagerImpl; +import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.settingslib.dream.DreamBackend; import com.android.systemui.SysuiTestCase; -import com.android.systemui.dreams.complication.Complication; import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor; import com.android.systemui.util.concurrency.FakeExecutor; @@ -52,10 +51,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - @SmallTest @RunWith(AndroidTestingRunner.class) public class DreamOverlayServiceTest extends SysuiTestCase { @@ -100,9 +95,6 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Mock DreamOverlayStateController mStateController; - @Mock - DreamBackend mDreamBackend; - DreamOverlayService mService; @Before @@ -118,8 +110,6 @@ public class DreamOverlayServiceTest extends SysuiTestCase { .thenReturn(mLifecycleRegistry); when(mDreamOverlayComponent.getDreamOverlayTouchMonitor()) .thenReturn(mDreamOverlayTouchMonitor); - when(mDreamOverlayComponent.getDreamBackend()) - .thenReturn(mDreamBackend); when(mDreamOverlayComponentFactory .create(any(), any())) .thenReturn(mDreamOverlayComponent); @@ -173,22 +163,12 @@ public class DreamOverlayServiceTest extends SysuiTestCase { } @Test - public void testSetAvailableComplicationTypes() throws Exception { - final Set<Integer> enabledComplications = new HashSet<>( - Arrays.asList(DreamBackend.COMPLICATION_TYPE_TIME, - DreamBackend.COMPLICATION_TYPE_DATE, - DreamBackend.COMPLICATION_TYPE_WEATHER)); - when(mDreamBackend.getEnabledComplications()).thenReturn(enabledComplications); - - final IBinder proxy = mService.onBind(new Intent()); - final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); - - overlay.startDream(mWindowParams, mDreamOverlayCallback); + public void testDestroy() { + mService.onDestroy(); mMainExecutor.runAllReady(); - final int expectedTypes = - Complication.COMPLICATION_TYPE_TIME | Complication.COMPLICATION_TYPE_DATE - | Complication.COMPLICATION_TYPE_WEATHER; - verify(mStateController).setAvailableComplicationTypes(expectedTypes); + verify(mKeyguardUpdateMonitor).removeCallback(any()); + verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED); + verify(mStateController).setOverlayActive(false); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java new file mode 100644 index 000000000000..09976e0e6192 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.database.ContentObserver; +import android.os.UserHandle; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.settingslib.dream.DreamBackend; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.settings.SecureSettings; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.HashSet; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class ComplicationTypesUpdaterTest extends SysuiTestCase { + @Mock + private Context mContext; + @Mock + private DreamBackend mDreamBackend; + @Mock + private SecureSettings mSecureSettings; + @Mock + private DreamOverlayStateController mDreamOverlayStateController; + @Captor + private ArgumentCaptor<ContentObserver> mSettingsObserverCaptor; + + private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); + + private ComplicationTypesUpdater mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>()); + + mController = new ComplicationTypesUpdater(mContext, mDreamBackend, mExecutor, + mSecureSettings, mDreamOverlayStateController); + } + + @Test + public void testPushUpdateToDreamOverlayStateControllerImmediatelyOnStart() { + // DreamOverlayStateController shouldn't be updated before start(). + verify(mDreamOverlayStateController, never()).setAvailableComplicationTypes(anyInt()); + + mController.start(); + mExecutor.runAllReady(); + + // DreamOverlayStateController updated immediately on start(). + verify(mDreamOverlayStateController).setAvailableComplicationTypes(anyInt()); + } + + @Test + public void testPushUpdateToDreamOverlayStateControllerOnChange() { + mController.start(); + mExecutor.runAllReady(); + + when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>(Arrays.asList( + DreamBackend.COMPLICATION_TYPE_TIME, DreamBackend.COMPLICATION_TYPE_WEATHER, + DreamBackend.COMPLICATION_TYPE_AIR_QUALITY))); + final ContentObserver settingsObserver = captureSettingsObserver(); + settingsObserver.onChange(false); + mExecutor.runAllReady(); + + verify(mDreamOverlayStateController).setAvailableComplicationTypes( + Complication.COMPLICATION_TYPE_TIME | Complication.COMPLICATION_TYPE_WEATHER + | Complication.COMPLICATION_TYPE_AIR_QUALITY); + } + + private ContentObserver captureSettingsObserver() { + verify(mSecureSettings).registerContentObserverForUser( + eq(Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS), + mSettingsObserverCaptor.capture(), eq(UserHandle.myUserId())); + return mSettingsObserverCaptor.getValue(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt index 9e67eda57607..57fbbc95efba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt @@ -62,7 +62,7 @@ class DumpHandlerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - dumpHandler = DumpHandler(mContext, dumpManager, logBufferEulogizer) + dumpHandler = DumpHandler(mContext, dumpManager, logBufferEulogizer, mutableMapOf()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 210cb82e1606..a80aed7a6d18 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -18,6 +18,9 @@ package com.android.systemui.keyguard; import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -43,6 +46,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardDisplayManager; +import com.android.keyguard.KeyguardSecurityView; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.mediator.ScreenOnCoordinator; import com.android.systemui.SysuiTestCase; @@ -181,6 +185,24 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager, atLeast(1)).show(null); } + @Test + public void testBouncerPrompt_deviceLockedByAdmin() { + // GIVEN no trust agents enabled and biometrics aren't enrolled + when(mUpdateMonitor.isTrustUsuallyManaged(anyInt())).thenReturn(false); + when(mUpdateMonitor.isUnlockingWithBiometricsPossible(anyInt())).thenReturn(false); + + // WHEN the strong auth reason is AFTER_DPM_LOCK_NOW + KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker = + mock(KeyguardUpdateMonitor.StrongAuthTracker.class); + when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(strongAuthTracker); + when(strongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn( + STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW); + + // THEN the bouncer prompt reason should return PROMPT_REASON_DEVICE_ADMIN + assertEquals(KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN, + mViewMediator.mViewMediatorCallback.getBouncerPromptReason()); + } + private void createAndStartViewMediator() { mViewMediator = new KeyguardViewMediator( mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 609291a983ce..708fc915410c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -74,6 +74,7 @@ private const val SESSION_KEY = "SESSION_KEY" private const val SESSION_ARTIST = "SESSION_ARTIST" private const val SESSION_TITLE = "SESSION_TITLE" private const val USER_ID = 0 +private const val DISABLED_DEVICE_NAME = "DISABLED_DEVICE_NAME" @SmallTest @RunWith(AndroidTestingRunner::class) @@ -131,7 +132,7 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var session: MediaSession private val device = MediaDeviceData(true, null, DEVICE_NAME) - private val disabledDevice = MediaDeviceData(false, null, "Disabled Device") + private val disabledDevice = MediaDeviceData(false, null, DISABLED_DEVICE_NAME) private lateinit var mediaData: MediaData private val clock = FakeSystemClock() @@ -396,13 +397,12 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindDisabledDevice() { seamless.id = 1 - val fallbackString = context.getString(R.string.media_seamless_other_device) player.attachPlayer(holder, MediaViewController.TYPE.PLAYER) val state = mediaData.copy(device = disabledDevice) player.bindPlayer(state, PACKAGE) assertThat(seamless.isEnabled()).isFalse() - assertThat(seamlessText.getText()).isEqualTo(fallbackString) - assertThat(seamless.contentDescription).isEqualTo(fallbackString) + assertThat(seamlessText.getText()).isEqualTo(DISABLED_DEVICE_NAME) + assertThat(seamless.contentDescription).isEqualTo(DISABLED_DEVICE_NAME) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 649ee872c99e..f4fa921703aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -1,6 +1,5 @@ package com.android.systemui.media -import android.app.Notification import android.app.Notification.MediaStyle import android.app.PendingIntent import android.app.smartspace.SmartspaceAction @@ -240,15 +239,14 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testOnNotificationAdded_isRcn_markedRemote() { - val bundle = Bundle().apply { - putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, "Remote Cast Notification") - } val rcn = SbnBuilder().run { setPkg("com.android.systemui") // System package modifyNotification(context).also { it.setSmallIcon(android.R.drawable.ic_media_pause) - it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) - it.addExtras(bundle) + it.setStyle(MediaStyle().apply { + setMediaSession(session.sessionToken) + setRemotePlaybackInfo("Remote device", 0, null) + }) } build() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt index 3d59497fd978..d912a8906ab3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt @@ -30,6 +30,8 @@ import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager +import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -44,6 +46,7 @@ import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.any +import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify @@ -71,6 +74,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Mock private lateinit var lmmFactory: LocalMediaManagerFactory @Mock private lateinit var lmm: LocalMediaManager @Mock private lateinit var mr2: MediaRouter2Manager + @Mock private lateinit var muteAwaitFactory: MediaMuteAwaitConnectionManagerFactory + @Mock private lateinit var muteAwaitManager: MediaMuteAwaitConnectionManager private lateinit var fakeFgExecutor: FakeExecutor private lateinit var fakeBgExecutor: FakeExecutor @Mock private lateinit var dumpster: DumpManager @@ -88,14 +93,22 @@ public class MediaDeviceManagerTest : SysuiTestCase() { fun setUp() { fakeFgExecutor = FakeExecutor(FakeSystemClock()) fakeBgExecutor = FakeExecutor(FakeSystemClock()) - manager = MediaDeviceManager(controllerFactory, lmmFactory, mr2, fakeFgExecutor, - fakeBgExecutor, dumpster) + manager = MediaDeviceManager( + controllerFactory, + lmmFactory, + mr2, + muteAwaitFactory, + fakeFgExecutor, + fakeBgExecutor, + dumpster + ) manager.addListener(listener) // Configure mocks. whenever(device.name).thenReturn(DEVICE_NAME) whenever(device.iconWithoutBackground).thenReturn(icon) whenever(lmmFactory.create(PACKAGE)).thenReturn(lmm) + whenever(muteAwaitFactory.create(lmm)).thenReturn(muteAwaitManager) whenever(lmm.getCurrentConnectedDevice()).thenReturn(device) whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(route) @@ -146,6 +159,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { manager.onMediaDataRemoved(KEY) fakeBgExecutor.runAllReady() verify(lmm).unregisterCallback(any()) + verify(muteAwaitManager).stopListening() } @Test @@ -169,6 +183,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { fakeFgExecutor.runAllReady() // THEN the listener for the old key should removed. verify(lmm).unregisterCallback(any()) + verify(muteAwaitManager).stopListening() // AND a new device event emitted val data = captureDeviceData(KEY, KEY_OLD) assertThat(data.enabled).isTrue() @@ -240,6 +255,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() val deviceCallback = captureCallback() + verify(muteAwaitManager).startListening() // WHEN the device list changes deviceCallback.onDeviceListUpdate(mutableListOf(device)) assertThat(fakeBgExecutor.runAllReady()).isEqualTo(1) @@ -268,6 +284,51 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test + fun onAboutToConnectDeviceChangedWithNonNullParams() { + manager.onMediaDataLoaded(KEY, null, mediaData) + // Run and reset the executors and listeners so we only focus on new events. + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + reset(listener) + + val deviceCallback = captureCallback() + // WHEN the about-to-connect device changes to non-null + val name = "AboutToConnectDeviceName" + val mockIcon = mock(Drawable::class.java) + deviceCallback.onAboutToConnectDeviceChanged(name, mockIcon) + assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) + // THEN the about-to-connect device is returned + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(name) + assertThat(data.icon).isEqualTo(mockIcon) + } + + @Test + fun onAboutToConnectDeviceChangedWithNullParams() { + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + val deviceCallback = captureCallback() + // First set a non-null about-to-connect device + deviceCallback.onAboutToConnectDeviceChanged( + "AboutToConnectDeviceName", mock(Drawable::class.java) + ) + // Run and reset the executors and listeners so we only focus on new events. + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + reset(listener) + + // WHEN the about-to-connect device changes to null + deviceCallback.onAboutToConnectDeviceChanged(null, null) + assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) + // THEN the normal device is returned + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(DEVICE_NAME) + assertThat(data.icon).isEqualTo(icon) + } + + @Test fun listenerReceivesKeyRemoved() { manager.onMediaDataLoaded(KEY, null, mediaData) // WHEN the notification is removed @@ -376,6 +437,24 @@ public class MediaDeviceManagerTest : SysuiTestCase() { verify(mr2, never()).getRoutingSessionForMediaController(eq(controller)) } + @Test + fun testRemotePlaybackDeviceOverride() { + whenever(route.name).thenReturn(DEVICE_NAME) + val deviceData = MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null) + val mediaDataWithDevice = mediaData.copy(device = deviceData) + + // GIVEN media data that already has a device set + manager.onMediaDataLoaded(KEY, null, mediaDataWithDevice) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + + // THEN we keep the device info, and don't register a listener + val data = captureDeviceData(KEY) + assertThat(data.enabled).isFalse() + assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) + verify(lmm, never()).registerCallback(any()) + } + fun captureCallback(): LocalMediaManager.DeviceCallback { val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java) verify(lmm).registerCallback(captor.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index 05769875ae11..bdc311725880 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -201,7 +201,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { assertThat(devices.containsAll(mMediaDevices)).isTrue(); assertThat(devices.size()).isEqualTo(mMediaDevices.size()); - verify(mCb).onRouteChanged(); + verify(mCb).onDeviceListChanged(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt new file mode 100644 index 000000000000..88c451499d21 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.muteawait + +import android.content.Context +import android.graphics.drawable.Drawable +import android.media.AudioAttributes.USAGE_MEDIA +import android.media.AudioAttributes.USAGE_UNKNOWN +import android.media.AudioDeviceAttributes +import android.media.AudioDeviceInfo +import android.media.AudioManager +import android.media.AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION +import android.test.suitebuilder.annotation.SmallTest +import com.android.settingslib.media.DeviceIconUtil +import com.android.settingslib.media.LocalMediaManager +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.time.FakeSystemClock +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + + +@SmallTest +class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() { + private lateinit var muteAwaitConnectionManager: MediaMuteAwaitConnectionManager + @Mock + private lateinit var audioManager: AudioManager + @Mock + private lateinit var deviceIconUtil: DeviceIconUtil + @Mock + private lateinit var localMediaManager: LocalMediaManager + private lateinit var icon: Drawable + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + context.addMockSystemService(Context.AUDIO_SERVICE, audioManager) + icon = context.getDrawable(R.drawable.ic_cake)!! + whenever(deviceIconUtil.getIconFromAudioDeviceType(any(), any())).thenReturn(icon) + + muteAwaitConnectionManager = MediaMuteAwaitConnectionManager( + FakeExecutor(FakeSystemClock()), + localMediaManager, + context, + deviceIconUtil + ) + } + + @Test + fun constructor_audioManagerCallbackNotRegistered() { + verify(audioManager, never()).registerMuteAwaitConnectionCallback(any(), any()) + } + + @Test + fun startListening_audioManagerCallbackRegistered() { + muteAwaitConnectionManager.startListening() + + verify(audioManager).registerMuteAwaitConnectionCallback(any(), any()) + } + + @Test + fun stopListening_audioManagerCallbackUnregistered() { + muteAwaitConnectionManager.stopListening() + + verify(audioManager).unregisterMuteAwaitConnectionCallback(any()) + } + + @Test + fun startListening_audioManagerHasNoMuteAwaitDevice_localMediaMangerNotNotified() { + whenever(audioManager.mutingExpectedDevice).thenReturn(null) + + muteAwaitConnectionManager.startListening() + + verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any()) + } + + @Test + fun startListening_audioManagerHasMuteAwaitDevice_localMediaMangerNotified() { + whenever(audioManager.mutingExpectedDevice).thenReturn(DEVICE) + + muteAwaitConnectionManager.startListening() + + verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon)) + } + + @Test + fun onMutedUntilConnection_notUsageMedia_localMediaManagerNotNotified() { + muteAwaitConnectionManager.startListening() + val muteAwaitListener = getMuteAwaitListener() + + muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_UNKNOWN)) + + verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any()) + } + + @Test + fun onMutedUntilConnection_isUsageMedia_localMediaManagerNotified() { + muteAwaitConnectionManager.startListening() + val muteAwaitListener = getMuteAwaitListener() + + + muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA)) + + verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon)) + } + + @Test + fun onUnmutedEvent_noDeviceMutedBefore_localMediaManagerNotNotified() { + muteAwaitConnectionManager.startListening() + val muteAwaitListener = getMuteAwaitListener() + + muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA)) + + verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any()) + } + + @Test + fun onUnmutedEvent_notSameDevice_localMediaManagerNotNotified() { + muteAwaitConnectionManager.startListening() + val muteAwaitListener = getMuteAwaitListener() + muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA)) + reset(localMediaManager) + + val otherDevice = AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_USB_HEADSET, + "address", + "DifferentName", + listOf(), + listOf(), + ) + muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, otherDevice, intArrayOf(USAGE_MEDIA)) + + verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any()) + } + + @Test + fun onUnmutedEvent_notUsageMedia_localMediaManagerNotNotified() { + muteAwaitConnectionManager.startListening() + val muteAwaitListener = getMuteAwaitListener() + muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA)) + reset(localMediaManager) + + muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_UNKNOWN)) + + verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any()) + } + + @Test + fun onUnmutedEvent_sameDeviceAndUsageMedia_localMediaManagerNotified() { + muteAwaitConnectionManager.startListening() + val muteAwaitListener = getMuteAwaitListener() + muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA)) + reset(localMediaManager) + + muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA)) + + verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(null), eq(null)) + } + + private fun getMuteAwaitListener(): AudioManager.MuteAwaitConnectionCallback { + val listenerCaptor = ArgumentCaptor.forClass( + AudioManager.MuteAwaitConnectionCallback::class.java + ) + verify(audioManager).registerMuteAwaitConnectionCallback(any(), listenerCaptor.capture()) + return listenerCaptor.value!! + } +} + +private const val DEVICE_NAME = "DeviceName" +private val DEVICE = AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_USB_HEADSET, + "address", + DEVICE_NAME, + listOf(), + listOf(), +) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt index cb05d0302698..14afecebd1f6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt @@ -203,7 +203,9 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { verify(statusBarManager).updateMediaTapToTransferReceiverDisplay( eq(StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER), - any() + any(), + nullable(), + nullable() ) } @@ -213,7 +215,9 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { verify(statusBarManager).updateMediaTapToTransferReceiverDisplay( eq(StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER), - any() + any(), + nullable(), + nullable() ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt index 242fd194fd72..f05d621eef3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt @@ -92,16 +92,16 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { fun setIcon_viewHasIconAndContentDescription() { controllerCommon.displayChip(getState()) val chipView = getChipView() - val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context) - val contentDescription = "test description" - controllerCommon.setIcon(MediaTttChipState(drawable, contentDescription), chipView) + val state = MediaTttChipState(PACKAGE_NAME) + controllerCommon.setIcon(state, chipView) - assertThat(chipView.getAppIconView().drawable).isEqualTo(drawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(contentDescription) + assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) + assertThat(chipView.getAppIconView().contentDescription) + .isEqualTo(state.getAppName(context)) } - private fun getState() = MediaTttChipState(appIconDrawable, APP_ICON_CONTENT_DESCRIPTION) + private fun getState() = MediaTttChipState(PACKAGE_NAME) private fun getChipView(): ViewGroup { val viewCaptor = ArgumentCaptor.forClass(View::class.java) @@ -122,4 +122,4 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { } } -private const val APP_ICON_CONTENT_DESCRIPTION = "Content description" +private const val PACKAGE_NAME = "com.android.systemui" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index fce495470ab0..44f691c3f698 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.media.taptotransfer.receiver import android.app.StatusBarManager import android.graphics.drawable.Icon import android.media.MediaRoute2Info +import android.os.Handler import android.view.View import android.view.ViewGroup import android.view.WindowManager @@ -52,7 +53,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - controllerReceiver = MediaTttChipControllerReceiver(commandQueue, context, windowManager) + controllerReceiver = MediaTttChipControllerReceiver( + commandQueue, context, windowManager, Handler.getMain()) val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) verify(commandQueue).addCallback(callbackCaptor.capture()) @@ -61,19 +63,24 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Test fun commandQueueCallback_closeToSender_triggersChip() { + val appName = "FakeAppName" commandQueueCallback.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, - routeInfo + routeInfo, + /* appIcon= */ null, + appName ) - assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(ROUTE_NAME) + assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(appName) } @Test fun commandQueueCallback_farFromSender_noChipShown() { commandQueueCallback.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, - routeInfo + routeInfo, + null, + null ) verify(windowManager, never()).addView(any(), any()) @@ -83,12 +90,16 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { fun commandQueueCallback_closeThenFar_chipShownThenHidden() { commandQueueCallback.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, - routeInfo + routeInfo, + null, + null ) commandQueueCallback.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, - routeInfo + routeInfo, + null, + null ) val viewCaptor = ArgumentCaptor.forClass(View::class.java) @@ -97,14 +108,43 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { } @Test - fun displayChip_chipContainsIcon() { + fun displayChip_nullAppIconDrawable_iconIsFromPackageName() { + val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, "appName") + + controllerReceiver.displayChip(state) + + assertThat(getChipView().getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) + + } + + @Test + fun displayChip_hasAppIconDrawable_iconIsDrawable() { val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context) - val contentDescription = "Test description" + val state = ChipStateReceiver(PACKAGE_NAME, drawable, "appName") - controllerReceiver.displayChip(ChipStateReceiver(drawable, contentDescription)) + controllerReceiver.displayChip(state) assertThat(getChipView().getAppIconView().drawable).isEqualTo(drawable) - assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(contentDescription) + } + + @Test + fun displayChip_nullAppName_iconContentDescriptionIsFromPackageName() { + val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, appName = null) + + controllerReceiver.displayChip(state) + + assertThat(getChipView().getAppIconView().contentDescription) + .isEqualTo(state.getAppName(context)) + } + + @Test + fun displayChip_hasAppName_iconContentDescriptionIsAppNameOverride() { + val appName = "FakeAppName" + val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, appName) + + controllerReceiver.displayChip(state) + + assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(appName) } private fun getChipView(): ViewGroup { @@ -116,7 +156,9 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) } -private const val ROUTE_NAME = "Test name" -private val routeInfo = MediaRoute2Info.Builder("id", ROUTE_NAME) +private const val PACKAGE_NAME = "com.android.systemui" + +private val routeInfo = MediaRoute2Info.Builder("id", "Test route name") .addFeature("feature") + .setPackageName(PACKAGE_NAME) .build() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index c74ac64656ad..dc39893d421b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -17,8 +17,6 @@ package com.android.systemui.media.taptotransfer.sender import android.app.StatusBarManager -import android.graphics.drawable.Drawable -import android.graphics.drawable.Icon import android.media.MediaRoute2Info import android.view.View import android.view.WindowManager @@ -44,8 +42,6 @@ import org.mockito.MockitoAnnotations @SmallTest @Ignore("b/216286227") class MediaTttChipControllerSenderTest : SysuiTestCase() { - private lateinit var appIconDrawable: Drawable - private lateinit var controllerSender: MediaTttChipControllerSender @Mock @@ -57,7 +53,6 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context) controllerSender = MediaTttChipControllerSender(commandQueue, context, windowManager) val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) @@ -197,8 +192,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(state) val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) + assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) + assertThat(chipView.getAppIconView().contentDescription) + .isEqualTo(state.getAppName(context)) assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) @@ -211,8 +207,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(state) val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) + assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) + assertThat(chipView.getAppIconView().contentDescription) + .isEqualTo(state.getAppName(context)) assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) @@ -225,8 +222,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(state) val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) + assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) + assertThat(chipView.getAppIconView().contentDescription) + .isEqualTo(state.getAppName(context)) assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) @@ -239,8 +237,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(state) val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) + assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) + assertThat(chipView.getAppIconView().contentDescription) + .isEqualTo(state.getAppName(context)) assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) @@ -253,8 +252,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(state) val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) + assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) + assertThat(chipView.getAppIconView().contentDescription) + .isEqualTo(state.getAppName(context)) assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) @@ -314,8 +314,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(state) val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) + assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) + assertThat(chipView.getAppIconView().contentDescription) + .isEqualTo(state.getAppName(context)) assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) @@ -375,8 +376,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(state) val chipView = getChipView() - assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) - assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) + assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) + assertThat(chipView.getAppIconView().contentDescription) + .isEqualTo(state.getAppName(context)) assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) @@ -449,39 +451,40 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { /** Helper method providing default parameters to not clutter up the tests. */ private fun almostCloseToStartCast() = - AlmostCloseToStartCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) + AlmostCloseToStartCast(PACKAGE_NAME, DEVICE_NAME) /** Helper method providing default parameters to not clutter up the tests. */ private fun almostCloseToEndCast() = - AlmostCloseToEndCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) + AlmostCloseToEndCast(PACKAGE_NAME, DEVICE_NAME) /** Helper method providing default parameters to not clutter up the tests. */ private fun transferToReceiverTriggered() = - TransferToReceiverTriggered(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) + TransferToReceiverTriggered(PACKAGE_NAME, DEVICE_NAME) /** Helper method providing default parameters to not clutter up the tests. */ private fun transferToThisDeviceTriggered() = - TransferToThisDeviceTriggered(appIconDrawable, APP_ICON_CONTENT_DESC) + TransferToThisDeviceTriggered(PACKAGE_NAME) /** Helper method providing default parameters to not clutter up the tests. */ private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) = TransferToReceiverSucceeded( - appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback + PACKAGE_NAME, DEVICE_NAME, undoCallback ) /** Helper method providing default parameters to not clutter up the tests. */ private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) = TransferToThisDeviceSucceeded( - appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback + PACKAGE_NAME, DEVICE_NAME, undoCallback ) /** Helper method providing default parameters to not clutter up the tests. */ - private fun transferFailed() = TransferFailed(appIconDrawable, APP_ICON_CONTENT_DESC) + private fun transferFailed() = TransferFailed(PACKAGE_NAME) } private const val DEVICE_NAME = "My Tablet" -private const val APP_ICON_CONTENT_DESC = "Content description" +private const val PACKAGE_NAME = "com.android.systemui" private val routeInfo = MediaRoute2Info.Builder("id", "Test Name") .addFeature("feature") + .setPackageName(PACKAGE_NAME) .build() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java deleted file mode 100644 index 84776c7eb18f..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * 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.systemui.qs; - -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.testing.TestableLooper.RunWithLooper; -import android.testing.ViewUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.FrameLayout; - -import androidx.test.filters.SmallTest; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.testing.UiEventLoggerFake; -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.DetailAdapter; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidTestingRunner.class) -@RunWithLooper -@SmallTest -public class QSDetailTest extends SysuiTestCase { - - private MetricsLogger mMetricsLogger; - private QSDetail mQsDetail; - private QSPanelController mQsPanelController; - private QuickStatusBarHeader mQuickHeader; - private ActivityStarter mActivityStarter; - private DetailAdapter mMockDetailAdapter; - private TestableLooper mTestableLooper; - private UiEventLoggerFake mUiEventLogger; - private FrameLayout mParent; - - @Before - public void setup() throws Exception { - mTestableLooper = TestableLooper.get(this); - mUiEventLogger = QSEvents.INSTANCE.setLoggerForTesting(); - - mParent = new FrameLayout(mContext); - mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class); - mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class); - LayoutInflater.from(mContext).inflate(R.layout.qs_detail, mParent); - mQsDetail = (QSDetail) mParent.getChildAt(0); - - mQsPanelController = mock(QSPanelController.class); - mQuickHeader = mock(QuickStatusBarHeader.class); - mQsDetail.setQsPanel(mQsPanelController, mQuickHeader, mock(QSFooter.class), - mock(FalsingManager.class)); - mQsDetail.mClipper = mock(QSDetailClipper.class); - - mMockDetailAdapter = mock(DetailAdapter.class); - when(mMockDetailAdapter.createDetailView(any(), any(), any())) - .thenReturn(new View(mContext)); - - // Only detail in use is the user detail - when(mMockDetailAdapter.openDetailEvent()) - .thenReturn(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN); - when(mMockDetailAdapter.closeDetailEvent()) - .thenReturn(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE); - when(mMockDetailAdapter.moreSettingsEvent()) - .thenReturn(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS); - ViewUtils.attachView(mParent); - } - - @After - public void tearDown() { - QSEvents.INSTANCE.resetLogger(); - mTestableLooper.processAllMessages(); - ViewUtils.detachView(mParent); - } - - @Test - public void testShowDetail_Metrics() { - mTestableLooper.processAllMessages(); - - mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false); - verify(mMetricsLogger).visible(eq(mMockDetailAdapter.getMetricsCategory())); - assertEquals(1, mUiEventLogger.numLogs()); - assertEquals(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN.getId(), mUiEventLogger.eventId(0)); - mUiEventLogger.getLogs().clear(); - - mQsDetail.handleShowingDetail(null, 0, 0, false); - verify(mMetricsLogger).hidden(eq(mMockDetailAdapter.getMetricsCategory())); - - assertEquals(1, mUiEventLogger.numLogs()); - assertEquals(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE.getId(), mUiEventLogger.eventId(0)); - } - - @Test - public void testShowDetail_ShouldAnimate() { - mTestableLooper.processAllMessages(); - - when(mMockDetailAdapter.shouldAnimate()).thenReturn(true); - mQsDetail.setFullyExpanded(true); - - mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false); - verify(mQsDetail.mClipper).updateCircularClip(eq(true) /* animate */, anyInt(), anyInt(), - eq(true) /* in */, any()); - clearInvocations(mQsDetail.mClipper); - - mQsDetail.handleShowingDetail(null, 0, 0, false); - verify(mQsDetail.mClipper).updateCircularClip(eq(true) /* animate */, anyInt(), anyInt(), - eq(false) /* in */, any()); - } - - @Test - public void testShowDetail_ShouldNotAnimate() { - mTestableLooper.processAllMessages(); - - when(mMockDetailAdapter.shouldAnimate()).thenReturn(false); - mQsDetail.setFullyExpanded(true); - - mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false); - verify(mQsDetail.mClipper).updateCircularClip(eq(false) /* animate */, anyInt(), anyInt(), - eq(true) /* in */, any()); - clearInvocations(mQsDetail.mClipper); - - // Detail adapters should always animate on close. shouldAnimate() should only affect the - // open transition - mQsDetail.handleShowingDetail(null, 0, 0, false); - verify(mQsDetail.mClipper).updateCircularClip(eq(true) /* animate */, anyInt(), anyInt(), - eq(false) /* in */, any()); - } - - @Test - public void testDoneButton_CloseDetailPanel() { - mTestableLooper.processAllMessages(); - - when(mMockDetailAdapter.onDoneButtonClicked()).thenReturn(false); - - mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false); - mQsDetail.requireViewById(android.R.id.button1).performClick(); - verify(mQsPanelController).closeDetail(); - } - - @Test - public void testDoneButton_KeepDetailPanelOpen() { - mTestableLooper.processAllMessages(); - - when(mMockDetailAdapter.onDoneButtonClicked()).thenReturn(true); - - mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false); - mQsDetail.requireViewById(android.R.id.button1).performClick(); - verify(mQsPanelController, never()).closeDetail(); - } - - @Test - public void testMoreSettingsButton() { - mTestableLooper.processAllMessages(); - - mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false); - mUiEventLogger.getLogs().clear(); - mQsDetail.requireViewById(android.R.id.button2).performClick(); - - int metricsCategory = mMockDetailAdapter.getMetricsCategory(); - verify(mMetricsLogger).action(eq(ACTION_QS_MORE_SETTINGS), eq(metricsCategory)); - assertEquals(1, mUiEventLogger.numLogs()); - assertEquals(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS.getId(), mUiEventLogger.eventId(0)); - - verify(mActivityStarter).postStartActivityDismissingKeyguard(any(), anyInt()); - } - - @Test - public void testNullAdapterClick() { - DetailAdapter mock = mock(DetailAdapter.class); - when(mock.moreSettingsEvent()).thenReturn(DetailAdapter.INVALID); - mQsDetail.setupDetailFooter(mock); - mQsDetail.requireViewById(android.R.id.button2).performClick(); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index 3266d6ac84a6..4ab39261e825 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -146,7 +146,6 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class), mock(SecureSettings.class), mock(CustomTileStatePersister.class), mTileServiceRequestControllerBuilder, mock(TileLifecycleManager.Factory.class)); - qs.setHost(host); qs.setListening(true); processAllMessages(); @@ -186,7 +185,6 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { mock(QSTileHost.class), mock(StatusBarStateController.class), commandQueue, - new QSDetailDisplayer(), mQSMediaHost, mQQSMediaHost, mBypassController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java deleted file mode 100644 index b2ca62f18f27..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java +++ /dev/null @@ -1,151 +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.qs; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.res.Configuration; -import android.content.res.Resources; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper.RunWithLooper; - -import androidx.test.filters.SmallTest; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.testing.UiEventLoggerFake; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.classifier.FalsingManagerFake; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.media.MediaHost; -import com.android.systemui.plugins.qs.QSTileView; -import com.android.systemui.qs.customize.QSCustomizerController; -import com.android.systemui.qs.logging.QSLogger; -import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.settings.brightness.BrightnessController; -import com.android.systemui.settings.brightness.BrightnessSliderController; -import com.android.systemui.settings.brightness.ToggleSlider; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.tuner.TunerService; -import com.android.systemui.util.animation.DisappearParameters; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Collections; - -@RunWith(AndroidTestingRunner.class) -@RunWithLooper -@SmallTest -public class QSPanelControllerTest extends SysuiTestCase { - - @Mock - private QSPanel mQSPanel; - @Mock - private QSTileHost mQSTileHost; - @Mock - private QSCustomizerController mQSCustomizerController; - @Mock - private QSTileRevealController.Factory mQSTileRevealControllerFactory; - @Mock - private QSTileRevealController mQSTileRevealController; - @Mock - private MediaHost mMediaHost; - @Mock - private MetricsLogger mMetricsLogger; - private UiEventLogger mUiEventLogger = new UiEventLoggerFake(); - private DumpManager mDumpManager = new DumpManager(); - @Mock - private TunerService mTunerService; - @Mock - private QSFgsManagerFooter mQSFgsManagerFooter; - @Mock - private QSSecurityFooter mQSSecurityFooter; - @Mock - private QSLogger mQSLogger; - @Mock - private BrightnessController.Factory mBrightnessControllerFactory; - @Mock - private BrightnessController mBrightnessController; - @Mock - private BrightnessSliderController.Factory mToggleSliderViewControllerFactory; - @Mock - private BrightnessSliderController mBrightnessSliderController; - @Mock - QSTileImpl mQSTile; - @Mock - QSTileView mQSTileView; - @Mock - PagedTileLayout mPagedTileLayout; - @Mock - CommandQueue mCommandQueue; - FalsingManagerFake mFalsingManager = new FalsingManagerFake(); - @Mock - Resources mResources; - @Mock - Configuration mConfiguration; - @Mock - FeatureFlags mFeatureFlags; - - private QSPanelController mController; - - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - - when(mQSPanel.isAttachedToWindow()).thenReturn(true); - when(mQSPanel.getDumpableTag()).thenReturn("QSPanel"); - when(mQSPanel.getOrCreateTileLayout()).thenReturn(mPagedTileLayout); - when(mQSPanel.getTileLayout()).thenReturn(mPagedTileLayout); - when(mQSPanel.getResources()).thenReturn(mResources); - when(mResources.getConfiguration()).thenReturn(mConfiguration); - when(mQSTileHost.getTiles()).thenReturn(Collections.singleton(mQSTile)); - when(mQSTileHost.createTileView(any(), eq(mQSTile), anyBoolean())).thenReturn(mQSTileView); - when(mToggleSliderViewControllerFactory.create(any(), any())) - .thenReturn(mBrightnessSliderController); - when(mBrightnessControllerFactory.create(any(ToggleSlider.class))) - .thenReturn(mBrightnessController); - when(mQSTileRevealControllerFactory.create(any(), any())) - .thenReturn(mQSTileRevealController); - when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters()); - - mController = new QSPanelController(mQSPanel, mQSFgsManagerFooter, mQSSecurityFooter, - mTunerService, mQSTileHost, mQSCustomizerController, true, mMediaHost, - mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger, - mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory, - mFalsingManager, mCommandQueue, mFeatureFlags - ); - - mController.init(); - } - - @Test - public void testOpenDetailsWithNonExistingTile_NoException() { - mController.openDetails("none"); - - verify(mQSPanel, never()).openDetails(any()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt index 4ae193327b7b..5213a30cfd59 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt @@ -36,10 +36,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock -import org.mockito.Mockito.never -import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever @@ -64,9 +61,6 @@ class QSPanelTest : SysuiTestCase() { private lateinit var mParentView: ViewGroup @Mock - private lateinit var mCallback: QSDetail.Callback - - @Mock private lateinit var mQSTileView: QSTileView private lateinit var mFooter: View @@ -97,26 +91,7 @@ class QSPanelTest : SysuiTestCase() { whenever(mHost.tiles).thenReturn(emptyList()) whenever(mHost.createTileView(any(), any(), anyBoolean())).thenReturn(mQSTileView) mQsPanel.addTile(mDndTileRecord) - mQsPanel.setCallback(mCallback) - } - } - - @Test - fun testOpenDetailsWithExistingTile_NoException() { - mTestableLooper.runWithLooper { - mQsPanel.openDetails(dndTile) } - - verify(mCallback).onShowingDetail(any(), anyInt(), anyInt()) - } - - @Test - fun testOpenDetailsWithNullParameter_NoException() { - mTestableLooper.runWithLooper { - mQsPanel.openDetails(null) - } - - verify(mCallback, never()).onShowingDetail(any(), anyInt(), anyInt()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSBrightnessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSBrightnessControllerTest.kt deleted file mode 100644 index de1d86b08785..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSBrightnessControllerTest.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs - -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.settings.brightness.BrightnessController -import com.android.systemui.statusbar.policy.BrightnessMirrorController -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.mockito.Mock -import org.mockito.Mockito.times -import org.mockito.Mockito.verify -import org.mockito.Mockito.never -import org.mockito.Mockito.mock -import org.mockito.junit.MockitoJUnit - -@SmallTest -class QuickQSBrightnessControllerTest : SysuiTestCase() { - - @Mock - lateinit var brightnessController: BrightnessController - @get:Rule - val mockito = MockitoJUnit.rule() - - lateinit var quickQSBrightnessController: QuickQSBrightnessController - - @Before - fun setUp() { - quickQSBrightnessController = QuickQSBrightnessController( - brightnessControllerFactory = { brightnessController }) - } - - @Test - fun testSliderIsShownWhenInitializedInSplitShade() { - quickQSBrightnessController.init(shouldUseSplitNotificationShade = true) - - verify(brightnessController).showSlider() - } - - @Test - fun testSliderIsShownWhenRefreshedInSplitShade() { - quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true) - - verify(brightnessController, times(1)).showSlider() - } - - @Test - fun testSliderIsHiddenWhenRefreshedInNonSplitShade() { - // needs to be shown first - quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true) - quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = false) - - verify(brightnessController).hideSlider() - } - - @Test - fun testSliderChangesVisibilityWhenRotating() { - quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true) - verify(brightnessController, times(1)).showSlider() - - quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = false) - verify(brightnessController, times(1)).hideSlider() - } - - @Test - fun testCallbacksAreRegisteredOnlyOnce() { - // this flow simulates expanding shade in portrait... - quickQSBrightnessController.setListening(true) - quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = false) - // ... and rotating to landscape/split shade where slider is visible - quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true) - - verify(brightnessController, times(1)).registerCallbacks() - } - - @Test - fun testCallbacksAreRegisteredOnlyOnceWhenRotatingPhone() { - quickQSBrightnessController.setListening(true) - quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true) - quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = false) - quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true) - - verify(brightnessController, times(1)).registerCallbacks() - } - - @Test - fun testCallbacksAreNotRegisteredWhenSliderNotVisible() { - quickQSBrightnessController.setListening(true) - quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = false) - - verify(brightnessController, never()).registerCallbacks() - } - - @Test - fun testMirrorIsSetWhenSliderIsShown() { - val mirrorController = mock(BrightnessMirrorController::class.java) - quickQSBrightnessController.setMirror(mirrorController) - quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true) - - verify(brightnessController).setMirror(mirrorController) - } -}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt index 1eb16fd64b85..62915b8ac7c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt @@ -71,8 +71,6 @@ class QuickQSPanelControllerTest : SysuiTestCase() { private lateinit var tileLayout: TileLayout @Mock private lateinit var tileView: QSTileView - @Mock - private lateinit var quickQsBrightnessController: QuickQSBrightnessController @Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener> @@ -100,8 +98,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() { metricsLogger, uiEventLogger, qsLogger, - dumpManager, - quickQsBrightnessController + dumpManager ) controller.init() @@ -133,16 +130,6 @@ class QuickQSPanelControllerTest : SysuiTestCase() { } @Test - fun testBrightnessRefreshedWhenConfigurationChanged() { - // times(2) because both controller and base controller are registering their listeners - verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture()) - - captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) } - - verify(quickQsBrightnessController).refreshVisibility(anyBoolean()) - } - - @Test fun testMediaExpansionUpdatedWhenConfigurationChanged() { `when`(mediaFlags.useMediaSessionLayout()).thenReturn(true) @@ -171,11 +158,10 @@ class QuickQSPanelControllerTest : SysuiTestCase() { metricsLogger: MetricsLogger, uiEventLogger: UiEventLoggerFake, qsLogger: QSLogger, - dumpManager: DumpManager, - quickQSBrightnessController: QuickQSBrightnessController + dumpManager: DumpManager ) : QuickQSPanelController(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, usingCollapsedLandscapeMedia, mediaFlags, metricsLogger, uiEventLogger, qsLogger, - dumpManager, quickQSBrightnessController) { + dumpManager) { private var rotation = RotationUtils.ROTATION_NONE diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index 8b7346dd9e6a..30b464b2557c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java @@ -53,7 +53,6 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.InstanceId; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSTileHost; @@ -420,11 +419,5 @@ public class TileQueryHelperTest extends SysuiTestCase { @Override public void destroy() {} - - - @Override - public DetailAdapter getDetailAdapter() { - return null; - } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt index a1c60a648de9..bdfbca47e569 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt @@ -20,6 +20,7 @@ import android.app.StatusBarManager import android.content.ComponentName import android.content.DialogInterface import android.graphics.drawable.Icon +import android.os.RemoteException import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId @@ -275,11 +276,89 @@ class TileServiceRequestControllerTest : SysuiTestCase() { assertThat(c.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED) } + @Test + fun interfaceThrowsRemoteException_doesntCrash() { + val cancelListenerCaptor = + ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java) + val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) + verify(commandQueue, atLeastOnce()).addCallback(capture(captor)) + + val callback = object : IAddTileResultCallback.Stub() { + override fun onTileRequest(p0: Int) { + throw RemoteException() + } + } + captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor)) + + cancelListenerCaptor.value.onCancel(tileRequestDialog) + } + + @Test + fun testDismissDialogResponse() { + val dismissListenerCaptor = + ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java) + + val callback = Callback() + controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor)) + + dismissListenerCaptor.value.onDismiss(tileRequestDialog) + assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED) + } + + @Test + fun addTileAndThenDismissSendsOnlyAddTile() { + // After clicking, the dialog is dismissed. This tests that only one response + // is sent (the first one) + val dismissListenerCaptor = + ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java) + val clickListenerCaptor = + ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) + + val callback = Callback() + controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor)) + verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor)) + + clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE) + dismissListenerCaptor.value.onDismiss(tileRequestDialog) + + assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.ADD_TILE) + assertThat(callback.timesCalled).isEqualTo(1) + } + + @Test + fun cancelAndThenDismissSendsOnlyOnce() { + // After cancelling, the dialog is dismissed. This tests that only one response + // is sent. + val dismissListenerCaptor = + ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java) + val cancelListenerCaptor = + ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java) + + val callback = Callback() + controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor)) + verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor)) + + cancelListenerCaptor.value.onCancel(tileRequestDialog) + dismissListenerCaptor.value.onDismiss(tileRequestDialog) + + assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED) + assertThat(callback.timesCalled).isEqualTo(1) + } + private class Callback : IAddTileResultCallback.Stub(), Consumer<Int> { var lastAccepted: Int? = null private set + + var timesCalled = 0 + private set + override fun accept(t: Int) { lastAccepted = t + timesCalled++ } override fun onTileRequest(r: Int) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt index 88b133ec56a8..0f2c2647f20f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt @@ -48,7 +48,6 @@ import com.android.systemui.qs.tiles.ReduceBrightColorsTile import com.android.systemui.qs.tiles.RotationLockTile import com.android.systemui.qs.tiles.ScreenRecordTile import com.android.systemui.qs.tiles.UiModeNightTile -import com.android.systemui.qs.tiles.UserTile import com.android.systemui.qs.tiles.WifiTile import com.android.systemui.qs.tiles.WorkModeTile import com.android.systemui.util.leak.GarbageMonitor @@ -76,7 +75,6 @@ private val specMap = mapOf( "location" to LocationTile::class.java, "cast" to CastTile::class.java, "hotspot" to HotspotTile::class.java, - "user" to UserTile::class.java, "battery" to BatterySaverTile::class.java, "saver" to DataSaverTile::class.java, "night" to NightDisplayTile::class.java, @@ -115,7 +113,6 @@ class QSFactoryImplTest : SysuiTestCase() { @Mock private lateinit var locationTile: LocationTile @Mock private lateinit var castTile: CastTile @Mock private lateinit var hotspotTile: HotspotTile - @Mock private lateinit var userTile: UserTile @Mock private lateinit var batterySaverTile: BatterySaverTile @Mock private lateinit var dataSaverTile: DataSaverTile @Mock private lateinit var nightDisplayTile: NightDisplayTile @@ -159,7 +156,6 @@ class QSFactoryImplTest : SysuiTestCase() { { locationTile }, { castTile }, { hotspotTile }, - { userTile }, { batterySaverTile }, { dataSaverTile }, { nightDisplayTile }, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt index 3a3d1546984d..9b0142d6a8fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt @@ -147,5 +147,6 @@ class UserDetailViewAdapterTest : SysuiTestCase() { current, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index c20e88708f40..ed35dcbbcfab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -29,6 +29,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; import com.android.wifitrackerlib.WifiEntry; @@ -68,6 +69,8 @@ public class InternetDialogTest extends SysuiTestCase { private InternetAdapter mInternetAdapter; @Mock private InternetDialogController mInternetDialogController; + @Mock + private KeyguardStateController mKeyguard; private FakeExecutor mBgExecutor = new FakeExecutor(new FakeSystemClock()); private InternetDialog mInternetDialog; @@ -100,7 +103,7 @@ public class InternetDialogTest extends SysuiTestCase { mInternetDialog = new InternetDialog(mContext, mock(InternetDialogFactory.class), mInternetDialogController, true, true, true, mock(UiEventLogger.class), mHandler, - mBgExecutor); + mBgExecutor, mKeyguard); mInternetDialog.mAdapter = mInternetAdapter; mInternetDialog.mConnectedWifiEntry = mInternetWifiEntry; mInternetDialog.mWifiEntriesCount = mWifiEntries.size(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt index b7fdc1a6cb0d..8695b2990b6a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt @@ -22,11 +22,13 @@ import android.provider.Settings import android.testing.AndroidTestingRunner import android.view.View import androidx.test.filters.SmallTest +import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.PseudoGridView +import com.android.systemui.qs.QSUserSwitcherEvent import com.android.systemui.qs.tiles.UserDetailView import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.mockito.any @@ -62,6 +64,8 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { private lateinit var launchView: View @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + @Mock + private lateinit var uiEventLogger: UiEventLogger @Captor private lateinit var clickCaptor: ArgumentCaptor<DialogInterface.OnClickListener> @@ -79,6 +83,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { activityStarter, falsingManager, dialogLaunchAnimator, + uiEventLogger, { dialog } ) } @@ -87,6 +92,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { fun showDialog_callsDialogShow() { controller.showDialog(launchView) verify(dialogLaunchAnimator).showFromView(dialog, launchView) + verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN) } @Test @@ -108,10 +114,14 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { } @Test - fun doneButtonSetWithNullHandler() { + fun doneButtonLogsCorrectly() { controller.showDialog(launchView) - verify(dialog).setPositiveButton(anyInt(), eq(null)) + verify(dialog).setPositiveButton(anyInt(), capture(clickCaptor)) + + clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL) + + verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE) } @Test @@ -129,6 +139,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { argThat(IntentMatcher(Settings.ACTION_USER_SETTINGS)), eq(0) ) + verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java index 218e7db5e450..711187be33fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/VersionInfoTest.java @@ -22,7 +22,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.OverlayPlugin; import com.android.systemui.plugins.annotations.Requires; -import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.QS.HeightListener; import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException; @@ -99,7 +98,6 @@ public class VersionInfoTest extends SysuiTestCase { @Requires(target = QS.class, version = QS.VERSION) @Requires(target = HeightListener.class, version = HeightListener.VERSION) - @Requires(target = DetailAdapter.class, version = DetailAdapter.VERSION) public static class QSImpl { } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 592432947df2..466d954e7be0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -244,6 +244,9 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verify(mKeyguardStateController).addCallback( mKeyguardStateControllerCallbackCaptor.capture()); mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue(); + + mExecutor.runAllReady(); + reset(mRotateTextViewController); } @Test @@ -328,6 +331,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void disclosure_unmanaged() { createController(); + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false); when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(false); sendUpdateDisclosureBroadcast(); @@ -339,6 +343,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void disclosure_deviceOwner_noOrganizationName() { createController(); + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null); sendUpdateDisclosureBroadcast(); @@ -350,6 +355,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void disclosure_orgOwnedDeviceWithManagedProfile_noOrganizationName() { createController(); + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true); when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList( new UserInfo(10, /* name */ null, /* flags */ FLAG_MANAGED_PROFILE))); @@ -363,6 +369,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void disclosure_deviceOwner_withOrganizationName() { createController(); + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME); sendUpdateDisclosureBroadcast(); @@ -374,6 +381,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void disclosure_orgOwnedDeviceWithManagedProfile_withOrganizationName() { createController(); + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true); when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList( new UserInfo(10, /* name */ null, FLAG_MANAGED_PROFILE))); @@ -386,6 +394,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void disclosure_updateOnTheFly() { + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false); createController(); @@ -416,6 +425,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void disclosure_deviceOwner_financedDeviceWithOrganizationName() { createController(); + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME); when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT)) @@ -738,7 +748,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void testEmptyOwnerInfoHidesIndicationArea() { createController(); - // GIVEN the owner info is set to an empty string + // GIVEN the owner info is set to an empty string & keyguard is showing + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mLockPatternUtils.getDeviceOwnerInfo()).thenReturn(""); // WHEN asked to update the indication area diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java index 8c5f04f92073..5e11858c7653 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java @@ -37,6 +37,7 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -55,6 +56,7 @@ public class NotificationListenerTest extends SysuiTestCase { @Mock private NotificationHandler mNotificationHandler; @Mock private NotificationManager mNotificationManager; + @Mock private PluginManager mPluginManager; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock); @@ -70,7 +72,8 @@ public class NotificationListenerTest extends SysuiTestCase { mContext, mNotificationManager, mFakeSystemClock, - mFakeExecutor); + mFakeExecutor, + mPluginManager); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, new Notification(), UserHandle.CURRENT, null, 0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt index d280f54e32f2..5d160366b27d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt @@ -36,14 +36,14 @@ class NotificationShelfTest : SysuiTestCase() { setFractionToShade(0f) setOnLockscreen(true) - shelf.updateStateWidth(shelfState, /* fraction */ 0f, /* shortestWidth */ 10) - assertTrue(shelfState.actualWidth == 10) + shelf.updateActualWidth(/* fractionToShade */ 0f, /* shortestWidth */ 10f); + assertTrue(shelf.actualWidth == 10) - shelf.updateStateWidth(shelfState, /* fraction */ 0.5f, /* shortestWidth */ 10) - assertTrue(shelfState.actualWidth == 20) + shelf.updateActualWidth(/* fractionToShade */ 0.5f, /* shortestWidth */ 10f) + assertTrue(shelf.actualWidth == 20) - shelf.updateStateWidth(shelfState, /* fraction */ 1f, /* shortestWidth */ 10) - assertTrue(shelfState.actualWidth == 30) + shelf.updateActualWidth(/* fractionToShade */ 1f, /* shortestWidth */ 10f) + assertTrue(shelf.actualWidth == 30) } @Test @@ -51,8 +51,8 @@ class NotificationShelfTest : SysuiTestCase() { setFractionToShade(0f) setOnLockscreen(false) - shelf.updateStateWidth(shelfState, /* fraction */ 0f, /* shortestWidth */ 10) - assertTrue(shelfState.actualWidth == 30) + shelf.updateActualWidth(/* fraction */ 0f, /* shortestWidth */ 10f) + assertTrue(shelf.actualWidth == 30) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index fb232ba3ac2c..ed144fa99369 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -126,10 +126,9 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { .thenReturn(true); when(mAuthController.isUdfpsFingerDown()).thenReturn(false); when(mKeyguardBypassController.canPlaySubtleWindowAnimations()).thenReturn(true); - mContext.addMockSystemService(PowerManager.class, mPowerManager); mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager); res.addOverride(com.android.internal.R.integer.config_wakeUpDelayDoze, 0); - mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController, + mBiometricUnlockController = new BiometricUnlockController(mDozeScrimController, mKeyguardViewMediator, mScrimController, mShadeController, mNotificationShadeWindowController, mKeyguardStateController, mHandler, mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index e9590b06c2c8..ed22cd3c55fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -40,6 +40,7 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Assert; @@ -47,6 +48,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Optional; + @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -76,7 +79,6 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { mDependency, TestableLooper.get(this)); mFirst = testHelper.createRow(); - mDependency.injectTestDependency(DarkIconDispatcher.class, mDarkIconDispatcher); mHeadsUpStatusBarView = new HeadsUpStatusBarView(mContext, mock(View.class), mock(TextView.class)); mHeadsUpManager = mock(HeadsUpManagerPhone.class); @@ -92,13 +94,14 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { mStatusbarStateController, mBypassController, mWakeUpCoordinator, + mDarkIconDispatcher, mKeyguardStateController, mCommandQueue, mStackScrollerController, mPanelView, mHeadsUpStatusBarView, - new View(mContext), - mOperatorNameView); + new Clock(mContext, null), + Optional.of(mOperatorNameView)); mHeadsUpAppearanceController.setAppearFraction(0.0f, 0.0f); } @@ -173,13 +176,14 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { mStatusbarStateController, mBypassController, mWakeUpCoordinator, + mDarkIconDispatcher, mKeyguardStateController, mCommandQueue, mStackScrollerController, mPanelView, mHeadsUpStatusBarView, - new View(mContext), - new View(mContext)); + new Clock(mContext, null), + Optional.empty()); Assert.assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f); Assert.assertEquals(appearFraction, newController.mAppearFraction, 0.0f); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java index 7e33c01572e1..cc4abfcaa42f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java @@ -20,6 +20,8 @@ import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; +import static junit.framework.Assert.assertTrue; + import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -41,6 +43,9 @@ import com.android.systemui.statusbar.policy.BatteryController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +import java.util.ArrayList; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -91,7 +96,9 @@ public class LightBarControllerTest extends SysuiTestCase { mLightBarController.onStatusBarAppearanceChanged( appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT, false /* navbarColorManagedByIme */); - verify(mStatusBarIconController).setIconsDarkArea(eq(firstBounds)); + ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class); + verify(mStatusBarIconController).setIconsDarkArea(captor.capture()); + assertTrue(captor.getValue().contains(firstBounds)); verify(mLightBarTransitionsController).setIconsDark(eq(true), anyBoolean()); } @@ -106,7 +113,29 @@ public class LightBarControllerTest extends SysuiTestCase { mLightBarController.onStatusBarAppearanceChanged( appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT, false /* navbarColorManagedByIme */); - verify(mStatusBarIconController).setIconsDarkArea(eq(secondBounds)); + ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class); + verify(mStatusBarIconController).setIconsDarkArea(captor.capture()); + assertTrue(captor.getValue().contains(secondBounds)); + verify(mLightBarTransitionsController).setIconsDark(eq(true), anyBoolean()); + } + + @Test + public void testOnStatusBarAppearanceChanged_multipleStacks_oneStackLightMultipleStackDark() { + final Rect firstBounds = new Rect(0, 0, 1, 1); + final Rect secondBounds = new Rect(1, 0, 2, 1); + final Rect thirdBounds = new Rect(2, 0, 3, 1); + final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{ + new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, firstBounds), + new AppearanceRegion(0 /* appearance */, secondBounds), + new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds) + }; + mLightBarController.onStatusBarAppearanceChanged( + appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT, + false /* navbarColorManagedByIme */); + ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class); + verify(mStatusBarIconController).setIconsDarkArea(captor.capture()); + assertTrue(captor.getValue().contains(firstBounds)); + assertTrue(captor.getValue().contains(thirdBounds)); verify(mLightBarTransitionsController).setIconsDark(eq(true), anyBoolean()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java index c13b3358a077..7070bc19db62 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java @@ -78,6 +78,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { @Mock private NotificationEntryManager mNotificationEntryManager; @Mock private RowContentBindStage mBindStage; @Mock PeopleNotificationIdentifier mPeopleNotificationIdentifier; + @Mock StatusBarStateController mStatusBarStateController; @Captor private ArgumentCaptor<NotificationEntryListener> mListenerCaptor; private NotificationEntryListener mNotificationEntryListener; private final HashMap<String, NotificationEntry> mPendingEntries = new HashMap<>(); @@ -94,7 +95,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { .thenReturn(mPendingEntries.values()); mGroupManager = new NotificationGroupManagerLegacy( - mock(StatusBarStateController.class), + mStatusBarStateController, () -> mPeopleNotificationIdentifier, Optional.of(mock(Bubbles.class)), mock(DumpManager.class)); @@ -103,7 +104,8 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams()); - mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(mBindStage); + mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper( + mBindStage, mStatusBarStateController, mGroupManager); mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager); mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index 7347565408f5..f6eff8207da3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -106,10 +106,10 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.media.KeyguardMediaController; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; +import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qrcodescanner.controller.QRCodeScannerController; -import com.android.systemui.qs.QSDetailDisplayer; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardAffordanceView; @@ -272,8 +272,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private IdleHostViewController mIdleHostViewController; @Mock - private QSDetailDisplayer mQSDetailDisplayer; - @Mock private KeyguardStatusViewComponent mKeyguardStatusViewComponent; @Mock private KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; @@ -366,6 +364,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Mock private NotificationShadeWindowController mNotificationShadeWindowController; + @Mock + private SysUiState mSysUiState; private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty(); private SysuiStatusBarStateController mStatusBarStateController; private NotificationPanelViewController mNotificationPanelViewController; @@ -525,7 +525,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mCommunalViewComponentFactory, mIdleViewComponentFactory, mLockscreenShadeTransitionController, - mQSDetailDisplayer, mGroupManager, mNotificationAreaController, mAuthController, @@ -555,6 +554,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mControlsComponent, mInteractionJankMonitor, mQsFrameTranslateController, + mSysUiState, mKeyguardUnlockAnimationController); mNotificationPanelViewController.initDependencies( mStatusBar, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt index a57f6a11d22f..4a579cb3fc4d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt @@ -27,26 +27,22 @@ import com.android.internal.logging.UiEventLogger import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.communal.CommunalStateController -import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.ScreenLifecycle import com.android.systemui.plugins.FalsingManager -import com.android.systemui.qs.tiles.UserDetailView import com.android.systemui.qs.user.UserSwitchDialogController import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.LockscreenGestureLogger -import com.android.systemui.statusbar.phone.NotificationPanelViewController import com.android.systemui.statusbar.phone.ScreenOffAnimationController import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.`when` import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -import javax.inject.Provider @SmallTest @TestableLooper.RunWithLooper @@ -77,23 +73,14 @@ class KeyguardQsUserSwitchControllerTest : SysuiTestCase() { private lateinit var dozeParameters: DozeParameters @Mock - private lateinit var userDetailViewAdapterProvider: Provider<UserDetailView.Adapter> - - @Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController @Mock - private lateinit var featureFlags: FeatureFlags - - @Mock private lateinit var userSwitchDialogController: UserSwitchDialogController @Mock private lateinit var uiEventLogger: UiEventLogger - @Mock - private lateinit var notificationPanelViewController: NotificationPanelViewController - private lateinit var view: FrameLayout private lateinit var testableLooper: TestableLooper private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController @@ -118,16 +105,12 @@ class KeyguardQsUserSwitchControllerTest : SysuiTestCase() { configurationController, statusBarStateController, dozeParameters, - userDetailViewAdapterProvider, screenOffAnimationController, - featureFlags, userSwitchDialogController, uiEventLogger) ViewUtils.attachView(view) testableLooper.processAllMessages() - keyguardQsUserSwitchController - .setNotificationPanelViewController(notificationPanelViewController) `when`(userSwitcherController.keyguardStateController).thenReturn(keyguardStateController) `when`(userSwitcherController.keyguardStateController.isShowing).thenReturn(true) keyguardQsUserSwitchController.init() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt index e479882ac50a..0dd6cbb7995a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt @@ -193,5 +193,6 @@ class KeyguardUserSwitcherAdapterTest : SysuiTestCase() { isCurrentUser, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt index 9a7e702152b4..07e0279e0c52 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.policy import android.app.IActivityManager -import android.app.IActivityTaskManager import android.app.admin.DevicePolicyManager import android.content.Context import android.content.DialogInterface @@ -49,13 +48,15 @@ import com.android.systemui.qs.QSUserSwitcherEvent import com.android.systemui.qs.user.UserSwitchDialogController import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.NotificationShadeWindowView +import com.android.systemui.statusbar.phone.ShadeController import com.android.systemui.telephony.TelephonyListenerManager import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.time.FakeSystemClock import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -84,8 +85,6 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Mock private lateinit var userManager: UserManager @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock private lateinit var activityTaskManager: IActivityTaskManager - @Mock private lateinit var userDetailAdapter: UserSwitcherController.UserDetailAdapter @Mock private lateinit var telephonyListenerManager: TelephonyListenerManager @Mock private lateinit var secureSettings: SecureSettings @Mock private lateinit var falsingManager: FalsingManager @@ -96,8 +95,10 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Mock private lateinit var notificationShadeWindowView: NotificationShadeWindowView @Mock private lateinit var threadedRenderer: ThreadedRenderer @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + @Mock private lateinit var shadeController: ShadeController private lateinit var testableLooper: TestableLooper - private lateinit var uiBgExecutor: FakeExecutor + private lateinit var bgExecutor: FakeExecutor + private lateinit var uiExecutor: FakeExecutor private lateinit var uiEventLogger: UiEventLoggerFake private lateinit var userSwitcherController: UserSwitcherController private lateinit var picture: Bitmap @@ -116,10 +117,11 @@ class UserSwitcherControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) - uiBgExecutor = FakeExecutor(FakeSystemClock()) + bgExecutor = FakeExecutor(FakeSystemClock()) + uiExecutor = FakeExecutor(FakeSystemClock()) uiEventLogger = UiEventLoggerFake() - context.orCreateTestableResources.addOverride( + mContext.orCreateTestableResources.addOverride( com.android.internal.R.bool.config_guestUserAutoCreated, false) mContext.addMockSystemService(Context.FACE_SERVICE, mock(FaceManager::class.java)) @@ -130,8 +132,27 @@ class UserSwitcherControllerTest : SysuiTestCase() { .thenReturn(true) `when`(notificationShadeWindowView.context).thenReturn(context) + // Since userSwitcherController involves InteractionJankMonitor. + // Let's fulfill the dependencies. + val mockedContext = mock(Context::class.java) + doReturn(mockedContext).`when`(notificationShadeWindowView).context + doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow + doNothing().`when`(threadedRenderer).addObserver(any()) + doNothing().`when`(threadedRenderer).removeObserver(any()) + doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer + + picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user)) + + // Create defaults for the current user + `when`(userTracker.userId).thenReturn(ownerId) + `when`(userTracker.userInfo).thenReturn(ownerInfo) + + setupController() + } + + private fun setupController() { userSwitcherController = UserSwitcherController( - context, + mContext, activityManager, userManager, userTracker, @@ -144,27 +165,14 @@ class UserSwitcherControllerTest : SysuiTestCase() { uiEventLogger, falsingManager, telephonyListenerManager, - activityTaskManager, - userDetailAdapter, secureSettings, - uiBgExecutor, + bgExecutor, + uiExecutor, interactionJankMonitor, latencyTracker, dumpManager, + { shadeController }, dialogLaunchAnimator) - userSwitcherController.mPauseRefreshUsers = true - - // Since userSwitcherController involves InteractionJankMonitor. - // Let's fulfill the dependencies. - val mockedContext = mock(Context::class.java) - doReturn(mockedContext).`when`(notificationShadeWindowView).context - doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow - doNothing().`when`(threadedRenderer).addObserver(any()) - doNothing().`when`(threadedRenderer).removeObserver(any()) - doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer - userSwitcherController.init(notificationShadeWindowView) - - picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user)) userSwitcherController.init(notificationShadeWindowView) } @@ -177,7 +185,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(ownerId) `when`(userTracker.userInfo).thenReturn(ownerInfo) @@ -196,7 +205,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(ownerId) `when`(userTracker.userInfo).thenReturn(ownerInfo) @@ -220,7 +230,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(ownerId) `when`(userTracker.userInfo).thenReturn(ownerInfo) @@ -240,7 +251,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { true /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(guestInfo.id) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -262,7 +274,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { true /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(guestInfo.id) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -283,7 +296,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { true /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(guestInfo.id) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -302,7 +316,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { true /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(guestId) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -323,7 +338,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(guestId) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -357,7 +373,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(guestId) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -389,7 +406,7 @@ class UserSwitcherControllerTest : SysuiTestCase() { userSwitcherController.users.add(UserSwitcherController.UserRecord( UserInfo(id, name, 0), null, false, isCurrent, false, - false, false + false, false, false )) } val bgUserName = "background_user" @@ -412,4 +429,42 @@ class UserSwitcherControllerTest : SysuiTestCase() { `when`(userTracker.userId).thenReturn(1) assertEquals(false, userSwitcherController.isSystemUser) } + + @Test + fun testCanCreateSupervisedUserWithConfiguredPackage() { + // GIVEN the supervised user creation package is configured + `when`(context.getString( + com.android.internal.R.string.config_supervisedUserCreationPackage)) + .thenReturn("some_pkg") + + // AND the current user is allowed to create new users + `when`(userTracker.userId).thenReturn(ownerId) + `when`(userTracker.userInfo).thenReturn(ownerInfo) + + // WHEN the controller is started with the above config + setupController() + testableLooper.processAllMessages() + + // THEN a supervised user can be constructed + assertTrue(userSwitcherController.canCreateSupervisedUser()) + } + + @Test + fun testCannotCreateSupervisedUserWithConfiguredPackage() { + // GIVEN the supervised user creation package is NOT configured + `when`(context.getString( + com.android.internal.R.string.config_supervisedUserCreationPackage)) + .thenReturn(null) + + // AND the current user is allowed to create new users + `when`(userTracker.userId).thenReturn(ownerId) + `when`(userTracker.userInfo).thenReturn(ownerInfo) + + // WHEN the controller is started with the above config + setupController() + testableLooper.processAllMessages() + + // THEN a supervised user can NOT be constructed + assertFalse(userSwitcherController.canCreateSupervisedUser()) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java index d6454490e62a..5118637ea710 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java @@ -17,6 +17,7 @@ package com.android.systemui.util.condition; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; @@ -24,16 +25,21 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.Arrays; @@ -46,6 +52,7 @@ public class ConditionMonitorTest extends SysuiTestCase { private FakeCondition mCondition2; private FakeCondition mCondition3; private HashSet<Condition> mConditions; + private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); private Monitor mConditionMonitor; @@ -58,7 +65,83 @@ public class ConditionMonitorTest extends SysuiTestCase { mCondition3 = spy(new FakeCondition()); mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3)); - mConditionMonitor = new Monitor(mConditions, null /*callbacks*/); + mConditionMonitor = new Monitor(mExecutor, mConditions, null /*callbacks*/); + } + + @Test + public void testOverridingCondition() { + final Condition overridingCondition = Mockito.mock(Condition.class); + final Condition regularCondition = Mockito.mock(Condition.class); + final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class); + + final Monitor monitor = new Monitor( + mExecutor, + new HashSet<>(Arrays.asList(overridingCondition, regularCondition)), + new HashSet<>(Arrays.asList(callback))); + + when(overridingCondition.isOverridingCondition()).thenReturn(true); + when(overridingCondition.isConditionMet()).thenReturn(true); + when(regularCondition.isConditionMet()).thenReturn(false); + + final ArgumentCaptor<Condition.Callback> mCallbackCaptor = + ArgumentCaptor.forClass(Condition.Callback.class); + + verify(overridingCondition).addCallback(mCallbackCaptor.capture()); + + mCallbackCaptor.getValue().onConditionChanged(overridingCondition); + mExecutor.runAllReady(); + + verify(callback).onConditionsChanged(eq(true)); + Mockito.clearInvocations(callback); + + when(regularCondition.isConditionMet()).thenReturn(true); + when(overridingCondition.isConditionMet()).thenReturn(false); + + mCallbackCaptor.getValue().onConditionChanged(overridingCondition); + mExecutor.runAllReady(); + + verify(callback).onConditionsChanged(eq(false)); + + clearInvocations(callback); + monitor.removeCondition(overridingCondition); + mExecutor.runAllReady(); + + verify(callback).onConditionsChanged(eq(true)); + } + + /** + * Ensures that when multiple overriding conditions are present, it is the aggregate of those + * conditions that are considered. + */ + @Test + public void testMultipleOverridingConditions() { + final Condition overridingCondition = Mockito.mock(Condition.class); + final Condition overridingCondition2 = Mockito.mock(Condition.class); + final Condition regularCondition = Mockito.mock(Condition.class); + final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class); + + final Monitor monitor = new Monitor( + mExecutor, + new HashSet<>(Arrays.asList(overridingCondition, overridingCondition2, + regularCondition)), + new HashSet<>(Arrays.asList(callback))); + + when(overridingCondition.isOverridingCondition()).thenReturn(true); + when(overridingCondition.isConditionMet()).thenReturn(true); + when(overridingCondition2.isOverridingCondition()).thenReturn(true); + when(overridingCondition.isConditionMet()).thenReturn(false); + when(regularCondition.isConditionMet()).thenReturn(true); + + final ArgumentCaptor<Condition.Callback> mCallbackCaptor = + ArgumentCaptor.forClass(Condition.Callback.class); + + verify(overridingCondition).addCallback(mCallbackCaptor.capture()); + + mCallbackCaptor.getValue().onConditionChanged(overridingCondition); + mExecutor.runAllReady(); + + verify(callback).onConditionsChanged(eq(false)); + Mockito.clearInvocations(callback); } @Test @@ -66,11 +149,13 @@ public class ConditionMonitorTest extends SysuiTestCase { final Monitor.Callback callback1 = mock(Monitor.Callback.class); mConditionMonitor.addCallback(callback1); + mExecutor.runAllReady(); mConditions.forEach(condition -> verify(condition).addCallback(any())); final Monitor.Callback callback2 = mock(Monitor.Callback.class); mConditionMonitor.addCallback(callback2); + mExecutor.runAllReady(); mConditions.forEach(condition -> verify(condition, times(1)).addCallback(any())); } @@ -79,6 +164,7 @@ public class ConditionMonitorTest extends SysuiTestCase { final Monitor.Callback callback = mock(Monitor.Callback.class); mConditionMonitor.addCallback(callback); + mExecutor.runAllReady(); verify(callback).onConditionsChanged(false); } @@ -86,38 +172,68 @@ public class ConditionMonitorTest extends SysuiTestCase { public void addCallback_addSecondCallback_reportWithExistingValue() { final Monitor.Callback callback1 = mock(Monitor.Callback.class); - mConditionMonitor.addCallback(callback1); - - mConditionMonitor.overrideAllConditionsMet(true); + final Condition condition = mock(Condition.class); + when(condition.isConditionMet()).thenReturn(true); + final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(condition)), + new HashSet<>(Arrays.asList(callback1))); final Monitor.Callback callback2 = mock(Monitor.Callback.class); - mConditionMonitor.addCallback(callback2); - verify(callback2).onConditionsChanged(true); + monitor.addCallback(callback2); + mExecutor.runAllReady(); + verify(callback2).onConditionsChanged(eq(true)); } @Test public void addCallback_noConditions_reportAllConditionsMet() { - final Monitor monitor = new Monitor(new HashSet<>(), null /*callbacks*/); + final Monitor monitor = new Monitor(mExecutor, new HashSet<>(), null /*callbacks*/); final Monitor.Callback callback = mock(Monitor.Callback.class); monitor.addCallback(callback); - + mExecutor.runAllReady(); verify(callback).onConditionsChanged(true); } @Test + public void addCallback_withMultipleInstancesOfTheSameCallback_registerOnlyOne() { + final Monitor monitor = new Monitor(mExecutor, new HashSet<>(), null /*callbacks*/); + final Monitor.Callback callback = mock(Monitor.Callback.class); + + // Adds the same instance multiple times. + monitor.addCallback(callback); + monitor.addCallback(callback); + monitor.addCallback(callback); + mExecutor.runAllReady(); + + // Callback should only be triggered once. + verify(callback, times(1)).onConditionsChanged(true); + } + + @Test public void removeCallback_shouldNoLongerReceiveUpdate() { + final Condition condition = mock(Condition.class); + final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(condition)), + null); final Monitor.Callback callback = mock(Monitor.Callback.class); - mConditionMonitor.addCallback(callback); + monitor.addCallback(callback); + monitor.removeCallback(callback); + mExecutor.runAllReady(); clearInvocations(callback); - mConditionMonitor.removeCallback(callback); - mConditionMonitor.overrideAllConditionsMet(true); + final ArgumentCaptor<Condition.Callback> conditionCallbackCaptor = + ArgumentCaptor.forClass(Condition.Callback.class); + verify(condition).addCallback(conditionCallbackCaptor.capture()); + final Condition.Callback conditionCallback = conditionCallbackCaptor.getValue(); + + when(condition.isConditionMet()).thenReturn(true); + conditionCallback.onConditionChanged(condition); + mExecutor.runAllReady(); verify(callback, never()).onConditionsChanged(true); - mConditionMonitor.overrideAllConditionsMet(false); + when(condition.isConditionMet()).thenReturn(false); + conditionCallback.onConditionChanged(condition); + mExecutor.runAllReady(); verify(callback, never()).onConditionsChanged(false); } @@ -131,9 +247,11 @@ public class ConditionMonitorTest extends SysuiTestCase { mConditionMonitor.addCallback(callback2); mConditionMonitor.removeCallback(callback1); + mExecutor.runAllReady(); mConditions.forEach(condition -> verify(condition, never()).removeCallback(any())); mConditionMonitor.removeCallback(callback2); + mExecutor.runAllReady(); mConditions.forEach(condition -> verify(condition).removeCallback(any())); } @@ -147,6 +265,7 @@ public class ConditionMonitorTest extends SysuiTestCase { mCondition1.fakeUpdateCondition(true); mCondition2.fakeUpdateCondition(true); mCondition3.fakeUpdateCondition(true); + mExecutor.runAllReady(); verify(callback).onConditionsChanged(true); } @@ -163,6 +282,7 @@ public class ConditionMonitorTest extends SysuiTestCase { clearInvocations(callback); mCondition1.fakeUpdateCondition(false); + mExecutor.runAllReady(); verify(callback).onConditionsChanged(false); } @@ -171,16 +291,20 @@ public class ConditionMonitorTest extends SysuiTestCase { final Monitor.Callback callback = mock(Monitor.Callback.class); mConditionMonitor.addCallback(callback); + mExecutor.runAllReady(); verify(callback).onConditionsChanged(false); clearInvocations(callback); mCondition1.fakeUpdateCondition(true); + mExecutor.runAllReady(); verify(callback, never()).onConditionsChanged(anyBoolean()); mCondition2.fakeUpdateCondition(true); + mExecutor.runAllReady(); verify(callback, never()).onConditionsChanged(anyBoolean()); mCondition3.fakeUpdateCondition(true); + mExecutor.runAllReady(); verify(callback).onConditionsChanged(true); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java index 7fc6b51bf2a6..9e0f863acc1a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java @@ -16,7 +16,8 @@ package com.android.systemui.util.condition; -import static org.mockito.ArgumentMatchers.anyBoolean; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -73,7 +74,8 @@ public class ConditionTest extends SysuiTestCase { final Condition.Callback callback2 = mock(Condition.Callback.class); mCondition.addCallback(callback2); - verify(callback2).onConditionChanged(mCondition, true); + verify(callback2).onConditionChanged(mCondition); + assertThat(mCondition.isConditionMet()).isTrue(); } @Test @@ -94,7 +96,8 @@ public class ConditionTest extends SysuiTestCase { mCondition.addCallback(callback); mCondition.fakeUpdateCondition(true); - verify(callback).onConditionChanged(eq(mCondition), eq(true)); + verify(callback).onConditionChanged(eq(mCondition)); + assertThat(mCondition.isConditionMet()).isTrue(); } @Test @@ -105,7 +108,8 @@ public class ConditionTest extends SysuiTestCase { mCondition.addCallback(callback); mCondition.fakeUpdateCondition(false); - verify(callback).onConditionChanged(eq(mCondition), eq(false)); + verify(callback).onConditionChanged(eq(mCondition)); + assertThat(mCondition.isConditionMet()).isFalse(); } @Test @@ -116,7 +120,7 @@ public class ConditionTest extends SysuiTestCase { mCondition.addCallback(callback); mCondition.fakeUpdateCondition(true); - verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean()); + verify(callback, never()).onConditionChanged(eq(mCondition)); } @Test @@ -127,6 +131,6 @@ public class ConditionTest extends SysuiTestCase { mCondition.addCallback(callback); mCondition.fakeUpdateCondition(false); - verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean()); + verify(callback, never()).onConditionChanged(eq(mCondition)); } } diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java index dafe7a470de0..daead0a5ff9d 100644 --- a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java +++ b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java @@ -58,11 +58,14 @@ public class CloudSearchManagerService extends private final ActivityTaskManagerInternal mActivityTaskManagerInternal; + private final Context mContext; + public CloudSearchManagerService(Context context) { super(context, new FrameworkResourcesServiceNameResolver(context, R.string.config_defaultCloudSearchService), null, PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH); mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); + mContext = context; } @Override @@ -106,6 +109,8 @@ public class CloudSearchManagerService extends @Override public void search(@NonNull SearchRequest searchRequest, @NonNull ICloudSearchManagerCallback callBack) { + searchRequest.setSource( + mContext.getPackageManager().getNameForUid(Binder.getCallingUid())); runForUserLocked("search", searchRequest.getRequestId(), (service) -> service.onSearchLocked(searchRequest, callBack)); } diff --git a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java b/services/companion/java/com/android/server/companion/AssociationCleanUpService.java index 0509e0cf5ccc..55246e14d592 100644 --- a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java +++ b/services/companion/java/com/android/server/companion/AssociationCleanUpService.java @@ -16,7 +16,9 @@ package com.android.server.companion; -import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; +import static com.android.server.companion.CompanionDeviceManagerService.TAG; + +import static java.util.concurrent.TimeUnit.DAYS; import android.app.job.JobInfo; import android.app.job.JobParameters; @@ -37,17 +39,16 @@ import com.android.server.LocalServices; */ public class AssociationCleanUpService extends JobService { private static final int JOB_ID = AssociationCleanUpService.class.hashCode(); - private static final long ONE_DAY_INTERVAL = 3 * 24 * 60 * 60 * 1000; // 1 Day - private CompanionDeviceManagerServiceInternal mCdmServiceInternal = LocalServices.getService( - CompanionDeviceManagerServiceInternal.class); + private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1); @Override public boolean onStartJob(final JobParameters params) { - Slog.i(LOG_TAG, "Execute the Association CleanUp job"); + Slog.i(TAG, "Execute the Association CleanUp job"); // Special policy for APP_STREAMING role that need to revoke associations if the device // does not connect for 3 months. AsyncTask.execute(() -> { - mCdmServiceInternal.associationCleanUp(AssociationRequest.DEVICE_PROFILE_APP_STREAMING); + LocalServices.getService(CompanionDeviceManagerServiceInternal.class) + .associationCleanUp(AssociationRequest.DEVICE_PROFILE_APP_STREAMING); jobFinished(params, false); }); return true; @@ -55,7 +56,7 @@ public class AssociationCleanUpService extends JobService { @Override public boolean onStopJob(final JobParameters params) { - Slog.i(LOG_TAG, "Association cleanup job stopped; id=" + params.getJobId() + Slog.i(TAG, "Association cleanup job stopped; id=" + params.getJobId() + ", reason=" + JobParameters.getInternalReasonCodeDescription( params.getInternalStopReasonCode())); @@ -63,7 +64,7 @@ public class AssociationCleanUpService extends JobService { } static void schedule(Context context) { - Slog.i(LOG_TAG, "Scheduling the Association Cleanup job"); + Slog.i(TAG, "Scheduling the Association Cleanup job"); final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); final JobInfo job = new JobInfo.Builder(JOB_ID, new ComponentName(context, AssociationCleanUpService.class)) diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index cef0e83f6006..eaa99f74e24e 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -18,31 +18,28 @@ package com.android.server.companion; import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; -import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES; -import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER; import static android.content.pm.PackageManager.CERT_INPUT_SHA256; -import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.Binder.getCallingUid; import static android.os.Process.SYSTEM_UID; import static android.os.UserHandle.getCallingUserId; import static com.android.internal.util.CollectionUtils.any; -import static com.android.internal.util.CollectionUtils.find; import static com.android.internal.util.Preconditions.checkState; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; -import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable; import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED; -import static com.android.server.companion.PermissionsUtils.checkCallerCanManageAssociationsForPackage; +import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature; +import static com.android.server.companion.PackageUtils.getPackageInfo; import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice; import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageCompanionDevice; import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr; import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId; +import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks; import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation; import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation; import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.MINUTES; import android.annotation.NonNull; @@ -53,26 +50,15 @@ import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.PendingIntent; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.le.BluetoothLeScanner; -import android.bluetooth.le.ScanCallback; -import android.bluetooth.le.ScanFilter; -import android.bluetooth.le.ScanResult; -import android.bluetooth.le.ScanSettings; import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.companion.DeviceNotAssociatedException; import android.companion.IAssociationRequestCallback; import android.companion.ICompanionDeviceManager; import android.companion.IOnAssociationsChangedListener; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.SharedPreferences; -import android.content.pm.FeatureInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; @@ -93,11 +79,10 @@ import android.os.ServiceManager; import android.os.ShellCallback; import android.os.UserHandle; import android.os.UserManager; -import android.permission.PermissionControllerManager; import android.text.BidiFormatter; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.ExceptionUtils; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -112,86 +97,49 @@ import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.companion.presence.CompanionDevicePresenceMonitor; import com.android.server.pm.UserManagerInternal; -import com.android.server.wm.ActivityTaskManagerInternal; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; -import java.util.TimeZone; -/** @hide */ @SuppressLint("LongLogTag") -public class CompanionDeviceManagerService extends SystemService - implements AssociationStore.OnChangeListener { - static final String LOG_TAG = "CompanionDeviceManagerService"; +public class CompanionDeviceManagerService extends SystemService { + static final String TAG = "CompanionDeviceManagerService"; static final boolean DEBUG = false; /** Range of Association IDs allocated for a user.*/ - static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000; - - private static final long DEVICE_DISAPPEARED_TIMEOUT_MS = 10 * 1000; - private static final long DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS = 10 * 60 * 1000; - - static final long DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS = 10 * 1000; - + private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000; private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min private static final String PREF_FILE_NAME = "companion_device_preferences.xml"; private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done"; - private static final long ASSOCIATION_CLEAN_UP_TIME_WINDOW = - 90L * 24 * 60 * 60 * 1000; // 3 months - - private static DateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - static { - sDateFormat.setTimeZone(TimeZone.getDefault()); - } + private static final long ASSOCIATION_CLEAN_UP_TIME_WINDOW = DAYS.toMillis(3 * 30); // 3 months - // Persistent data store for all Associations. private PersistentDataStore mPersistentStore; - private final AssociationStoreImpl mAssociationStore = new AssociationStoreImpl(); - private AssociationRequestsProcessor mAssociationRequestsProcessor; + private final PersistUserStateHandler mUserPersistenceHandler; - private PowerWhitelistManager mPowerWhitelistManager; - private IAppOpsService mAppOpsManager; - private BluetoothAdapter mBluetoothAdapter; - private UserManager mUserManager; - - private ScanCallback mBleScanCallback = new BleScanCallback(); - PermissionControllerManager mPermissionControllerManager; - - private BluetoothDeviceConnectedListener mBluetoothDeviceConnectedListener = - new BluetoothDeviceConnectedListener(); - private BleStateBroadcastReceiver mBleStateBroadcastReceiver = new BleStateBroadcastReceiver(); - private List<String> mCurrentlyConnectedDevices = new ArrayList<>(); - Set<Integer> mPresentSelfManagedDevices = new HashSet<>(); - private ArrayMap<String, Date> mDevicesLastNearby = new ArrayMap<>(); - private UnbindDeviceListenersRunnable - mUnbindDeviceListenersRunnable = new UnbindDeviceListenersRunnable(); - private ArrayMap<String, TriggerDeviceDisappearedRunnable> mTriggerDeviceDisappearedRunnables = - new ArrayMap<>(); - private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners = - new RemoteCallbackList<>(); - private final CompanionDeviceManagerServiceInternal mLocalService = new LocalService(this); + private final AssociationStoreImpl mAssociationStore; + private AssociationRequestsProcessor mAssociationRequestsProcessor; + private CompanionDevicePresenceMonitor mDevicePresenceMonitor; + private CompanionApplicationController mCompanionAppController; - final Handler mMainHandler = Handler.getMain(); - private final PersistUserStateHandler mUserPersistenceHandler = new PersistUserStateHandler(); - private CompanionDevicePresenceController mCompanionDevicePresenceController; + private final ActivityManagerInternal mAmInternal; + private final IAppOpsService mAppOpsManager; + private final PowerWhitelistManager mPowerWhitelistManager; + private final UserManager mUserManager; + final PackageManagerInternal mPackageManagerInternal; /** - * A structure that consist of two nested maps, and effectively maps (userId + packageName) to + * A structure that consists of two nested maps, and effectively maps (userId + packageName) to * a list of IDs that have been previously assigned to associations for that package. * We maintain this structure so that we never re-use association IDs for the same package * (until it's uninstalled). @@ -199,9 +147,8 @@ public class CompanionDeviceManagerService extends SystemService @GuardedBy("mPreviouslyUsedIds") private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>(); - ActivityTaskManagerInternal mAtmInternal; - ActivityManagerInternal mAmInternal; - PackageManagerInternal mPackageManagerInternal; + private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners = + new RemoteCallbackList<>(); public CompanionDeviceManagerService(Context context) { super(context); @@ -209,14 +156,12 @@ public class CompanionDeviceManagerService extends SystemService mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class); mAppOpsManager = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); - mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); mAmInternal = LocalServices.getService(ActivityManagerInternal.class); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); - mPermissionControllerManager = requireNonNull( - context.getSystemService(PermissionControllerManager.class)); mUserManager = context.getSystemService(UserManager.class); - LocalServices.addService(CompanionDeviceManagerServiceInternal.class, mLocalService); + mUserPersistenceHandler = new PersistUserStateHandler(); + mAssociationStore = new AssociationStoreImpl(); } @Override @@ -224,14 +169,24 @@ public class CompanionDeviceManagerService extends SystemService mPersistentStore = new PersistentDataStore(); loadAssociationsFromDisk(); - mAssociationStore.registerListener(this); + mAssociationStore.registerListener(mAssociationStoreChangeListener); + + mDevicePresenceMonitor = new CompanionDevicePresenceMonitor( + mAssociationStore, mDevicePresenceCallback); - mCompanionDevicePresenceController = new CompanionDevicePresenceController(this); - mAssociationRequestsProcessor = new AssociationRequestsProcessor(this, mAssociationStore); + mAssociationRequestsProcessor = new AssociationRequestsProcessor( + /* cdmService */this, mAssociationStore); - // Publish "binder service" + final Context context = getContext(); + mCompanionAppController = new CompanionApplicationController( + context, mApplicationControllerCallback); + + // Publish "binder" service. final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl(); publishBinderService(Context.COMPANION_DEVICE_SERVICE, impl); + + // Publish "local" service. + LocalServices.addService(CompanionDeviceManagerServiceInternal.class, new LocalService()); } void loadAssociationsFromDisk() { @@ -248,21 +203,13 @@ public class CompanionDeviceManagerService extends SystemService @Override public void onBootPhase(int phase) { - if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { - registerPackageMonitor(); - - // Init Bluetooth - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - if (mBluetoothAdapter != null) { - mBluetoothAdapter.registerBluetoothConnectionCallback( - getContext().getMainExecutor(), - mBluetoothDeviceConnectedListener); - getContext().registerReceiver( - mBleStateBroadcastReceiver, mBleStateBroadcastReceiver.mIntentFilter); - initBleScanning(); - } else { - Slog.w(LOG_TAG, "No BluetoothAdapter available"); - } + final Context context = getContext(); + if (phase == PHASE_SYSTEM_SERVICES_READY) { + // WARNING: moving PackageMonitor to another thread (Looper) may introduce significant + // delays (even in case of the Main Thread). It may be fine overall, but would require + // updating the tests (adding a delay there). + mPackageMonitor.register(context, FgThread.get().getLooper(), UserHandle.ALL, true); + mDevicePresenceMonitor.init(context); } else if (phase == PHASE_BOOT_COMPLETED) { // Run the Association CleanUp job service daily. AssociationCleanUpService.schedule(getContext()); @@ -288,76 +235,84 @@ public class CompanionDeviceManagerService extends SystemService @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) { final AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress( userId, packageName, macAddress); - return sanitizeWithCallerChecks(association); + return sanitizeWithCallerChecks(getContext(), association); } @Nullable AssociationInfo getAssociationWithCallerChecks(int associationId) { final AssociationInfo association = mAssociationStore.getAssociationById(associationId); - return sanitizeWithCallerChecks(association); + return sanitizeWithCallerChecks(getContext(), association); } - @Nullable - private AssociationInfo sanitizeWithCallerChecks(@Nullable AssociationInfo association) { - if (association == null) return null; + private void onDeviceAppearedInternal(int associationId) { + if (DEBUG) Log.i(TAG, "onDevice_Appeared_Internal() id=" + associationId); + + final AssociationInfo association = mAssociationStore.getAssociationById(associationId); + if (DEBUG) Log.d(TAG, " association=" + associationId); + + if (!association.shouldBindWhenPresent()) return; final int userId = association.getUserId(); final String packageName = association.getPackageName(); - if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) { - return null; - } - return association; + if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { + mCompanionAppController.bindCompanionApplication(userId, packageName); + } else if (DEBUG) { + Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound"); + } + mCompanionAppController.notifyCompanionApplicationDeviceAppeared(association); } - // Revoke associations if the selfManaged companion device does not connect for 3 - // months for specific profile. - private void associationCleanUp(String profile) { - for (AssociationInfo ai : mAssociationStore.getAssociations()) { - if (ai.isSelfManaged() - && profile.equals(ai.getDeviceProfile()) - && System.currentTimeMillis() - ai.getLastTimeConnectedMs() - >= ASSOCIATION_CLEAN_UP_TIME_WINDOW) { - Slog.d(LOG_TAG, "Removing the association for associationId: " - + ai.getId() - + " due to the device does not connect for 3 months." - + " Current time: " - + new Date(System.currentTimeMillis())); - disassociateInternal(ai.getId()); - } + private void onDeviceDisappearedInternal(int associationId) { + if (DEBUG) Log.i(TAG, "onDevice_Disappeared_Internal() id=" + associationId); + + final AssociationInfo association = mAssociationStore.getAssociationById(associationId); + if (DEBUG) Log.d(TAG, " association=" + associationId); + + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + + if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { + if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound"); + return; } + + if (association.shouldBindWhenPresent()) { + mCompanionAppController.notifyCompanionApplicationDeviceDisappeared(association); + } + + // Check if there are other devices associated to the app that are present. + if (shouldBindPackage(userId, packageName)) return; + + mCompanionAppController.unbindCompanionApplication(userId, packageName); } - void maybeGrantAutoRevokeExemptions() { - Slog.d(LOG_TAG, "maybeGrantAutoRevokeExemptions()"); - PackageManager pm = getContext().getPackageManager(); - for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) { - SharedPreferences pref = getContext().getSharedPreferences( - new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME), - Context.MODE_PRIVATE); - if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) { - continue; - } + private boolean onCompanionApplicationBindingDiedInternal( + @UserIdInt int userId, @NonNull String packageName) { + // TODO(b/218613015): implement. + return false; + } - try { - final List<AssociationInfo> associations = - mAssociationStore.getAssociationsForUser(userId); - for (AssociationInfo a : associations) { - try { - int uid = pm.getPackageUidAsUser(a.getPackageName(), userId); - exemptFromAutoRevoke(a.getPackageName(), uid); - } catch (PackageManager.NameNotFoundException e) { - Slog.w(LOG_TAG, "Unknown companion package: " + a.getPackageName(), e); - } - } - } finally { - pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply(); - } + private void onRebindCompanionApplicationTimeoutInternal( + @UserIdInt int userId, @NonNull String packageName) { + // TODO(b/218613015): implement. + } + + /** + * @return whether the package should be bound (i.e. at least one of the devices associated with + * the package is currently present). + */ + private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) { + final List<AssociationInfo> packageAssociations = + mAssociationStore.getAssociationsForPackage(userId, packageName); + for (AssociationInfo association : packageAssociations) { + if (!association.shouldBindWhenPresent()) continue; + if (mDevicePresenceMonitor.isDevicePresent(association.getId())) return true; } + return false; } - @Override - public void onAssociationChanged( + private void onAssociationChangedInternal( @AssociationStore.ChangeType int changeType, AssociationInfo association) { final int id = association.getId(); final int userId = association.getUserId(); @@ -379,8 +334,6 @@ public class CompanionDeviceManagerService extends SystemService notifyListeners(userId, updatedAssociations); } updateAtm(userId, updatedAssociations); - - restartBleScan(); } private void persistStateForUser(@UserIdInt int userId) { @@ -417,15 +370,59 @@ public class CompanionDeviceManagerService extends SystemService } } - class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub { + private void onPackageRemoveOrDataClearedInternal( + @UserIdInt int userId, @NonNull String packageName) { + if (DEBUG) { + Log.i(TAG, "onPackageRemove_Or_DataCleared() u" + userId + "/" + + packageName); + } + + // Clear associations. + final List<AssociationInfo> associationsForPackage = + mAssociationStore.getAssociationsForPackage(userId, packageName); + for (AssociationInfo association : associationsForPackage) { + mAssociationStore.removeAssociation(association.getId()); + } + + mCompanionAppController.onPackagesChanged(userId); + } + + private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) { + if (DEBUG) Log.i(TAG, "onPackageModified() u" + userId + "/" + packageName); + + final List<AssociationInfo> associationsForPackage = + mAssociationStore.getAssociationsForPackage(userId, packageName); + for (AssociationInfo association : associationsForPackage) { + updateSpecialAccessPermissionForAssociatedPackage(association); + } + + mCompanionAppController.onPackagesChanged(userId); + } + + // Revoke associations if the selfManaged companion device does not connect for 3 + // months for specific profile. + private void associationCleanUp(String profile) { + for (AssociationInfo ai : mAssociationStore.getAssociations()) { + if (ai.isSelfManaged() + && profile.equals(ai.getDeviceProfile()) + && System.currentTimeMillis() - ai.getLastTimeConnectedMs() + >= ASSOCIATION_CLEAN_UP_TIME_WINDOW) { + Slog.i(TAG, "Removing the association for associationId: " + + ai.getId() + + " due to the device does not connect for 3 months."); + disassociateInternal(ai.getId()); + } + } + } + class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub { @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { try { return super.onTransact(code, data, reply, flags); } catch (Throwable e) { - Slog.e(LOG_TAG, "Error during IPC", e); + Slog.e(TAG, "Error during IPC", e); throw ExceptionUtils.propagate(e, RemoteException.class); } } @@ -433,7 +430,7 @@ public class CompanionDeviceManagerService extends SystemService @Override public void associate(AssociationRequest request, IAssociationRequestCallback callback, String packageName, int userId) throws RemoteException { - Slog.i(LOG_TAG, "associate() " + Slog.i(TAG, "associate() " + "request=" + request + ", " + "package=u" + userId + "/" + packageName); enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName, @@ -451,7 +448,7 @@ public class CompanionDeviceManagerService extends SystemService if (!checkCallerCanManageCompanionDevice(getContext())) { // If the caller neither is system nor holds MANAGE_COMPANION_DEVICES: it needs to // request the feature (also: the caller is the app itself). - checkUsesFeature(packageName, getCallingUserId()); + enforceUsesCompanionDeviceFeature(getContext(), userId, packageName); } return mAssociationStore.getAssociationsForPackage(userId, packageName); @@ -487,6 +484,11 @@ public class CompanionDeviceManagerService extends SystemService @Override public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) { + if (DEBUG) { + Log.i(TAG, "legacyDisassociate() pkg=u" + userId + "/" + packageName + + ", macAddress=" + deviceMacAddress); + } + requireNonNull(deviceMacAddress); requireNonNull(packageName); @@ -503,6 +505,8 @@ public class CompanionDeviceManagerService extends SystemService @Override public void disassociate(int associationId) { + if (DEBUG) Log.i(TAG, "disassociate() associationId=" + associationId); + final AssociationInfo association = getAssociationWithCallerChecks(associationId); if (association == null) { throw new IllegalArgumentException("Association with ID " + associationId + " " @@ -519,9 +523,9 @@ public class CompanionDeviceManagerService extends SystemService throws RemoteException { String callingPackage = component.getPackageName(); checkCanCallNotificationApi(callingPackage); - //TODO: check userId. + // TODO: check userId. String packageTitle = BidiFormatter.getInstance().unicodeWrap( - getPackageInfo(callingPackage, userId) + getPackageInfo(getContext(), userId, callingPackage) .applicationInfo .loadSafeLabel(getContext().getPackageManager(), PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, @@ -575,26 +579,28 @@ public class CompanionDeviceManagerService extends SystemService @Override public void registerDevicePresenceListenerService(String deviceAddress, String callingPackage, int userId) throws RemoteException { - //TODO: take the userId into account. + // TODO: take the userId into account. registerDevicePresenceListenerActive(callingPackage, deviceAddress, true); } @Override public void unregisterDevicePresenceListenerService(String deviceAddress, String callingPackage, int userId) throws RemoteException { - //TODO: take the userId into account. + // TODO: take the userId into account. registerDevicePresenceListenerActive(callingPackage, deviceAddress, false); } @Override public void dispatchMessage(int messageId, int associationId, byte[] message) throws RemoteException { - //TODO: b/199427116 + // TODO(b/199427116): implement. } @Override public void notifyDeviceAppeared(int associationId) { - final AssociationInfo association = getAssociationWithCallerChecks(associationId); + if (DEBUG) Log.i(TAG, "notifyDevice_Appeared() id=" + associationId); + + AssociationInfo association = getAssociationWithCallerChecks(associationId); if (association == null) { throw new IllegalArgumentException("Association with ID " + associationId + " " + "does not exist " @@ -607,23 +613,20 @@ public class CompanionDeviceManagerService extends SystemService + " is not self-managed. notifyDeviceAppeared(int) can only be called for" + " self-managed associations."); } - - if (!mPresentSelfManagedDevices.add(associationId)) { - Slog.w(LOG_TAG, "Association with ID " + associationId + " is already present"); - return; - } - - AssociationInfo updatedAssociationInfo = AssociationInfo.builder(association) + // AssociationInfo class is immutable: create a new AssociationInfo object with updated + // timestamp. + association = AssociationInfo.builder(association) .setLastTimeConnected(System.currentTimeMillis()) .build(); - mAssociationStore.updateAssociation(updatedAssociationInfo); + mAssociationStore.updateAssociation(association); - mCompanionDevicePresenceController.onDeviceNotifyAppeared( - updatedAssociationInfo, getContext(), mMainHandler); + mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId); } @Override public void notifyDeviceDisappeared(int associationId) { + if (DEBUG) Log.i(TAG, "notifyDevice_Disappeared() id=" + associationId); + final AssociationInfo association = getAssociationWithCallerChecks(associationId); if (association == null) { throw new IllegalArgumentException("Association with ID " + associationId + " " @@ -638,14 +641,7 @@ public class CompanionDeviceManagerService extends SystemService + " self-managed associations."); } - if (!mPresentSelfManagedDevices.contains(associationId)) { - Slog.w(LOG_TAG, "Association with ID " + associationId + " is not connected"); - return; - } - - mPresentSelfManagedDevices.remove(associationId); - mCompanionDevicePresenceController.onDeviceNotifyDisappearedAndUnbind( - association, getContext(), mMainHandler); + mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId); } private void registerDevicePresenceListenerActive(String packageName, String deviceAddress, @@ -656,8 +652,7 @@ public class CompanionDeviceManagerService extends SystemService final int userId = getCallingUserId(); enforceCallerIsSystemOr(userId, packageName); - final AssociationInfo association = - mAssociationStore.getAssociationsForPackageWithAddress( + AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress( userId, packageName, deviceAddress); if (association == null) { @@ -666,10 +661,14 @@ public class CompanionDeviceManagerService extends SystemService + " for user " + userId)); } - AssociationInfo updatedAssociationInfo = AssociationInfo.builder(association) + // AssociationInfo class is immutable: create a new AssociationInfo object with updated + // flag. + association = AssociationInfo.builder(association) .setNotifyOnDeviceNearby(active) .build(); - mAssociationStore.updateAssociation(updatedAssociationInfo); + mAssociationStore.updateAssociation(association); + + // TODO(b/218615198): correctly handle the case when the device is currently present. } @Override @@ -677,7 +676,7 @@ public class CompanionDeviceManagerService extends SystemService byte[] certificate) { if (!getContext().getPackageManager().hasSigningCertificate( packageName, certificate, CERT_INPUT_SHA256)) { - Slog.e(LOG_TAG, "Given certificate doesn't match the package certificate."); + Slog.e(TAG, "Given certificate doesn't match the package certificate."); return; } @@ -691,10 +690,12 @@ public class CompanionDeviceManagerService extends SystemService final int userId = getCallingUserId(); enforceCallerIsSystemOr(userId, callingPackage); + if (getCallingUid() == SYSTEM_UID) return; + + enforceUsesCompanionDeviceFeature(getContext(), userId, callingPackage); checkState(!ArrayUtils.isEmpty( mAssociationStore.getAssociationsForPackage(userId, callingPackage)), "App must have an association before calling this API"); - checkUsesFeature(callingPackage, userId); } @Override @@ -720,47 +721,20 @@ public class CompanionDeviceManagerService extends SystemService } @Override - public void dump(@NonNull FileDescriptor fd, - @NonNull PrintWriter fout, + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter out, @Nullable String[] args) { - if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), LOG_TAG, fout)) { + if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, out)) { return; } - fout.append("Companion Device Associations:").append('\n'); + // TODO(b/218615185): mAssociationStore.dump() instead + out.append("Companion Device Associations:").append('\n'); for (AssociationInfo a : mAssociationStore.getAssociations()) { - fout.append(" ").append(a.toString()).append('\n'); + out.append(" ").append(a.toString()).append('\n'); } - fout.append("Currently Connected Devices:").append('\n'); - for (int i = 0, size = mCurrentlyConnectedDevices.size(); i < size; i++) { - fout.append(" ").append(mCurrentlyConnectedDevices.get(i)).append('\n'); - } - - fout.append("Currently SelfManaged Connected Devices associationId:").append('\n'); - for (Integer associationId : mPresentSelfManagedDevices) { - fout.append(" ").append("AssociationId: ").append( - String.valueOf(associationId)).append('\n'); - } - - fout.append("Devices Last Nearby:").append('\n'); - for (int i = 0, size = mDevicesLastNearby.size(); i < size; i++) { - String device = mDevicesLastNearby.keyAt(i); - Date time = mDevicesLastNearby.valueAt(i); - fout.append(" ").append(device).append(" -> ") - .append(sDateFormat.format(time)).append('\n'); - } - - fout.append("Device Listener Services State:").append('\n'); - for (int i = 0, size = mCompanionDevicePresenceController.mBoundServices.size(); - i < size; i++) { - int userId = mCompanionDevicePresenceController.mBoundServices.keyAt(i); - fout.append(" ") - .append("u").append(Integer.toString(userId)).append(": ") - .append(Objects.toString( - mCompanionDevicePresenceController.mBoundServices.valueAt(i))) - .append('\n'); - } + // TODO(b/218615185): mDevicePresenceMonitor.dump() + // TODO(b/218615185): mCompanionAppController.dump() } } @@ -784,7 +758,7 @@ public class CompanionDeviceManagerService extends SystemService final AssociationInfo association = new AssociationInfo(id, userId, packageName, macAddress, displayName, deviceProfile, selfManaged, false, timestamp, Long.MAX_VALUE); - Slog.i(LOG_TAG, "New CDM association created=" + association); + Slog.i(TAG, "New CDM association created=" + association); mAssociationStore.addAssociation(association); // If the "Device Profile" is specified, make the companion application a holder of the @@ -862,52 +836,50 @@ public class CompanionDeviceManagerService extends SystemService } } - //TODO: also revoke notification access + // TODO: also revoke notification access void disassociateInternal(int associationId) { - onAssociationPreRemove(associationId); + final AssociationInfo association = mAssociationStore.getAssociationById(associationId); + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + final String deviceProfile = association.getDeviceProfile(); + + final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(associationId); + + // Removing the association. mAssociationStore.removeAssociation(associationId); - } - void onAssociationPreRemove(int associationId) { - final AssociationInfo association = mAssociationStore.getAssociationById(associationId); - if (association.isNotifyOnDeviceNearby() - || (association.isSelfManaged() - && mPresentSelfManagedDevices.contains(association.getId()))) { - mCompanionDevicePresenceController.unbindDevicePresenceListener( - association.getPackageName(), association.getUserId()); - } + final List<AssociationInfo> otherAssociations = + mAssociationStore.getAssociationsForPackage(userId, packageName); - String deviceProfile = association.getDeviceProfile(); + // Check if the package is associated with other devices with the same profile. + // If not: take away the role. if (deviceProfile != null) { - AssociationInfo otherAssociationWithDeviceProfile = find( - mAssociationStore.getAssociationsForUser(association.getUserId()), - a -> !a.equals(association) && deviceProfile.equals(a.getDeviceProfile())); - if (otherAssociationWithDeviceProfile != null) { - Slog.i(LOG_TAG, "Not revoking " + deviceProfile - + " for " + association - + " - profile still present in " + otherAssociationWithDeviceProfile); - } else { - Binder.withCleanCallingIdentity( - () -> removeRoleHolderForAssociation(getContext(), association)); + final boolean shouldKeepTheRole = any(otherAssociations, + it -> deviceProfile.equals(it.getDeviceProfile())); + if (!shouldKeepTheRole) { + Binder.withCleanCallingIdentity(() -> + removeRoleHolderForAssociation(getContext(), association)); } } + + if (!wasPresent || !association.isNotifyOnDeviceNearby()) return; + // The device was connected and the app was notified: check if we need to unbind the app + // now. + final boolean shouldStayBound = any(otherAssociations, + it -> it.isNotifyOnDeviceNearby() + && mDevicePresenceMonitor.isDevicePresent(it.getId())); + if (shouldStayBound) return; + mCompanionAppController.unbindCompanionApplication(userId, packageName); } private void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) { - PackageInfo packageInfo = getPackageInfo( - association.getPackageName(), - association.getUserId()); - if (packageInfo == null) { - return; - } + final PackageInfo packageInfo = + getPackageInfo(getContext(), association.getUserId(), association.getPackageName()); - Binder.withCleanCallingIdentity(obtainRunnable(CompanionDeviceManagerService:: - updateSpecialAccessPermissionAsSystem, this, association, packageInfo) - .recycleOnUse()); + Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo)); } - private void updateSpecialAccessPermissionAsSystem( - AssociationInfo association, PackageInfo packageInfo) { + private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) { if (containsEither(packageInfo.requestedPermissions, android.Manifest.permission.RUN_IN_BACKGROUND, android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) { @@ -916,7 +888,7 @@ public class CompanionDeviceManagerService extends SystemService try { mPowerWhitelistManager.removeFromWhitelist(packageInfo.packageName); } catch (UnsupportedOperationException e) { - Slog.w(LOG_TAG, packageInfo.packageName + " can't be removed from power save" + Slog.w(TAG, packageInfo.packageName + " can't be removed from power save" + " whitelist. It might due to the package is whitelisted by the system."); } } @@ -935,10 +907,6 @@ public class CompanionDeviceManagerService extends SystemService } exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid); - - if (association.isNotifyOnDeviceNearby()) { - restartBleScan(); - } } private void exemptFromAutoRevoke(String packageName, int uid) { @@ -949,23 +917,10 @@ public class CompanionDeviceManagerService extends SystemService packageName, AppOpsManager.MODE_IGNORED); } catch (RemoteException e) { - Slog.w(LOG_TAG, - "Error while granting auto revoke exemption for " + packageName, e); + Slog.w(TAG, "Error while granting auto revoke exemption for " + packageName, e); } } - private static <T> boolean containsEither(T[] array, T a, T b) { - return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b); - } - - @Nullable - private PackageInfo getPackageInfo(String packageName, int userId) { - final int flags = PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS; - return Binder.withCleanCallingIdentity( - () -> getContext().getPackageManager() - .getPackageInfoAsUser(packageName, flags , userId)); - } - private void updateAtm(int userId, List<AssociationInfo> associations) { final Set<Integer> companionAppUids = new ArraySet<>(); for (AssociationInfo association : associations) { @@ -981,262 +936,85 @@ public class CompanionDeviceManagerService extends SystemService } } - void onDeviceConnected(String address) { - Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")"); - mCurrentlyConnectedDevices.add(address); - onDeviceNearby(address); - } - - void onDeviceDisconnected(String address) { - Slog.d(LOG_TAG, "onDeviceDisconnected(address = " + address + ")"); + private void maybeGrantAutoRevokeExemptions() { + Slog.d(TAG, "maybeGrantAutoRevokeExemptions()"); - mCurrentlyConnectedDevices.remove(address); + PackageManager pm = getContext().getPackageManager(); + for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) { + SharedPreferences pref = getContext().getSharedPreferences( + new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME), + Context.MODE_PRIVATE); + if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) { + continue; + } - Date lastSeen = mDevicesLastNearby.get(address); - if (isDeviceDisappeared(lastSeen)) { - onDeviceDisappeared(address); - unscheduleTriggerDeviceDisappearedRunnable(address); + try { + final List<AssociationInfo> associations = + mAssociationStore.getAssociationsForUser(userId); + for (AssociationInfo a : associations) { + try { + int uid = pm.getPackageUidAsUser(a.getPackageName(), userId); + exemptFromAutoRevoke(a.getPackageName(), uid); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e); + } + } + } finally { + pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply(); + } } } - private boolean isDeviceDisappeared(Date lastSeen) { - return lastSeen == null || System.currentTimeMillis() - lastSeen.getTime() - >= DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS; - } - - private class BleScanCallback extends ScanCallback { + private final AssociationStore.OnChangeListener mAssociationStoreChangeListener = + new AssociationStore.OnChangeListener() { @Override - public void onScanResult(int callbackType, ScanResult result) { - if (DEBUG) { - Slog.i(LOG_TAG, "onScanResult(callbackType = " - + callbackType + ", result = " + result + ")"); - } - - onDeviceNearby(result.getDevice().getAddress()); + public void onAssociationChanged(int changeType, AssociationInfo association) { + onAssociationChangedInternal(changeType, association); } + }; + private final CompanionDevicePresenceMonitor.Callback mDevicePresenceCallback = + new CompanionDevicePresenceMonitor.Callback() { @Override - public void onBatchScanResults(List<ScanResult> results) { - for (int i = 0, size = results.size(); i < size; i++) { - onScanResult(CALLBACK_TYPE_ALL_MATCHES, results.get(i)); - } + public void onDeviceAppeared(int associationId) { + onDeviceAppearedInternal(associationId); } @Override - public void onScanFailed(int errorCode) { - if (errorCode == SCAN_FAILED_ALREADY_STARTED) { - // ignore - this might happen if BT tries to auto-restore scans for us in the - // future - Slog.i(LOG_TAG, "Ignoring BLE scan error: SCAN_FAILED_ALREADY_STARTED"); - } else { - Slog.w(LOG_TAG, "Failed to start BLE scan: error " + errorCode); - } + public void onDeviceDisappeared(int associationId) { + onDeviceDisappearedInternal(associationId); } - } - - private class BleStateBroadcastReceiver extends BroadcastReceiver { - - final IntentFilter mIntentFilter = - new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED); + }; + private final CompanionApplicationController.Callback mApplicationControllerCallback = + new CompanionApplicationController.Callback() { @Override - public void onReceive(Context context, Intent intent) { - int previousState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, -1); - int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); - Slog.d(LOG_TAG, "Received BT state transition broadcast: " - + BluetoothAdapter.nameForState(previousState) - + " -> " + BluetoothAdapter.nameForState(newState)); - - boolean bleOn = newState == BluetoothAdapter.STATE_ON - || newState == BluetoothAdapter.STATE_BLE_ON; - if (bleOn) { - if (mBluetoothAdapter.getBluetoothLeScanner() != null) { - startBleScan(); - } else { - Slog.wtf(LOG_TAG, "BLE on, but BluetoothLeScanner == null"); - } - } - } - } - - private class UnbindDeviceListenersRunnable implements Runnable { - - public String getJobId(String address) { - return "CDM_deviceGone_unbind_" + address; + public boolean onCompanionApplicationBindingDied(int userId, @NonNull String packageName) { + return onCompanionApplicationBindingDiedInternal(userId, packageName); } @Override - public void run() { - int size = mDevicesLastNearby.size(); - for (int i = 0; i < size; i++) { - String address = mDevicesLastNearby.keyAt(i); - Date lastNearby = mDevicesLastNearby.valueAt(i); - - if (isDeviceDisappeared(lastNearby)) { - final List<AssociationInfo> associations = - mAssociationStore.getAssociationsByAddress(address); - for (AssociationInfo association : associations) { - if (association.isNotifyOnDeviceNearby()) { - mCompanionDevicePresenceController.unbindDevicePresenceListener( - association.getPackageName(), association.getUserId()); - } - } - } - } - } - } - - private class TriggerDeviceDisappearedRunnable implements Runnable { - - private final String mAddress; - - TriggerDeviceDisappearedRunnable(String address) { - mAddress = address; - } - - public void schedule() { - mMainHandler.removeCallbacks(this); - mMainHandler.postDelayed(this, this, DEVICE_DISAPPEARED_TIMEOUT_MS); + public void onRebindCompanionApplicationTimeout(int userId, @NonNull String packageName) { + onRebindCompanionApplicationTimeoutInternal(userId, packageName); } + }; + private final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override - public void run() { - Slog.d(LOG_TAG, "TriggerDeviceDisappearedRunnable.run(address = " + mAddress + ")"); - if (!mCurrentlyConnectedDevices.contains(mAddress)) { - onDeviceDisappeared(mAddress); - } - } - } - - private void unscheduleTriggerDeviceDisappearedRunnable(String address) { - Runnable r = mTriggerDeviceDisappearedRunnables.get(address); - if (r != null) { - Slog.d(LOG_TAG, - "unscheduling TriggerDeviceDisappearedRunnable(address = " + address + ")"); - mMainHandler.removeCallbacks(r); - } - } - - private void onDeviceNearby(String address) { - Date timestamp = new Date(); - Date oldTimestamp = mDevicesLastNearby.put(address, timestamp); - - cancelUnbindDeviceListener(address); - - mTriggerDeviceDisappearedRunnables - .computeIfAbsent(address, addr -> new TriggerDeviceDisappearedRunnable(address)) - .schedule(); - - // Avoid spamming the app if device is already known to be nearby - boolean justAppeared = oldTimestamp == null - || timestamp.getTime() - oldTimestamp.getTime() >= DEVICE_DISAPPEARED_TIMEOUT_MS; - if (justAppeared) { - Slog.i(LOG_TAG, "onDeviceNearby(justAppeared, address = " + address + ")"); - final List<AssociationInfo> associations = - mAssociationStore.getAssociationsByAddress(address); - for (AssociationInfo association : associations) { - if (association.isNotifyOnDeviceNearby()) { - mCompanionDevicePresenceController.onDeviceNotifyAppeared(association, - getContext(), mMainHandler); - } - } - } - } - - private void onDeviceDisappeared(String address) { - Slog.i(LOG_TAG, "onDeviceDisappeared(address = " + address + ")"); - - boolean hasDeviceListeners = false; - final List<AssociationInfo> associations = - mAssociationStore.getAssociationsByAddress(address); - for (AssociationInfo association : associations) { - if (association.isNotifyOnDeviceNearby()) { - mCompanionDevicePresenceController.onDeviceNotifyDisappeared( - association, getContext(), mMainHandler); - hasDeviceListeners = true; - } - } - - cancelUnbindDeviceListener(address); - if (hasDeviceListeners) { - mMainHandler.postDelayed( - mUnbindDeviceListenersRunnable, - mUnbindDeviceListenersRunnable.getJobId(address), - DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS); - } - } - - private void cancelUnbindDeviceListener(String address) { - mMainHandler.removeCallbacks( - mUnbindDeviceListenersRunnable, mUnbindDeviceListenersRunnable.getJobId(address)); - } - - private void initBleScanning() { - Slog.i(LOG_TAG, "initBleScanning()"); - - boolean bluetoothReady = mBluetoothAdapter.registerServiceLifecycleCallback( - new BluetoothAdapter.ServiceLifecycleCallback() { - @Override - public void onBluetoothServiceUp() { - Slog.i(LOG_TAG, "Bluetooth stack is up"); - startBleScan(); - } - - @Override - public void onBluetoothServiceDown() { - Slog.w(LOG_TAG, "Bluetooth stack is down"); - } - }); - if (bluetoothReady) { - startBleScan(); - } - } - - void startBleScan() { - Slog.i(LOG_TAG, "startBleScan()"); - - List<ScanFilter> filters = getBleScanFilters(); - if (filters.isEmpty()) { - return; + public void onPackageRemoved(String packageName, int uid) { + onPackageRemoveOrDataClearedInternal(getChangingUserId(), packageName); } - BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); - if (scanner == null) { - Slog.w(LOG_TAG, "scanner == null (likely BLE isn't ON yet)"); - } else { - scanner.startScan( - filters, - new ScanSettings.Builder().setScanMode(SCAN_MODE_LOW_POWER).build(), - mBleScanCallback); - } - } - void restartBleScan() { - if (mBluetoothAdapter.getBluetoothLeScanner() != null) { - mBluetoothAdapter.getBluetoothLeScanner().stopScan(mBleScanCallback); - startBleScan(); - } else { - Slog.w(LOG_TAG, "BluetoothLeScanner is null (likely BLE isn't ON yet)."); + @Override + public void onPackageDataCleared(String packageName, int uid) { + onPackageRemoveOrDataClearedInternal(getChangingUserId(), packageName); } - } - private List<ScanFilter> getBleScanFilters() { - ArrayList<ScanFilter> result = new ArrayList<>(); - ArraySet<String> addressesSeen = new ArraySet<>(); - for (AssociationInfo association : mAssociationStore.getAssociations()) { - if (association.isSelfManaged()) { - continue; - } - String address = association.getDeviceMacAddressAsString(); - if (addressesSeen.contains(address)) { - continue; - } - if (association.isNotifyOnDeviceNearby()) { - result.add(new ScanFilter.Builder().setDeviceAddress(address).build()); - addressesSeen.add(address); - } + @Override + public void onPackageModified(String packageName) { + onPackageModifiedInternal(getChangingUserId(), packageName); } - return result; - } + }; static int getFirstAssociationIdForUser(@UserIdInt int userId) { // We want the IDs to start from 1, not 0. @@ -1247,82 +1025,6 @@ public class CompanionDeviceManagerService extends SystemService return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE; } - private class BluetoothDeviceConnectedListener - extends BluetoothAdapter.BluetoothConnectionCallback { - @Override - public void onDeviceConnected(BluetoothDevice device) { - CompanionDeviceManagerService.this.onDeviceConnected(device.getAddress()); - } - - @Override - public void onDeviceDisconnected(BluetoothDevice device, int reason) { - Slog.d(LOG_TAG, device.getAddress() + " disconnected w/ reason: (" + reason + ") " - + BluetoothAdapter.BluetoothConnectionCallback.disconnectReasonText(reason)); - CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress()); - } - } - - void checkUsesFeature(@NonNull String pkg, @UserIdInt int userId) { - if (getCallingUid() == SYSTEM_UID) return; - - final FeatureInfo[] requestedFeatures = getPackageInfo(pkg, userId).reqFeatures; - if (requestedFeatures != null) { - for (int i = 0; i < requestedFeatures.length; i++) { - if (FEATURE_COMPANION_DEVICE_SETUP.equals(requestedFeatures[i].name)) return; - } - } - - throw new IllegalStateException("Must declare uses-feature " - + FEATURE_COMPANION_DEVICE_SETUP - + " in manifest to use this API"); - } - - private void registerPackageMonitor() { - new PackageMonitor() { - @Override - public void onPackageRemoved(String packageName, int uid) { - final int userId = getChangingUserId(); - Slog.i(LOG_TAG, "onPackageRemoved() u" + userId + "/" + packageName); - - clearAssociationForPackage(userId, packageName); - } - - @Override - public void onPackageDataCleared(String packageName, int uid) { - final int userId = getChangingUserId(); - Slog.i(LOG_TAG, "onPackageDataCleared() u" + userId + "/" + packageName); - - clearAssociationForPackage(userId, packageName); - } - - @Override - public void onPackageModified(String packageName) { - final int userId = getChangingUserId(); - Slog.i(LOG_TAG, "onPackageModified() u" + userId + "/" + packageName); - - final List<AssociationInfo> associationsForPackage = - mAssociationStore.getAssociationsForPackage(userId, packageName); - for (AssociationInfo association : associationsForPackage) { - updateSpecialAccessPermissionForAssociatedPackage(association); - } - } - }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true); - } - - private void clearAssociationForPackage(@UserIdInt int userId, @NonNull String packageName) { - if (DEBUG) Slog.d(LOG_TAG, "clearAssociationForPackage() u" + userId + "/" + packageName); - - // First, unbind CompanionService if needed. - mCompanionDevicePresenceController.unbindDevicePresenceListener(packageName, userId); - - // Clear associations. - final List<AssociationInfo> associationsForPackage = - mAssociationStore.getAssociationsForPackage(userId, packageName); - for (AssociationInfo association : associationsForPackage) { - mAssociationStore.removeAssociation(association.getId()); - } - } - private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) { final Map<String, Set<Integer>> copy = new HashMap<>(); @@ -1334,16 +1036,14 @@ public class CompanionDeviceManagerService extends SystemService return Collections.unmodifiableMap(copy); } - private final class LocalService extends CompanionDeviceManagerServiceInternal { - private final CompanionDeviceManagerService mService; - - LocalService(CompanionDeviceManagerService service) { - mService = service; - } + private static <T> boolean containsEither(T[] array, T a, T b) { + return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b); + } + private class LocalService extends CompanionDeviceManagerServiceInternal { @Override public void associationCleanUp(String profile) { - mService.associationCleanUp(profile); + CompanionDeviceManagerService.this.associationCleanUp(profile); } } diff --git a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java deleted file mode 100644 index fc6681705cb6..000000000000 --- a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion; - -import static android.Manifest.permission.BIND_COMPANION_DEVICE_SERVICE; -import static android.content.Context.BIND_IMPORTANT; - -import static com.android.internal.util.CollectionUtils.filter; - -import android.annotation.NonNull; -import android.companion.AssociationInfo; -import android.companion.CompanionDeviceService; -import android.companion.ICompanionDeviceService; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.Handler; -import android.util.ArrayMap; -import android.util.Slog; - -import com.android.internal.infra.PerUser; -import com.android.internal.infra.ServiceConnector; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * This class creates/removes {@link ServiceConnector}s between {@link CompanionDeviceService} and - * the companion apps. The controller also will notify the companion apps with device status. - */ -public class CompanionDevicePresenceController { - private static final String LOG_TAG = "CompanionDevicePresenceController"; - PerUser<ArrayMap<String, List<BoundService>>> mBoundServices; - private static final String META_DATA_KEY_PRIMARY = "android.companion.primary"; - private final CompanionDeviceManagerService mService; - - public CompanionDevicePresenceController(CompanionDeviceManagerService service) { - mService = service; - mBoundServices = new PerUser<ArrayMap<String, List<BoundService>>>() { - @NonNull - @Override - protected ArrayMap<String, List<BoundService>> create(int userId) { - return new ArrayMap<>(); - } - }; - } - - void onDeviceNotifyAppeared(AssociationInfo association, Context context, Handler handler) { - for (BoundService boundService : getDeviceListenerServiceConnector( - association, context, handler)) { - if (boundService.mIsPrimary) { - Slog.i(LOG_TAG, - "Sending onDeviceAppeared to " + association.getPackageName() + ")"); - boundService.mServiceConnector.run( - service -> service.onDeviceAppeared(association)); - } else { - Slog.i(LOG_TAG, "Connecting to " + boundService.mComponentName); - boundService.mServiceConnector.connect(); - } - } - } - - void onDeviceNotifyDisappeared(AssociationInfo association, Context context, Handler handler) { - for (BoundService boundService : getDeviceListenerServiceConnector( - association, context, handler)) { - if (boundService.mIsPrimary) { - Slog.i(LOG_TAG, - "Sending onDeviceDisappeared to " + association.getPackageName() + ")"); - boundService.mServiceConnector.run(service -> - service.onDeviceDisappeared(association)); - } - } - } - - void onDeviceNotifyDisappearedAndUnbind(AssociationInfo association, - Context context, Handler handler) { - for (BoundService boundService : getDeviceListenerServiceConnector( - association, context, handler)) { - if (boundService.mIsPrimary) { - Slog.i(LOG_TAG, - "Sending onDeviceDisappeared to " + association.getPackageName() + ")"); - boundService.mServiceConnector.post( - service -> { - service.onDeviceDisappeared(association); - }).thenRun(() -> unbindDevicePresenceListener( - association.getPackageName(), association.getUserId())); - } - } - } - - void unbindDevicePresenceListener(String packageName, int userId) { - List<BoundService> boundServices = mBoundServices.forUser(userId) - .remove(packageName); - if (boundServices != null) { - for (BoundService boundService: boundServices) { - Slog.d(LOG_TAG, "Unbinding the serviceConnector: " + boundService.mComponentName); - boundService.mServiceConnector.unbind(); - } - } - } - - private List<BoundService> getDeviceListenerServiceConnector(AssociationInfo a, Context context, - Handler handler) { - return mBoundServices.forUser(a.getUserId()).computeIfAbsent( - a.getPackageName(), - pkg -> createDeviceListenerServiceConnector(a, context, handler)); - } - - private List<BoundService> createDeviceListenerServiceConnector(AssociationInfo a, - Context context, Handler handler) { - List<ResolveInfo> resolveInfos = context - .getPackageManager() - .queryIntentServicesAsUser(new Intent(CompanionDeviceService.SERVICE_INTERFACE), - PackageManager.GET_META_DATA, a.getUserId()); - List<ResolveInfo> packageResolveInfos = filter(resolveInfos, - info -> Objects.equals(info.serviceInfo.packageName, a.getPackageName())); - List<BoundService> serviceConnectors = new ArrayList<>(); - if (!validatePackageInfo(packageResolveInfos, a)) { - return serviceConnectors; - } - for (ResolveInfo packageResolveInfo : packageResolveInfos) { - boolean isPrimary = (packageResolveInfo.serviceInfo.metaData != null - && packageResolveInfo.serviceInfo.metaData.getBoolean(META_DATA_KEY_PRIMARY)) - || packageResolveInfos.size() == 1; - ComponentName componentName = packageResolveInfo.serviceInfo.getComponentName(); - - Slog.i(LOG_TAG, "Initializing CompanionDeviceService binding for " + componentName); - - ServiceConnector<ICompanionDeviceService> serviceConnector = - new ServiceConnector.Impl<ICompanionDeviceService>(context, - new Intent(CompanionDeviceService.SERVICE_INTERFACE).setComponent( - componentName), BIND_IMPORTANT, a.getUserId(), - ICompanionDeviceService.Stub::asInterface) { - @Override - protected long getAutoDisconnectTimeoutMs() { - // Service binding is managed manually based on corresponding device - // being nearby - return -1; - } - - @Override - public void binderDied() { - super.binderDied(); - if (a.isSelfManaged()) { - mBoundServices.forUser(a.getUserId()).remove(a.getPackageName()); - mService.mPresentSelfManagedDevices.remove(a.getId()); - } else { - // Re-connect to the service if process gets killed - handler.postDelayed( - this::connect, - CompanionDeviceManagerService - .DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS); - } - } - }; - - serviceConnectors.add(new BoundService(componentName, isPrimary, serviceConnector)); - } - return serviceConnectors; - } - - private boolean validatePackageInfo(List<ResolveInfo> packageResolveInfos, - AssociationInfo association) { - if (packageResolveInfos.size() == 0 || packageResolveInfos.size() > 5) { - Slog.e(LOG_TAG, "Device presence listener package must have at least one and not " - + "more than five CompanionDeviceService(s) declared. But " - + association.getPackageName() - + " has " + packageResolveInfos.size()); - return false; - } - - int primaryCount = 0; - for (ResolveInfo packageResolveInfo : packageResolveInfos) { - String servicePermission = packageResolveInfo.serviceInfo.permission; - if (!BIND_COMPANION_DEVICE_SERVICE.equals(servicePermission)) { - Slog.e(LOG_TAG, "Binding CompanionDeviceService must have " - + BIND_COMPANION_DEVICE_SERVICE + " permission."); - return false; - } - - if (packageResolveInfo.serviceInfo.metaData != null - && packageResolveInfo.serviceInfo.metaData.getBoolean(META_DATA_KEY_PRIMARY)) { - primaryCount++; - if (primaryCount > 1) { - Slog.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService " - + "to be bound but " - + association.getPackageName() + "has " + primaryCount); - return false; - } - } - } - - if (packageResolveInfos.size() > 1 && primaryCount == 0) { - Slog.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService " - + "to be bound when declare more than one CompanionDeviceService but " - + association.getPackageName() + " has " + primaryCount); - return false; - } - - if (packageResolveInfos.size() == 1 && primaryCount != 0) { - Slog.w(LOG_TAG, "Do not need the primary metadata if there's only one" - + " CompanionDeviceService " + "but " + association.getPackageName() - + " has " + primaryCount); - } - - return true; - } - - private static class BoundService { - private final ComponentName mComponentName; - private final boolean mIsPrimary; - private final ServiceConnector<ICompanionDeviceService> mServiceConnector; - - BoundService(ComponentName componentName, - boolean isPrimary, ServiceConnector<ICompanionDeviceService> serviceConnector) { - this.mComponentName = componentName; - this.mIsPrimary = isPrimary; - this.mServiceConnector = serviceConnector; - } - } -} diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java index 777917cd5a9e..f2a58b74a65a 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java @@ -17,6 +17,7 @@ package com.android.server.companion; import static android.content.Context.BIND_IMPORTANT; +import static android.os.Process.THREAD_PRIORITY_DEFAULT; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,10 +29,12 @@ import android.companion.ICompanionDeviceService; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.os.Handler; import android.os.IBinder; import android.util.Log; import com.android.internal.infra.ServiceConnector; +import com.android.server.ServiceThread; /** * Manages a connection (binding) to an instance of {@link CompanionDeviceService} running in the @@ -106,6 +109,19 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe return ICompanionDeviceService.Stub.asInterface(service); } + /** + * Overrides {@link ServiceConnector.Impl#getJobHandler()} to provide an alternative Thread + * ("in form of" a {@link Handler}) to process jobs on. + * <p> + * (By default, {@link ServiceConnector.Impl} process jobs on the + * {@link android.os.Looper#getMainLooper() MainThread} which is a shared singleton thread + * within system_server and thus tends to get heavily congested) + */ + @Override + protected @NonNull Handler getJobHandler() { + return getServiceThread().getThreadHandler(); + } + @Override protected long getAutoDisconnectTimeoutMs() { // Do NOT auto-disconnect. @@ -116,4 +132,25 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe return new Intent(CompanionDeviceService.SERVICE_INTERFACE) .setComponent(componentName); } + + private static @NonNull ServiceThread getServiceThread() { + if (sServiceThread == null) { + synchronized (CompanionDeviceManagerService.class) { + if (sServiceThread == null) { + sServiceThread = new ServiceThread("companion-device-service-connector", + THREAD_PRIORITY_DEFAULT, /* allowIo */ false); + sServiceThread.start(); + } + } + } + return sServiceThread; + } + + /** + * A worker thread for the {@link ServiceConnector} to process jobs on. + * + * <p> + * Do NOT reference directly, use {@link #getServiceThread()} method instead. + */ + private static volatile @Nullable ServiceThread sServiceThread; } diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java index fcb14a4f04d0..a2b20593a9cb 100644 --- a/services/companion/java/com/android/server/companion/PackageUtils.java +++ b/services/companion/java/com/android/server/companion/PackageUtils.java @@ -18,10 +18,9 @@ package com.android.server.companion; import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP; import static android.content.pm.PackageManager.GET_CONFIGURATIONS; -import static android.content.pm.PackageManager.GET_META_DATA; import static android.content.pm.PackageManager.GET_PERMISSIONS; -import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; +import static com.android.server.companion.CompanionDeviceManagerService.TAG; import android.Manifest; import android.annotation.NonNull; @@ -53,7 +52,8 @@ import java.util.Map; final class PackageUtils { private static final Intent COMPANION_SERVICE_INTENT = new Intent(CompanionDeviceService.SERVICE_INTERFACE); - private static final String META_DATA_PRIMARY_TAG = "android.companion.primary"; + private static final String PROPERTY_PRIMARY_TAG = + "android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE"; static @Nullable PackageInfo getPackageInfo(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName) { @@ -84,9 +84,8 @@ final class PackageUtils { static @NonNull Map<String, List<ComponentName>> getCompanionServicesForUser( @NonNull Context context, @UserIdInt int userId) { final PackageManager pm = context.getPackageManager(); - final ResolveInfoFlags flags = ResolveInfoFlags.of(GET_META_DATA); - final List<ResolveInfo> companionServices = - pm.queryIntentServicesAsUser(COMPANION_SERVICE_INTENT, flags, userId); + final List<ResolveInfo> companionServices = pm.queryIntentServicesAsUser( + COMPANION_SERVICE_INTENT, ResolveInfoFlags.of(0), userId); final Map<String, List<ComponentName>> packageNameToServiceInfoList = new HashMap<>(); @@ -96,7 +95,7 @@ final class PackageUtils { final boolean requiresPermission = Manifest.permission.BIND_COMPANION_DEVICE_SERVICE .equals(resolveInfo.serviceInfo.permission); if (!requiresPermission) { - Slog.w(LOG_TAG, "CompanionDeviceService " + Slog.w(TAG, "CompanionDeviceService " + service.getComponentName().flattenToShortString() + " must require " + "android.permission.BIND_COMPANION_DEVICE_SERVICE"); continue; @@ -109,7 +108,8 @@ final class PackageUtils { service.packageName, it -> new LinkedList<>()); final ComponentName componentName = service.getComponentName(); - if (isPrimaryCompanionDeviceService(service)) { + + if (isPrimaryCompanionDeviceService(pm, componentName)) { // "Primary" service should be at the head of the list. services.addFirst(componentName); } else { @@ -120,7 +120,12 @@ final class PackageUtils { return packageNameToServiceInfoList; } - private static boolean isPrimaryCompanionDeviceService(ServiceInfo service) { - return service.metaData != null && service.metaData.getBoolean(META_DATA_PRIMARY_TAG); + private static boolean isPrimaryCompanionDeviceService(@NonNull PackageManager pm, + @NonNull ComponentName componentName) { + try { + return pm.getProperty(PROPERTY_PRIMARY_TAG, componentName).getBoolean(); + } catch (PackageManager.NameNotFoundException e) { + return false; + } } } diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java index 0e593e14a037..ac1bf1bd8c23 100644 --- a/services/companion/java/com/android/server/companion/PermissionsUtils.java +++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java @@ -36,6 +36,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; import android.content.Context; @@ -190,6 +191,19 @@ final class PermissionsUtils { return checkCallerCanManageCompanionDevice(context); } + static @Nullable AssociationInfo sanitizeWithCallerChecks(@NonNull Context context, + @Nullable AssociationInfo association) { + if (association == null) return null; + + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + if (!checkCallerCanManageAssociationsForPackage(context, userId, packageName)) { + return null; + } + + return association; + } + private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) { try { return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED; diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java index 904283f4e60e..35488a80b78b 100644 --- a/services/companion/java/com/android/server/companion/RolesUtils.java +++ b/services/companion/java/com/android/server/companion/RolesUtils.java @@ -19,6 +19,7 @@ package com.android.server.companion; import static android.app.role.RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP; import static com.android.server.companion.CompanionDeviceManagerService.DEBUG; +import static com.android.server.companion.CompanionDeviceManagerService.TAG; import android.annotation.NonNull; import android.annotation.SuppressLint; @@ -35,7 +36,6 @@ import java.util.List; /** Utility methods for accessing {@link RoleManager} APIs. */ @SuppressLint("LongLogTag") final class RolesUtils { - private static final String TAG = CompanionDeviceManagerService.LOG_TAG; static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName, @NonNull String role) { diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java new file mode 100644 index 000000000000..2c42c910b9ab --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual; + +import static android.hardware.camera2.CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_UNSUPPORTED; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraInjectionSession; +import android.hardware.camera2.CameraManager; +import android.util.ArrayMap; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; + +/** + * Handles blocking access to the camera for apps running on virtual devices. + */ +class CameraAccessController extends CameraManager.AvailabilityCallback { + private static final String TAG = "CameraAccessController"; + + private final Object mLock = new Object(); + + private final Context mContext; + private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal; + CameraAccessBlockedCallback mBlockedCallback; + private CameraManager mCameraManager; + private boolean mListeningForCameraEvents; + private PackageManager mPackageManager; + + @GuardedBy("mLock") + private ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>(); + + static class InjectionSessionData { + public int appUid; + public ArrayMap<String, CameraInjectionSession> cameraIdToSession = new ArrayMap<>(); + } + + interface CameraAccessBlockedCallback { + /** + * Called whenever an app was blocked from accessing a camera. + * @param appUid uid for the app which was blocked + */ + void onCameraAccessBlocked(int appUid); + } + + CameraAccessController(Context context, + VirtualDeviceManagerInternal virtualDeviceManagerInternal, + CameraAccessBlockedCallback blockedCallback) { + mContext = context; + mVirtualDeviceManagerInternal = virtualDeviceManagerInternal; + mBlockedCallback = blockedCallback; + mCameraManager = mContext.getSystemService(CameraManager.class); + mPackageManager = mContext.getPackageManager(); + } + + /** + * Starts watching for camera access by uids running on a virtual device, if we were not + * already doing so. + */ + public void startObservingIfNeeded() { + synchronized (mLock) { + if (!mListeningForCameraEvents) { + mCameraManager.registerAvailabilityCallback(mContext.getMainExecutor(), this); + mListeningForCameraEvents = true; + } + } + } + + /** + * Stop watching for camera access. + */ + public void stopObserving() { + synchronized (mLock) { + mCameraManager.unregisterAvailabilityCallback(this); + mListeningForCameraEvents = false; + } + } + + @Override + public void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) { + synchronized (mLock) { + try { + final ApplicationInfo ainfo = + mPackageManager.getApplicationInfo(packageName, 0); + InjectionSessionData data = mPackageToSessionData.get(packageName); + if (!mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(ainfo.uid)) { + CameraInjectionSession existingSession = + (data != null) ? data.cameraIdToSession.get(cameraId) : null; + if (existingSession != null) { + existingSession.close(); + data.cameraIdToSession.remove(cameraId); + if (data.cameraIdToSession.isEmpty()) { + mPackageToSessionData.remove(packageName); + } + } + return; + } + if (data == null) { + data = new InjectionSessionData(); + data.appUid = ainfo.uid; + mPackageToSessionData.put(packageName, data); + } + if (data.cameraIdToSession.containsKey(cameraId)) { + return; + } + startBlocking(packageName, cameraId); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, "onCameraOpened - unknown package " + packageName, e); + return; + } + } + } + + @Override + public void onCameraClosed(@NonNull String cameraId) { + synchronized (mLock) { + for (int i = mPackageToSessionData.size() - 1; i >= 0; i--) { + InjectionSessionData data = mPackageToSessionData.valueAt(i); + CameraInjectionSession session = data.cameraIdToSession.get(cameraId); + if (session != null) { + session.close(); + data.cameraIdToSession.remove(cameraId); + if (data.cameraIdToSession.isEmpty()) { + mPackageToSessionData.removeAt(i); + } + } + } + } + } + + /** + * Turns on blocking for a particular camera and package. + */ + private void startBlocking(String packageName, String cameraId) { + try { + mCameraManager.injectCamera(packageName, cameraId, /* externalCamId */ "", + mContext.getMainExecutor(), + new CameraInjectionSession.InjectionStatusCallback() { + @Override + public void onInjectionSucceeded( + @NonNull CameraInjectionSession session) { + CameraAccessController.this.onInjectionSucceeded(cameraId, packageName, + session); + } + + @Override + public void onInjectionError(@NonNull int errorCode) { + CameraAccessController.this.onInjectionError(cameraId, packageName, + errorCode); + } + }); + } catch (CameraAccessException e) { + Slog.e(TAG, + "Failed to injectCamera for cameraId:" + cameraId + " package:" + packageName, + e); + } + } + + private void onInjectionSucceeded(String cameraId, String packageName, + @NonNull CameraInjectionSession session) { + synchronized (mLock) { + InjectionSessionData data = mPackageToSessionData.get(packageName); + if (data == null) { + Slog.e(TAG, "onInjectionSucceeded didn't find expected entry for package " + + packageName); + session.close(); + return; + } + CameraInjectionSession existingSession = data.cameraIdToSession.put(cameraId, session); + if (existingSession != null) { + Slog.e(TAG, "onInjectionSucceeded found unexpected existing session for camera " + + cameraId); + existingSession.close(); + } + } + } + + private void onInjectionError(String cameraId, String packageName, @NonNull int errorCode) { + if (errorCode != ERROR_INJECTION_UNSUPPORTED) { + // ERROR_INJECTION_UNSUPPORTED means that there wasn't an external camera to map to the + // internal camera, which is expected when using the injection interface as we are in + // this class to simply block camera access. Any other error is unexpected. + Slog.e(TAG, "Unexpected injection error code:" + errorCode + " for camera:" + cameraId + + " and package:" + packageName); + return; + } + synchronized (mLock) { + InjectionSessionData data = mPackageToSessionData.get(packageName); + if (data != null) { + mBlockedCallback.onCameraAccessBlocked(data.appUid); + } + } + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index dafcc60fb651..4afa96c8072d 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -47,9 +47,17 @@ import java.util.function.Consumer; /** * A controller to control the policies of the windows that can be displayed on the virtual display. */ -class GenericWindowPolicyController extends DisplayWindowPolicyController { +public class GenericWindowPolicyController extends DisplayWindowPolicyController { - private static final String TAG = "VirtualDeviceManager"; + private static final String TAG = "GenericWindowPolicyController"; + + /** Interface to listen running applications change on virtual display. */ + public interface RunningAppsChangedListener { + /** + * Notifies the running applications change. + */ + void onRunningAppsChanged(ArraySet<Integer> runningUids); + } private static final ComponentName BLOCKED_APP_STREAMING_COMPONENT = new ComponentName("android", BlockedAppStreamingActivity.class.getName()); @@ -74,6 +82,9 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { @Nullable private final ActivityListener mActivityListener; private final Handler mHandler = new Handler(Looper.getMainLooper()); + @Nullable + private RunningAppsChangedListener mRunningAppsChangedListener; + /** * Creates a window policy controller that is generic to the different use cases of virtual * device. @@ -84,7 +95,7 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { * @param activityListener Activity listener to listen for activity changes. The display ID * is not populated in this callback and is always {@link Display#INVALID_DISPLAY}. */ - GenericWindowPolicyController(int windowFlags, int systemWindowFlags, + public GenericWindowPolicyController(int windowFlags, int systemWindowFlags, @NonNull ArraySet<UserHandle> allowedUsers, @Nullable Set<ComponentName> allowedActivities, @Nullable Set<ComponentName> blockedActivities, @@ -98,6 +109,11 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { mActivityListener = activityListener; } + /** Sets listener for running applications change. */ + public void setRunningAppsChangedListener(@Nullable RunningAppsChangedListener listener) { + mRunningAppsChangedListener = listener; + } + @Override public boolean canContainActivities(@NonNull List<ActivityInfo> activities) { // Can't display all the activities if any of them don't want to be displayed. @@ -139,6 +155,9 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { // Post callback on the main thread so it doesn't block activity launching mHandler.post(() -> mActivityListener.onDisplayEmpty(Display.INVALID_DISPLAY)); } + if (mRunningAppsChangedListener != null) { + mRunningAppsChangedListener.onRunningAppsChanged(runningUids); + } } /** diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 075d96dd7ad4..387d911672a8 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -23,6 +23,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.StringRes; import android.app.Activity; import android.app.ActivityOptions; import android.app.PendingIntent; @@ -32,6 +34,7 @@ import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.VirtualDeviceManager.ActivityListener; import android.companion.virtual.VirtualDeviceParams; +import android.companion.virtual.audio.IAudioSessionCallback; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -55,11 +58,14 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; +import android.view.Display; +import android.widget.Toast; import android.window.DisplayWindowPolicyController; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.BlockedAppStreamingActivity; +import com.android.server.companion.virtual.audio.VirtualAudioController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -78,6 +84,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final PendingTrampolineCallback mPendingTrampolineCallback; private final int mOwnerUid; private final InputController mInputController; + private VirtualAudioController mVirtualAudioController; @VisibleForTesting final Set<Integer> mVirtualDisplayIds = new ArraySet<>(); private final OnDeviceCloseListener mListener; @@ -245,6 +252,46 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub close(); } + @VisibleForTesting + VirtualAudioController getVirtualAudioControllerForTesting() { + return mVirtualAudioController; + } + + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @Override // Binder call + public void onAudioSessionStarting(int displayId, IAudioSessionCallback callback) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CREATE_VIRTUAL_DEVICE, + "Permission required to start audio session"); + synchronized (mVirtualDeviceLock) { + if (!mVirtualDisplayIds.contains(displayId)) { + throw new SecurityException( + "Cannot start audio session for a display not associated with this virtual " + + "device"); + } + + if (mVirtualAudioController == null) { + mVirtualAudioController = new VirtualAudioController(mContext); + GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId); + mVirtualAudioController.startListening(gwpc, callback); + } + } + } + + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @Override // Binder call + public void onAudioSessionEnded() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CREATE_VIRTUAL_DEVICE, + "Permission required to stop audio session"); + synchronized (mVirtualDeviceLock) { + if (mVirtualAudioController != null) { + mVirtualAudioController.stopListening(); + mVirtualAudioController = null; + } + } + } + @Override // Binder call public void createVirtualKeyboard( int displayId, @@ -539,6 +586,30 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub return false; } + /** + * Shows a toast on virtual displays owned by this device which have a given uid running. + */ + void showToastWhereUidIsRunning(int uid, @StringRes int resId, @Toast.Duration int duration) { + synchronized (mVirtualDeviceLock) { + DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); + final int size = mWindowPolicyControllers.size(); + for (int i = 0; i < size; i++) { + if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) { + int displayId = mWindowPolicyControllers.keyAt(i); + Display display = displayManager.getDisplay(displayId); + if (display != null && display.isValid()) { + Toast.makeText(mContext.createDisplayContext(display), resId, + duration).show(); + } + } + } + } + } + + boolean isDisplayOwnedByVirtualDevice(int displayId) { + return mVirtualDisplayIds.contains(displayId); + } + interface OnDeviceCloseListener { void onClose(int associationId); } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index b507110d5473..c7d8daac606b 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -39,6 +39,7 @@ import android.os.RemoteException; import android.util.ExceptionUtils; import android.util.Slog; import android.util.SparseArray; +import android.widget.Toast; import android.window.DisplayWindowPolicyController; import com.android.internal.annotations.GuardedBy; @@ -62,8 +63,10 @@ public class VirtualDeviceManagerService extends SystemService { private final Object mVirtualDeviceManagerLock = new Object(); private final VirtualDeviceManagerImpl mImpl; + private VirtualDeviceManagerInternal mLocalService; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler); + private final CameraAccessController mCameraAccessController; /** * Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for @@ -90,6 +93,9 @@ public class VirtualDeviceManagerService extends SystemService { public VirtualDeviceManagerService(Context context) { super(context); mImpl = new VirtualDeviceManagerImpl(); + mLocalService = new LocalService(); + mCameraAccessController = new CameraAccessController(getContext(), mLocalService, + this::onCameraAccessBlocked); } private final ActivityInterceptorCallback mActivityInterceptorCallback = @@ -118,8 +124,7 @@ public class VirtualDeviceManagerService extends SystemService { @Override public void onStart() { publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl); - publishLocalService(VirtualDeviceManagerInternal.class, new LocalService()); - + publishLocalService(VirtualDeviceManagerInternal.class, mLocalService); ActivityTaskManagerInternal activityTaskManagerInternal = getLocalService( ActivityTaskManagerInternal.class); activityTaskManagerInternal.registerActivityStartInterceptor( @@ -169,6 +174,16 @@ public class VirtualDeviceManagerService extends SystemService { } } + void onCameraAccessBlocked(int appUid) { + synchronized (mVirtualDeviceManagerLock) { + int size = mVirtualDevices.size(); + for (int i = 0; i < size; i++) { + mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid, + com.android.internal.R.string.vdm_camera_access_denied, Toast.LENGTH_LONG); + } + } + } + class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements VirtualDeviceImpl.PendingTrampolineCallback { @@ -205,10 +220,14 @@ public class VirtualDeviceManagerService extends SystemService { public void onClose(int associationId) { synchronized (mVirtualDeviceManagerLock) { mVirtualDevices.remove(associationId); + if (mVirtualDevices.size() == 0) { + mCameraAccessController.stopObserving(); + } } } }, this, activityListener, params); + mCameraAccessController.startObservingIfNeeded(); mVirtualDevices.put(associationInfo.getId(), virtualDevice); return virtualDevice; } @@ -329,6 +348,19 @@ public class VirtualDeviceManagerService extends SystemService { } return false; } + + @Override + public boolean isDisplayOwnedByAnyVirtualDevice(int displayId) { + synchronized (mVirtualDeviceManagerLock) { + int size = mVirtualDevices.size(); + for (int i = 0; i < size; i++) { + if (mVirtualDevices.valueAt(i).isDisplayOwnedByVirtualDevice(displayId)) { + return true; + } + } + } + return false; + } } private static final class PendingTrampolineMap { diff --git a/services/companion/java/com/android/server/companion/virtual/audio/AudioPlaybackDetector.java b/services/companion/java/com/android/server/companion/virtual/audio/AudioPlaybackDetector.java new file mode 100644 index 000000000000..2d7291300e23 --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/audio/AudioPlaybackDetector.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual.audio; + +import android.annotation.NonNull; +import android.content.Context; +import android.media.AudioManager; +import android.media.AudioPlaybackConfiguration; + +import java.util.List; + +/** + * Wrapper class for other classes to listen {@link #onPlaybackConfigChanged(List)} by implementing + * {@link AudioPlaybackCallback} instead of inheriting the + * {@link AudioManager.AudioPlaybackCallback}. + */ +final class AudioPlaybackDetector extends AudioManager.AudioPlaybackCallback { + + /** + * Interface to listen {@link #onPlaybackConfigChanged(List)} from + * {@link AudioManager.AudioPlaybackCallback}. + */ + interface AudioPlaybackCallback { + void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs); + } + + private final AudioManager mAudioManager; + private AudioPlaybackCallback mAudioPlaybackCallback; + + AudioPlaybackDetector(Context context) { + mAudioManager = context.getSystemService(AudioManager.class); + } + + void register(@NonNull AudioPlaybackCallback callback) { + mAudioPlaybackCallback = callback; + mAudioManager.registerAudioPlaybackCallback(/* cb= */ this, /* handler= */ null); + } + + void unregister() { + mAudioPlaybackCallback = null; + mAudioManager.unregisterAudioPlaybackCallback(/* cb= */ this); + } + + @Override + public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) { + super.onPlaybackConfigChanged(configs); + if (mAudioPlaybackCallback != null) { + mAudioPlaybackCallback.onPlaybackConfigChanged(configs); + } + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/audio/AudioRecordingDetector.java b/services/companion/java/com/android/server/companion/virtual/audio/AudioRecordingDetector.java new file mode 100644 index 000000000000..c20414529f5b --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/audio/AudioRecordingDetector.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual.audio; + +import android.annotation.NonNull; +import android.content.Context; +import android.media.AudioManager; +import android.media.AudioRecordingConfiguration; + +import java.util.List; + +/** + * Wrapper class for other classes to listen {@link #onRecordingConfigChanged(List)} by implementing + * {@link AudioRecordingCallback} instead of inheriting the + * {@link AudioManager.AudioRecordingCallback}. + */ +final class AudioRecordingDetector extends AudioManager.AudioRecordingCallback { + + /** + * Interface to listen {@link #onRecordingConfigChanged(List)} from + * {@link AudioManager.AudioRecordingCallback}. + */ + interface AudioRecordingCallback { + void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs); + } + + private final AudioManager mAudioManager; + private AudioRecordingCallback mAudioRecordingCallback; + + AudioRecordingDetector(Context context) { + mAudioManager = context.getSystemService(AudioManager.class); + } + + void register(@NonNull AudioRecordingCallback callback) { + mAudioRecordingCallback = callback; + mAudioManager.registerAudioRecordingCallback(/* cb= */ this, /* handler= */ null); + } + + void unregister() { + mAudioRecordingCallback = null; + mAudioManager.unregisterAudioRecordingCallback(/* cb= */ this); + } + + @Override + public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) { + super.onRecordingConfigChanged(configs); + if (mAudioRecordingCallback != null) { + mAudioRecordingCallback.onRecordingConfigChanged(configs); + } + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java new file mode 100644 index 000000000000..1dc87d68aeae --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual.audio; + +import static android.media.AudioPlaybackConfiguration.PLAYER_STATE_STARTED; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.companion.virtual.audio.IAudioSessionCallback; +import android.content.Context; +import android.media.AudioManager; +import android.media.AudioPlaybackConfiguration; +import android.media.AudioRecordingConfiguration; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.companion.virtual.GenericWindowPolicyController; +import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener; +import com.android.server.companion.virtual.audio.AudioPlaybackDetector.AudioPlaybackCallback; +import com.android.server.companion.virtual.audio.AudioRecordingDetector.AudioRecordingCallback; + +import java.util.ArrayList; +import java.util.List; + +/** + * Manages audio streams associated with a {@link VirtualAudioDevice}. Responsible for monitoring + * running applications and playback configuration changes in order to correctly re-route audio and + * then notify clients of these changes. + */ +public final class VirtualAudioController implements AudioPlaybackCallback, + AudioRecordingCallback, RunningAppsChangedListener { + private static final String TAG = "VirtualAudioController"; + private static final int UPDATE_REROUTING_APPS_DELAY_MS = 2000; + + private final Context mContext; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final Runnable mUpdateAudioRoutingRunnable = this::notifyAppsNeedingAudioRoutingChanged; + private final AudioPlaybackDetector mAudioPlaybackDetector; + private final AudioRecordingDetector mAudioRecordingDetector; + private final Object mLock = new Object(); + @GuardedBy("mLock") + private final ArraySet<Integer> mRunningAppUids = new ArraySet<>(); + @GuardedBy("mLock") + private ArraySet<Integer> mPlayingAppUids = new ArraySet<>(); + private GenericWindowPolicyController mGenericWindowPolicyController; + private final Object mCallbackLock = new Object(); + @Nullable + @GuardedBy("mCallbackLock") + private IAudioSessionCallback mCallback; + + public VirtualAudioController(Context context) { + mContext = context; + mAudioPlaybackDetector = new AudioPlaybackDetector(context); + mAudioRecordingDetector = new AudioRecordingDetector(context); + } + + /** + * Starts to listen to running applications and audio configuration changes on virtual display + * for audio capture and injection. + */ + public void startListening( + @NonNull GenericWindowPolicyController genericWindowPolicyController, + @Nullable IAudioSessionCallback callback) { + mGenericWindowPolicyController = genericWindowPolicyController; + mGenericWindowPolicyController.setRunningAppsChangedListener(/* listener= */ this); + synchronized (mCallbackLock) { + mCallback = callback; + } + synchronized (mLock) { + mRunningAppUids.clear(); + mPlayingAppUids.clear(); + } + mAudioPlaybackDetector.register(/* callback= */ this); + mAudioRecordingDetector.register(/* callback= */ this); + } + + /** + * Stops listening to running applications and audio configuration changes on virtual display + * for audio capture and injection. + */ + public void stopListening() { + if (mHandler.hasCallbacks(mUpdateAudioRoutingRunnable)) { + mHandler.removeCallbacks(mUpdateAudioRoutingRunnable); + } + mAudioPlaybackDetector.unregister(); + mAudioRecordingDetector.unregister(); + if (mGenericWindowPolicyController != null) { + mGenericWindowPolicyController.setRunningAppsChangedListener(/* listener= */ null); + mGenericWindowPolicyController = null; + } + synchronized (mCallbackLock) { + mCallback = null; + } + } + + @Override + public void onRunningAppsChanged(ArraySet<Integer> runningUids) { + synchronized (mLock) { + if (mRunningAppUids.equals(runningUids)) { + // Ignore no-op events. + return; + } + mRunningAppUids.clear(); + mRunningAppUids.addAll(runningUids); + + ArraySet<Integer> oldPlayingAppUids = mPlayingAppUids; + + // Update the list of playing apps after caching the old list, and before checking if + // the list of playing apps is empty. This is a subset of the running apps, so we need + // to update this here as well. + AudioManager audioManager = mContext.getSystemService(AudioManager.class); + List<AudioPlaybackConfiguration> configs = + audioManager.getActivePlaybackConfigurations(); + mPlayingAppUids = findPlayingAppUids(configs, mRunningAppUids); + + // Do not change rerouted applications while any application is playing, or the sound + // will be leaked from phone during the transition. Delay the change until we detect + // there is no application is playing in onPlaybackConfigChanged(). + if (!mPlayingAppUids.isEmpty()) { + Slog.i(TAG, "Audio is playing, do not change rerouted apps"); + return; + } + + // An application previously playing audio was removed from the display. + if (!oldPlayingAppUids.isEmpty()) { + // Delay changing the rerouted application when the last application playing audio + // was removed from virtual device, or the sound will be leaked from phone side + // during the transition. + Slog.i(TAG, "The last playing app removed, delay change rerouted apps"); + if (mHandler.hasCallbacks(mUpdateAudioRoutingRunnable)) { + mHandler.removeCallbacks(mUpdateAudioRoutingRunnable); + } + mHandler.postDelayed(mUpdateAudioRoutingRunnable, UPDATE_REROUTING_APPS_DELAY_MS); + return; + } + } + + // Normal case with no application playing, just update routing. + notifyAppsNeedingAudioRoutingChanged(); + } + + @Override + public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) { + updatePlayingApplications(configs); + + List<AudioPlaybackConfiguration> audioPlaybackConfigurations; + synchronized (mLock) { + // Filter configurations of applications running on virtual device. + audioPlaybackConfigurations = findPlaybackConfigurations(configs, mRunningAppUids); + } + synchronized (mCallbackLock) { + if (mCallback != null) { + try { + mCallback.onPlaybackConfigChanged(audioPlaybackConfigurations); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when calling onPlaybackConfigChanged", e); + } + } + } + } + + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @Override + public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) { + List<AudioRecordingConfiguration> audioRecordingConfigurations; + synchronized (mLock) { + // Filter configurations of applications running on virtual device. + audioRecordingConfigurations = findRecordingConfigurations(configs, mRunningAppUids); + } + synchronized (mCallbackLock) { + if (mCallback != null) { + try { + mCallback.onRecordingConfigChanged(audioRecordingConfigurations); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when calling onRecordingConfigChanged", e); + } + } + } + } + + private void updatePlayingApplications(List<AudioPlaybackConfiguration> configs) { + synchronized (mLock) { + ArraySet<Integer> playingAppUids = findPlayingAppUids(configs, mRunningAppUids); + if (mPlayingAppUids.equals(playingAppUids)) { + return; + } + mPlayingAppUids = playingAppUids; + } + + // Updated rerouted apps, even if the app is already playing. It originally should be done + // when onRunningAppsChanged() is called, but we don't want to interrupt the audio + // streaming and cause the sound leak from phone when it's playing, so delay until here. + notifyAppsNeedingAudioRoutingChanged(); + } + + private void notifyAppsNeedingAudioRoutingChanged() { + if (mHandler.hasCallbacks(mUpdateAudioRoutingRunnable)) { + mHandler.removeCallbacks(mUpdateAudioRoutingRunnable); + } + + int[] runningUids; + synchronized (mLock) { + runningUids = new int[mRunningAppUids.size()]; + for (int i = 0; i < mRunningAppUids.size(); i++) { + runningUids[i] = mRunningAppUids.valueAt(i); + } + } + + synchronized (mCallbackLock) { + if (mCallback != null) { + try { + mCallback.onAppsNeedingAudioRoutingChanged(runningUids); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when calling updateReroutingApps", e); + } + } + } + } + + /** + * Finds uid of playing applications from the given running applications. + * + * @param configs a list of playback configs which get from {@link AudioManager} + */ + private static ArraySet<Integer> findPlayingAppUids(List<AudioPlaybackConfiguration> configs, + ArraySet<Integer> runningAppUids) { + ArraySet<Integer> playingAppUids = new ArraySet<>(); + for (AudioPlaybackConfiguration config : configs) { + if (runningAppUids.contains(config.getClientUid()) + && config.getPlayerState() == PLAYER_STATE_STARTED) { + playingAppUids.add(config.getClientUid()); + } + } + return playingAppUids; + } + + /** Finds a list of {@link AudioPlaybackConfiguration} for the given running applications. */ + private static List<AudioPlaybackConfiguration> findPlaybackConfigurations( + List<AudioPlaybackConfiguration> configs, + ArraySet<Integer> runningAppUids) { + List<AudioPlaybackConfiguration> runningConfigs = new ArrayList<>(); + for (AudioPlaybackConfiguration config : configs) { + if (runningAppUids.contains(config.getClientUid())) { + runningConfigs.add(config); + } + } + return runningConfigs; + } + + /** Finds a list of {@link AudioRecordingConfiguration} for the given running applications. */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + private static List<AudioRecordingConfiguration> findRecordingConfigurations( + List<AudioRecordingConfiguration> configs, ArraySet<Integer> runningAppUids) { + List<AudioRecordingConfiguration> runningConfigs = new ArrayList<>(); + for (AudioRecordingConfiguration config : configs) { + if (runningAppUids.contains(config.getClientUid())) { + runningConfigs.add(config); + } + } + return runningConfigs; + } + + @VisibleForTesting + boolean hasPendingRunnable() { + return mHandler.hasCallbacks(mUpdateAudioRoutingRunnable); + } + + @VisibleForTesting + void addPlayingAppsForTesting(int appUid) { + synchronized (mLock) { + mPlayingAppUids.add(appUid); + } + } +} diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index a8eeaf8d0ae2..e335a1677a8b 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -49,6 +49,7 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.AndroidPackageApi; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.SharedUserApi; import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.mutate.PackageStateMutator; @@ -1262,6 +1263,21 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP boolean migrateAppsData); /** + * Returns an array of PackageStateInternal that are all part of a shared user setting which is + * denoted by the app ID. Returns an empty set if the shared user setting doesn't exist or does + * not contain any package. + */ + @NonNull + public abstract ArraySet<PackageStateInternal> getSharedUserPackages(int sharedUserAppId); + + /** + * Returns the SharedUserApi denoted by the app ID of the shared user setting. Returns null if + * the corresponding shared user setting doesn't exist. + */ + @Nullable + public abstract SharedUserApi getSharedUserApi(int sharedUserAppId); + + /** * Initiates a package state mutation request, returning the current state as known by * PackageManager. This allows the later commit request to compare the initial values and * determine if any state was changed or any packages were updated since the whole request diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 91d2f55519a6..6c220f6f9503 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -45,6 +45,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.Executors; import java.util.stream.Collectors; /** @@ -369,10 +370,13 @@ public class BinaryTransparencyService extends SystemService { // we are only interested in doing things at PHASE_BOOT_COMPLETED if (phase == PHASE_BOOT_COMPLETED) { - // due to potentially long computation that holds up boot time, apex sha computations - // are deferred to first call Slog.i(TAG, "Boot completed. Getting VBMeta Digest."); getVBMetaDigestInformation(); + + // due to potentially long computation that may hold up boot time, SHA256 computations + // for APEXs and Modules will be executed via threads. + Slog.i(TAG, "Executing APEX & Module digest computations"); + computeApexAndModuleDigests(); } } @@ -382,6 +386,12 @@ public class BinaryTransparencyService extends SystemService { FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest); } + private void computeApexAndModuleDigests() { + // using Executors will allow the computations to be done asynchronously, thus not holding + // up boot time. + Executors.defaultThreadFactory().newThread(() -> updateBinaryMeasurements()).start(); + } + @NonNull private List<PackageInfo> getInstalledApexs() { List<PackageInfo> results = new ArrayList<PackageInfo>(); diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java index a0575cf6bab9..26d76a848a02 100644 --- a/services/core/java/com/android/server/BootReceiver.java +++ b/services/core/java/com/android/server/BootReceiver.java @@ -51,15 +51,12 @@ import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import java.io.BufferedReader; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.regex.Matcher; @@ -128,11 +125,9 @@ public class BootReceiver extends BroadcastReceiver { // Location of ftrace pipe for notifications from kernel memory tools like KFENCE and KASAN. private static final String ERROR_REPORT_TRACE_PIPE = "/sys/kernel/tracing/instances/bootreceiver/trace_pipe"; - // Stop after sending this many reports. See http://b/182159975. + // Stop after sending too many reports. See http://b/182159975. private static final int MAX_ERROR_REPORTS = 8; private static int sSentReports = 0; - // Avoid reporing the same bug from processDmesg() twice. - private static String sLastReportedBug = null; @Override public void onReceive(final Context context, Intent intent) { @@ -175,7 +170,8 @@ public class BootReceiver extends BroadcastReceiver { * We read from /sys/kernel/tracing/instances/bootreceiver/trace_pipe (set up by the * system), which will print an ftrace event when a memory corruption is detected in the * kernel. - * When an error is detected, we run the dmesg shell command and process its output. + * When an error is detected, we set the dmesg.start system property to notify dmesgd + * about a new error. */ OnFileDescriptorEventListener traceCallback = new OnFileDescriptorEventListener() { final int mBufferSize = 1024; @@ -191,8 +187,7 @@ public class BootReceiver extends BroadcastReceiver { * line, but to be on the safe side we keep reading until the buffer * contains a '\n' character. In the unlikely case of a very buggy kernel * the buffer may contain multiple tracing events that cannot be attributed - * to particular error reports. In that case the latest error report - * residing in dmesg is picked. + * to particular error reports. dmesgd will take care of all errors. */ try { int nbytes = Os.read(fd, mTraceBuffer, 0, mBufferSize); @@ -201,10 +196,13 @@ public class BootReceiver extends BroadcastReceiver { if (readStr.indexOf("\n") == -1) { return OnFileDescriptorEventListener.EVENT_INPUT; } - processDmesg(context); + if (sSentReports < MAX_ERROR_REPORTS) { + SystemProperties.set("dmesgd.start", "1"); + sSentReports++; + } } } catch (Exception e) { - Slog.wtf(TAG, "Error processing dmesg output", e); + Slog.wtf(TAG, "Error watching for trace events", e); return 0; // Unregister the handler. } return OnFileDescriptorEventListener.EVENT_INPUT; @@ -216,157 +214,6 @@ public class BootReceiver extends BroadcastReceiver { } - /** - * Check whether it is safe to collect this dmesg line or not. - * - * We only consider lines belonging to KASAN or KFENCE reports, but those may still contain - * user information, namely the process name: - * - * [ 69.547684] [ T6006]c7 6006 CPU: 7 PID: 6006 Comm: sh Tainted: G S C O ... - * - * hardware information: - * - * [ 69.558923] [ T6006]c7 6006 Hardware name: <REDACTED> - * - * or register dump (in KASAN reports only): - * - * ... RIP: 0033:0x7f96443109da - * ... RSP: 002b:00007ffcf0b51b08 EFLAGS: 00000202 ORIG_RAX: 00000000000000af - * ... RAX: ffffffffffffffda RBX: 000055dc3ee521a0 RCX: 00007f96443109da - * - * (on x86_64) - * - * ... pc : lpm_cpuidle_enter+0x258/0x384 - * ... lr : lpm_cpuidle_enter+0x1d4/0x384 - * ... sp : ffffff800820bea0 - * ... x29: ffffff800820bea0 x28: ffffffc2305f3ce0 - * ... ... - * ... x9 : 0000000000000001 x8 : 0000000000000000 - * (on ARM64) - * - * We therefore omit the lines that contain "Comm:", "Hardware name:", or match the general - * purpose register regexp. - * - * @param line single line of `dmesg` output. - * @return updated line with sensitive data removed, or null if the line must be skipped. - */ - public static String stripSensitiveData(String line) { - /* - * General purpose register names begin with "R" on x86_64 and "x" on ARM64. The letter is - * followed by two symbols (numbers, letters or spaces) and a colon, which is followed by a - * 16-digit hex number. The optional "_" prefix accounts for ORIG_RAX on x86. - */ - final String registerRegex = "[ _][Rx]..: [0-9a-f]{16}"; - final Pattern registerPattern = Pattern.compile(registerRegex); - - final String corruptionRegex = "Detected corrupted memory at 0x[0-9a-f]+"; - final Pattern corruptionPattern = Pattern.compile(corruptionRegex); - - if (line.contains("Comm: ") || line.contains("Hardware name: ")) return null; - if (registerPattern.matcher(line).find()) return null; - - Matcher cm = corruptionPattern.matcher(line); - if (cm.find()) return cm.group(0); - return line; - } - - /* - * Search dmesg output for the last error report from KFENCE or KASAN and copy it to Dropbox. - * - * Example report printed by the kernel (redacted to fit into 100 column limit): - * [ 69.236673] [ T6006]c7 6006 ========================================================= - * [ 69.245688] [ T6006]c7 6006 BUG: KFENCE: out-of-bounds in kfence_handle_page_fault - * [ 69.245688] [ T6006]c7 6006 - * [ 69.257816] [ T6006]c7 6006 Out-of-bounds access at 0xffffffca75c45000 (...) - * [ 69.267102] [ T6006]c7 6006 kfence_handle_page_fault+0x1bc/0x208 - * [ 69.273536] [ T6006]c7 6006 __do_kernel_fault+0xa8/0x11c - * ... - * [ 69.355427] [ T6006]c7 6006 kfence-#2 [0xffffffca75c46f30-0xffffffca75c46fff, ... - * [ 69.366938] [ T6006]c7 6006 __d_alloc+0x3c/0x1b4 - * [ 69.371946] [ T6006]c7 6006 d_alloc_parallel+0x48/0x538 - * [ 69.377578] [ T6006]c7 6006 __lookup_slow+0x60/0x15c - * ... - * [ 69.547684] [ T6006]c7 6006 CPU: 7 PID: 6006 Comm: sh Tainted: G S C O ... - * [ 69.558923] [ T6006]c7 6006 Hardware name: <REDACTED> - * [ 69.567059] [ T6006]c7 6006 ========================================================= - * - * We rely on the kernel printing task/CPU ID for every log line (CONFIG_PRINTK_CALLER=y). - * E.g. for the above report the task ID is T6006. Report lines may interleave with lines - * printed by other kernel tasks, which will have different task IDs, so in order to collect - * the report we: - * - find the next occurrence of the "BUG: " line in the kernel log, parse it to obtain the - * task ID and the tool name; - * - scan the rest of dmesg output and pick every line that has the same task ID, until we - * encounter a horizontal ruler, i.e.: - * [ 69.567059] [ T6006]c7 6006 ====================================================== - * - add that line to the error report, unless it contains sensitive information (see - * logLinePotentiallySensitive()) - * - repeat the above steps till the last report is found. - */ - private void processDmesg(Context ctx) throws IOException { - if (sSentReports == MAX_ERROR_REPORTS) return; - /* - * Only SYSTEM_KASAN_ERROR_REPORT and SYSTEM_KFENCE_ERROR_REPORT are supported at the - * moment. - */ - final String[] bugTypes = new String[] { "KASAN", "KFENCE" }; - final String tsRegex = "^\\[[^]]+\\] "; - final String bugRegex = - tsRegex + "\\[([^]]+)\\].*BUG: (" + String.join("|", bugTypes) + "):"; - final Pattern bugPattern = Pattern.compile(bugRegex); - - Process p = new ProcessBuilder("/system/bin/timeout", "-k", "90s", "60s", - "dmesg").redirectErrorStream(true).start(); - BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); - String line = null; - String task = null; - String tool = null; - String bugTitle = null; - Pattern reportPattern = null; - ArrayList<String> currentReport = null; - String lastReport = null; - - while ((line = reader.readLine()) != null) { - if (currentReport == null) { - Matcher bm = bugPattern.matcher(line); - if (!bm.find()) continue; - task = bm.group(1); - tool = bm.group(2); - bugTitle = line; - currentReport = new ArrayList<String>(); - currentReport.add(line); - String reportRegex = tsRegex + "\\[" + task + "\\].*"; - reportPattern = Pattern.compile(reportRegex); - continue; - } - Matcher rm = reportPattern.matcher(line); - if (!rm.matches()) continue; - if ((line = stripSensitiveData(line)) == null) continue; - if (line.contains("================================")) { - lastReport = String.join("\n", currentReport); - currentReport = null; - continue; - } - currentReport.add(line); - } - if (lastReport == null) { - Slog.w(TAG, "Could not find report in dmesg."); - return; - } - - // Avoid sending the same bug report twice. - if (bugTitle.equals(sLastReportedBug)) return; - - final String reportTag = "SYSTEM_" + tool + "_ERROR_REPORT"; - final DropBoxManager db = ctx.getSystemService(DropBoxManager.class); - final String headers = getCurrentBootHeaders(); - final String reportText = headers + lastReport; - - addTextToDropBox(db, reportTag, reportText, "/dev/kmsg", LOG_SIZE); - sLastReportedBug = bugTitle; - sSentReports++; - } - private void removeOldUpdatePackages(Context context) { Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS); } diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 39ac5effe6fa..b59cd4c0f212 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -21,6 +21,7 @@ import static android.Manifest.permission.NETWORK_SETTINGS; import static android.Manifest.permission.OBSERVE_NETWORK_POLICY; import static android.Manifest.permission.SHUTDOWN; import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; @@ -30,6 +31,7 @@ import static android.net.INetd.FIREWALL_DENYLIST; import static android.net.INetd.FIREWALL_RULE_ALLOW; import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; @@ -206,6 +208,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub { */ @GuardedBy("mRulesLock") private SparseIntArray mUidFirewallRestrictedRules = new SparseIntArray(); + /** + * Contains the per-UID firewall rules that are used when Low Power Standby is enabled. + */ + @GuardedBy("mRulesLock") + private SparseIntArray mUidFirewallLowPowerStandbyRules = new SparseIntArray(); /** Set of states for the child firewall chains. True if the chain is active. */ @GuardedBy("mRulesLock") final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray(); @@ -506,12 +513,14 @@ public class NetworkManagementService extends INetworkManagementService.Stub { syncFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, "dozable "); syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, "powersave "); syncFirewallChainLocked(FIREWALL_CHAIN_RESTRICTED, "restricted "); + syncFirewallChainLocked(FIREWALL_CHAIN_LOW_POWER_STANDBY, "low power standby "); final int[] chains = { FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE, - FIREWALL_CHAIN_RESTRICTED + FIREWALL_CHAIN_RESTRICTED, + FIREWALL_CHAIN_LOW_POWER_STANDBY }; for (int chain : chains) { @@ -1438,6 +1447,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { return FIREWALL_CHAIN_NAME_POWERSAVE; case FIREWALL_CHAIN_RESTRICTED: return FIREWALL_CHAIN_NAME_RESTRICTED; + case FIREWALL_CHAIN_LOW_POWER_STANDBY: + return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; default: throw new IllegalArgumentException("Bad child chain: " + chain); } @@ -1453,6 +1464,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { return FIREWALL_ALLOWLIST; case FIREWALL_CHAIN_RESTRICTED: return FIREWALL_ALLOWLIST; + case FIREWALL_CHAIN_LOW_POWER_STANDBY: + return FIREWALL_ALLOWLIST; default: return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST; } @@ -1571,6 +1584,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { return mUidFirewallPowerSaveRules; case FIREWALL_CHAIN_RESTRICTED: return mUidFirewallRestrictedRules; + case FIREWALL_CHAIN_LOW_POWER_STANDBY: + return mUidFirewallLowPowerStandbyRules; case FIREWALL_CHAIN_NONE: return mUidFirewallRules; default: @@ -1626,6 +1641,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub { pw.println(getFirewallChainState(FIREWALL_CHAIN_RESTRICTED)); dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_RESTRICTED, mUidFirewallRestrictedRules); + + pw.print("UID firewall low power standby chain enabled: "); + pw.println(getFirewallChainState(FIREWALL_CHAIN_LOW_POWER_STANDBY)); + dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY, + mUidFirewallLowPowerStandbyRules); } pw.print("Firewall enabled: "); pw.println(mFirewallEnabled); @@ -1749,6 +1769,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub { if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of restricted mode"); return true; } + if (getFirewallChainState(FIREWALL_CHAIN_LOW_POWER_STANDBY) + && mUidFirewallLowPowerStandbyRules.get(uid) != FIREWALL_RULE_ALLOW) { + if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of low power standby"); + return true; + } if (mUidRejectOnMetered.get(uid)) { if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data" + " in the background"); diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 178b66691f2f..c194527d54a5 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -134,6 +134,7 @@ import android.util.DataUnit; import android.util.Log; import android.util.Pair; import android.util.Slog; +import android.util.SparseArray; import android.util.TimeUtils; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; @@ -190,6 +191,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -471,6 +473,9 @@ class StorageManagerService extends IStorageManager.Stub @GuardedBy("mLock") private String mMoveTargetUuid; + @GuardedBy("mCloudMediaProviders") + private final SparseArray<String> mCloudMediaProviders = new SparseArray<>(); + private volatile int mMediaStoreAuthorityAppId = -1; private volatile int mDownloadsAuthorityAppId = -1; @@ -752,6 +757,7 @@ class StorageManagerService extends IStorageManager.Stub private static final int H_BOOT_COMPLETED = 13; private static final int H_COMPLETE_UNLOCK_USER = 14; private static final int H_VOLUME_STATE_CHANGED = 15; + private static final int H_CLOUD_MEDIA_PROVIDER_CHANGED = 16; class StorageManagerServiceHandler extends Handler { public StorageManagerServiceHandler(Looper looper) { @@ -872,13 +878,24 @@ class StorageManagerService extends IStorageManager.Stub break; } case H_COMPLETE_UNLOCK_USER: { - completeUnlockUser((int) msg.obj); + completeUnlockUser(msg.arg1); break; } case H_VOLUME_STATE_CHANGED: { final SomeArgs args = (SomeArgs) msg.obj; - onVolumeStateChangedAsync((VolumeInfo) args.arg1, (int) args.arg2, - (int) args.arg3); + onVolumeStateChangedAsync((VolumeInfo) args.arg1, args.argi1, args.argi2); + args.recycle(); + break; + } + case H_CLOUD_MEDIA_PROVIDER_CHANGED: { + final Object listener = msg.obj; + if (listener instanceof StorageManagerInternal.CloudProviderChangeListener) { + notifyCloudMediaProviderChangedAsync( + (StorageManagerInternal.CloudProviderChangeListener) listener); + } else { + onCloudMediaProviderChangedAsync(msg.arg1); + } + break; } } } @@ -1205,7 +1222,8 @@ class StorageManagerService extends IStorageManager.Stub Slog.w(TAG, "UNLOCK_USER lost from vold reset, will retry, user:" + userId); mVold.onUserStarted(userId); mStoraged.onUserStarted(userId); - mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId).sendToTarget(); + mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId, /* arg2 (unusued) */ 0) + .sendToTarget(); } } @@ -1263,7 +1281,8 @@ class StorageManagerService extends IStorageManager.Stub Slog.wtf(TAG, e); } - mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId).sendToTarget(); + mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId, /* arg2 (unusued) */ 0) + .sendToTarget(); if (mRemountCurrentUserVolumesOnUnlock && userId == mCurrentUserId) { maybeRemountVolumes(userId); mRemountCurrentUserVolumesOnUnlock = false; @@ -1493,18 +1512,17 @@ class StorageManagerService extends IStorageManager.Stub } @Override - public void onVolumeStateChanged(String volId, int state) { + public void onVolumeStateChanged(String volId, final int newState) { synchronized (mLock) { final VolumeInfo vol = mVolumes.get(volId); if (vol != null) { final int oldState = vol.state; - final int newState = state; vol.state = newState; final VolumeInfo vInfo = new VolumeInfo(vol); final SomeArgs args = SomeArgs.obtain(); args.arg1 = vInfo; - args.arg2 = oldState; - args.arg3 = newState; + args.argi1 = oldState; + args.argi2 = newState; mHandler.obtainMessage(H_VOLUME_STATE_CHANGED, args).sendToTarget(); onVolumeStateChangedLocked(vInfo, oldState, newState); } @@ -1696,7 +1714,6 @@ class StorageManagerService extends IStorageManager.Stub return true; } - private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) { if (vol.type == VolumeInfo.TYPE_EMULATED) { if (newState != VolumeInfo.STATE_MOUNTED) { @@ -1835,6 +1852,27 @@ class StorageManagerService extends IStorageManager.Stub } } + private void notifyCloudMediaProviderChangedAsync( + @NonNull StorageManagerInternal.CloudProviderChangeListener listener) { + synchronized (mCloudMediaProviders) { + for (int i = mCloudMediaProviders.size() - 1; i >= 0; --i) { + listener.onCloudProviderChanged( + mCloudMediaProviders.keyAt(i), mCloudMediaProviders.valueAt(i)); + } + } + } + + private void onCloudMediaProviderChangedAsync(int userId) { + final String authority; + synchronized (mCloudMediaProviders) { + authority = mCloudMediaProviders.get(userId); + } + for (StorageManagerInternal.CloudProviderChangeListener listener : + mStorageManagerInternal.mCloudProviderChangeListeners) { + listener.onCloudProviderChanged(userId, authority); + } + } + private void maybeLogMediaMount(VolumeInfo vol, int newState) { if (!SecurityLog.isLoggingEnabled()) { return; @@ -3772,7 +3810,7 @@ class StorageManagerService extends IStorageManager.Stub Binder.restoreCallingIdentity(token); } } - + @Override public void notifyAppIoBlocked(String volumeUuid, int uid, int tid, int reason) { enforceExternalStorageService(); @@ -3793,11 +3831,46 @@ class StorageManagerService extends IStorageManager.Stub return isAppIoBlocked(uid); } - private boolean isAppIoBlocked(int uid) { return mStorageSessionController.isAppIoBlocked(uid); } + @Override + public void setCloudMediaProvider(@Nullable String authority) { + enforceExternalStorageService(); + + final int userId = UserHandle.getUserId(Binder.getCallingUid()); + synchronized (mCloudMediaProviders) { + final String oldAuthority = mCloudMediaProviders.get(userId); + if (!Objects.equals(authority, oldAuthority)) { + mCloudMediaProviders.put(userId, authority); + mHandler.obtainMessage(H_CLOUD_MEDIA_PROVIDER_CHANGED, userId, 0, authority) + .sendToTarget(); + } + } + } + + @Override + @Nullable + public String getCloudMediaProvider() { + final int callingUid = Binder.getCallingUid(); + final int userId = UserHandle.getUserId(callingUid); + final String authority; + synchronized (mCloudMediaProviders) { + authority = mCloudMediaProviders.get(userId); + } + if (authority == null) { + return null; + } + final ProviderInfo pi = mPmInternal.resolveContentProvider( + authority, 0, userId, callingUid); + if (pi == null + || mPmInternal.filterAppAccess(pi.packageName, callingUid, userId)) { + return null; + } + return authority; + } + /** * Enforces that the caller is the {@link ExternalStorageService} * @@ -4674,7 +4747,7 @@ class StorageManagerService extends IStorageManager.Stub private int getMountModeInternal(int uid, String packageName) { try { // Get some easy cases out of the way first - if (Process.isIsolated(uid)) { + if (Process.isIsolated(uid) || Process.isSupplemental(uid)) { return StorageManager.MOUNT_MODE_EXTERNAL_NONE; } @@ -4950,6 +5023,12 @@ class StorageManagerService extends IStorageManager.Stub pw.decreaseIndent(); } + synchronized (mCloudMediaProviders) { + pw.println(); + pw.print("Media cloud providers: "); + pw.println(mCloudMediaProviders); + } + pw.println(); pw.print("Last maintenance: "); pw.println(TimeUtils.formatForLogging(mLastMaintenance)); @@ -4970,6 +5049,9 @@ class StorageManagerService extends IStorageManager.Stub private final List<StorageManagerInternal.ResetListener> mResetListeners = new ArrayList<>(); + private final CopyOnWriteArraySet<StorageManagerInternal.CloudProviderChangeListener> + mCloudProviderChangeListeners = new CopyOnWriteArraySet<>(); + @Override public boolean isFuseMounted(int userId) { synchronized (mLock) { @@ -5192,5 +5274,12 @@ class StorageManagerService extends IStorageManager.Stub return mCeStoragePreparedUsers.contains(userId); } } + + @Override + public void registerCloudProviderChangeListener( + @NonNull StorageManagerInternal.CloudProviderChangeListener listener) { + mCloudProviderChangeListeners.add(listener); + mHandler.obtainMessage(H_CLOUD_MEDIA_PROVIDER_CHANGED, listener); + } } } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index dc64d800c8c0..092172a15861 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2721,8 +2721,8 @@ public final class ActiveServices { int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service, String resolvedType, final IServiceConnection connection, int flags, - String instanceName, boolean isSupplementalProcessService, String callingPackage, - final int userId) + String instanceName, boolean isSupplementalProcessService, int supplementedAppUid, + String callingPackage, final int userId) throws TransactionTooLargeException { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service + " type=" + resolvedType + " conn=" + connection.asBinder() @@ -2807,8 +2807,8 @@ public final class ActiveServices { final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0; ServiceLookupResult res = retrieveServiceLocked(service, instanceName, - isSupplementalProcessService, resolvedType, callingPackage, callingPid, callingUid, - userId, true, callerFg, isBindExternal, allowInstant); + isSupplementalProcessService, supplementedAppUid, resolvedType, callingPackage, + callingPid, callingUid, userId, true, callerFg, isBindExternal, allowInstant); if (res == null) { return 0; } @@ -3228,13 +3228,14 @@ public final class ActiveServices { int callingPid, int callingUid, int userId, boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal, boolean allowInstant) { - return retrieveServiceLocked(service, instanceName, false, resolvedType, callingPackage, + return retrieveServiceLocked(service, instanceName, false, 0, resolvedType, callingPackage, callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal, allowInstant); } private ServiceLookupResult retrieveServiceLocked(Intent service, - String instanceName, boolean isSupplementalProcessService, String resolvedType, + String instanceName, boolean isSupplementalProcessService, int supplementedAppUid, + String resolvedType, String callingPackage, int callingPid, int callingUid, int userId, boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal, boolean allowInstant) { @@ -3415,7 +3416,7 @@ public final class ActiveServices { : null; r = new ServiceRecord(mAm, className, name, definingPackageName, definingUid, filter, sInfo, callingFromFg, res, - supplementalProcessName); + supplementalProcessName, supplementedAppUid); res.setService(r); smap.mServicesByInstanceName.put(name, r); smap.mServicesByIntent.put(filter, r); @@ -4189,8 +4190,16 @@ public final class ActiveServices { if (app == null && !permissionsReviewRequired && !packageFrozen) { // TODO (chriswailes): Change the Zygote policy flags based on if the launch-for-service // was initiated from a notification tap or not. - if ((app = mAm.startProcessLocked(procName, r.appInfo, true, intentFlags, - hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated)) == null) { + if (r.supplemental) { + final int uid = Process.toSupplementalUid(r.supplementedAppUid); + app = mAm.startSupplementalProcessLocked(procName, r.appInfo, true, intentFlags, + hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, uid); + r.isolationHostProc = app; + } else { + app = mAm.startProcessLocked(procName, r.appInfo, true, intentFlags, + hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated); + } + if (app == null) { String msg = "Unable to launch app " + r.appInfo.packageName + "/" + r.appInfo.uid + " for service " diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 940ad73eee98..e2e53cb5e4ec 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -20,6 +20,8 @@ import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGRO import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK; +import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY; +import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY; import android.annotation.NonNull; import android.app.ActivityThread; @@ -203,6 +205,10 @@ final class ActivityManagerConstants extends ContentObserver { private static final boolean DEFAULT_ENABLE_COMPONENT_ALIAS = false; private static final String DEFAULT_COMPONENT_ALIAS_OVERRIDES = ""; + private static final int DEFAULT_DEFER_BOOT_COMPLETED_BROADCAST = + DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY + | DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY; + // Flag stored in the DeviceConfig API. /** * Maximum number of cached processes. @@ -292,6 +298,9 @@ final class ActivityManagerConstants extends ContentObserver { */ private static final String KEY_PROCESS_KILL_TIMEOUT = "process_kill_timeout"; + private static final String KEY_DEFER_BOOT_COMPLETED_BROADCAST = + "defer_boot_completed_broadcast"; + // Maximum number of cached processes we will allow. public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; @@ -603,6 +612,15 @@ final class ActivityManagerConstants extends ContentObserver { volatile boolean mEnableComponentAlias = DEFAULT_ENABLE_COMPONENT_ALIAS; /** + * Where or not to defer LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts until the first + * time the process of the UID is started. + * Defined in {@link BroadcastConstants#DeferBootCompletedBroadcastType} + */ + @GuardedBy("mService") + volatile @BroadcastConstants.DeferBootCompletedBroadcastType int mDeferBootCompletedBroadcast = + DEFAULT_DEFER_BOOT_COMPLETED_BROADCAST; + + /** * Defines component aliases. Format * ComponentName ":" ComponentName ( "," ComponentName ":" ComponentName )* */ @@ -846,6 +864,9 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_PROCESS_KILL_TIMEOUT: updateProcessKillTimeout(); break; + case KEY_DEFER_BOOT_COMPLETED_BROADCAST: + updateDeferBootCompletedBroadcast(); + break; default: break; } @@ -1258,6 +1279,13 @@ final class ActivityManagerConstants extends ContentObserver { } } + private void updateDeferBootCompletedBroadcast() { + mDeferBootCompletedBroadcast = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_DEFER_BOOT_COMPLETED_BROADCAST, + DEFAULT_DEFER_BOOT_COMPLETED_BROADCAST); + } + private long[] parseLongArray(@NonNull String key, @NonNull long[] def) { final String val = DeviceConfig.getString(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, key, null); @@ -1529,6 +1557,8 @@ final class ActivityManagerConstants extends ContentObserver { pw.print("="); pw.println(mEnableComponentAlias); pw.print(" "); pw.print(KEY_COMPONENT_ALIAS_OVERRIDES); pw.print("="); pw.println(mComponentAliasOverrides); + pw.print(" "); pw.print(KEY_DEFER_BOOT_COMPLETED_BROADCAST); + pw.print("="); pw.println(mDeferBootCompletedBroadcast); pw.println(); if (mOverrideMaxCachedProcesses >= 0) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 7f64c0029419..2371101d7c9f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -458,7 +458,7 @@ public class ActivityManagerService extends IActivityManager.Stub * broadcasts */ private static final boolean ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT = - SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", true); + SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", false); static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM; static final String TAG_BACKUP = TAG + POSTFIX_BACKUP; @@ -1892,6 +1892,8 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessRecord app = mProcessList.newProcessRecordLocked(info, info.processName, false, 0, + false, + 0, new HostingRecord("system")); app.setPersistent(true); app.setPid(MY_PID); @@ -2780,18 +2782,32 @@ public class ActivityManagerService extends IActivityManager.Stub false /* knownToBeDead */, 0 /* intentFlags */, sNullHostingRecord /* hostingRecord */, ZYGOTE_POLICY_FLAG_EMPTY, true /* allowWhileBooting */, true /* isolated */, - uid, abiOverride, entryPoint, entryPointArgs, crashHandler); + uid, false /* supplemental */, 0 /* supplementalUid */, + abiOverride, entryPoint, entryPointArgs, crashHandler); return proc != null; } } @GuardedBy("this") + final ProcessRecord startSupplementalProcessLocked(String processName, + ApplicationInfo info, boolean knownToBeDead, int intentFlags, + HostingRecord hostingRecord, int zygotePolicyFlags, int supplementalUid) { + return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags, + hostingRecord, zygotePolicyFlags, false /* allowWhileBooting */, + false /* isolated */, 0 /* isolatedUid */, + true /* supplemental */, supplementalUid, + null /* ABI override */, null /* entryPoint */, + null /* entryPointArgs */, null /* crashHandler */); + } + + @GuardedBy("this") final ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord, int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated) { return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags, hostingRecord, zygotePolicyFlags, allowWhileBooting, isolated, 0 /* isolatedUid */, + false /* supplemental */, 0 /* supplementalUid */, null /* ABI override */, null /* entryPoint */, null /* entryPointArgs */, null /* crashHandler */); } @@ -4840,6 +4856,10 @@ public class ActivityManagerService extends IActivityManager.Stub } } + if (!badApp) { + updateUidReadyForBootCompletedBroadcastLocked(app.uid); + } + // Check if a next-broadcast receiver is in this process... if (!badApp && isPendingBroadcastProcessLocked(pid)) { try { @@ -6466,7 +6486,8 @@ public class ActivityManagerService extends IActivityManager.Stub return isBackgroundRestrictedNoCheck(callingUid, packageName); } - boolean isBackgroundRestrictedNoCheck(final int uid, final String packageName) { + @VisibleForTesting + public boolean isBackgroundRestrictedNoCheck(final int uid, final String packageName) { final int mode = getAppOpsManager().checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName); return mode != AppOpsManager.MODE_ALLOWED; @@ -6521,6 +6542,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (app == null) { app = mProcessList.newProcessRecordLocked(info, customProcess, isolated, 0, + false, 0, new HostingRecord("added application", customProcess != null ? customProcess : info.processName)); updateLruProcessLocked(app, false, null); @@ -12346,12 +12368,13 @@ public class ActivityManagerService extends IActivityManager.Stub String resolvedType, IServiceConnection connection, int flags, String instanceName, String callingPackage, int userId) throws TransactionTooLargeException { return bindServiceInstance(caller, token, service, resolvedType, connection, flags, - instanceName, false, callingPackage, userId); + instanceName, false, 0, callingPackage, userId); } private int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, String instanceName, - boolean isSupplementalProcessService, String callingPackage, int userId) + boolean isSupplementalProcessService, int supplementedAppUid, String callingPackage, + int userId) throws TransactionTooLargeException { enforceNotIsolatedCaller("bindService"); @@ -12382,7 +12405,8 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized(this) { return mServices.bindServiceLocked(caller, token, service, resolvedType, connection, - flags, instanceName, isSupplementalProcessService, callingPackage, userId); + flags, instanceName, isSupplementalProcessService, supplementedAppUid, + callingPackage, userId); } } @@ -12745,6 +12769,13 @@ public class ActivityManagerService extends IActivityManager.Stub return didSomething; } + void updateUidReadyForBootCompletedBroadcastLocked(int uid) { + for (BroadcastQueue queue : mBroadcastQueues) { + queue.updateUidReadyForBootCompletedBroadcastLocked(uid); + queue.scheduleBroadcastsLocked(); + } + } + /** * @deprecated Use {@link #registerReceiverWithFeature} */ @@ -13370,10 +13401,8 @@ public class ActivityManagerService extends IActivityManager.Stub } } - // TODO (206518114): We need to use the "real" package name which sent the broadcast, - // in case the broadcast is sent via PendingIntent. if (brOptions.getIdForResponseEvent() > 0) { - enforceUsageStatsPermission(callerPackage, realCallingUid, realCallingPid, + enforceUsageStatsPermission(callerPackage, callingUid, callingPid, "recordResponseEventWhileInBackground()"); } } @@ -15976,8 +16005,8 @@ public class ActivityManagerService extends IActivityManager.Stub return ActivityManagerService.this.bindServiceInstance( mContext.getIApplicationThread(), mContext.getActivityToken(), service, service.resolveTypeIfNeeded(mContext.getContentResolver()), sd, flags, - processName, /*isSupplementalProcessService*/ true, mContext.getOpPackageName(), - UserHandle.getUserId(userAppUid)) != 0; + processName, /*isSupplementalProcessService*/ true, userAppUid, + mContext.getOpPackageName(), UserHandle.getUserId(userAppUid)) != 0; } @Override @@ -16121,6 +16150,23 @@ public class ActivityManagerService extends IActivityManager.Stub } /** + * Returns package name by pid. + */ + @Override + @Nullable + public String getPackageNameByPid(int pid) { + synchronized (mPidsSelfLocked) { + final ProcessRecord app = mPidsSelfLocked.get(pid); + + if (app != null && app.info != null) { + return app.info.packageName; + } + + return null; + } + } + + /** * Sets if the given pid has an overlay UI or not. * * @param pid The pid we are setting overlay UI for. diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 97727b0875a3..08508b28b9a5 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -31,6 +31,7 @@ import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_MOD import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_COUNT; import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING; import android.app.ActivityManager; @@ -3256,12 +3257,12 @@ final class ActivityManagerShellCommand extends ShellCommand { return -1; } if (arg == null) { - batteryTracker.mDebugUidPercentages.clear(); + batteryTracker.clearDebugUidPercentage(); return 0; } String[] pairs = arg.split(","); int[] uids = new int[pairs.length]; - double[] values = new double[pairs.length]; + double[][] values = new double[pairs.length][]; try { for (int i = 0; i < pairs.length; i++) { String[] pair = pairs[i].split("="); @@ -3270,16 +3271,21 @@ final class ActivityManagerShellCommand extends ShellCommand { return -1; } uids[i] = Integer.parseInt(pair[0]); - values[i] = Double.parseDouble(pair[1]); + final String[] vals = pair[1].split(":"); + if (vals.length != BATTERY_USAGE_COUNT) { + getErrPrintWriter().println("Malformed input"); + return -1; + } + values[i] = new double[vals.length]; + for (int j = 0; j < vals.length; j++) { + values[i][j] = Double.parseDouble(vals[j]); + } } } catch (NumberFormatException e) { getErrPrintWriter().println("Malformed input"); return -1; } - batteryTracker.mDebugUidPercentages.clear(); - for (int i = 0; i < pairs.length; i++) { - batteryTracker.mDebugUidPercentages.put(uids[i], values[i]); - } + batteryTracker.setDebugUidPercentage(uids, values); return 0; } diff --git a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java index 75de3a167a5f..d1cf0049d146 100644 --- a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java +++ b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java @@ -18,8 +18,9 @@ package com.android.server.am; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.AppBatteryTracker.BATTERY_USAGE_NONE; import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX; -import static com.android.server.am.BaseAppStateDurationsTracker.EVENT_NUM; +import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_NUM; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,13 +29,16 @@ import android.os.SystemClock; import android.util.Pair; import android.util.Slog; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.server.am.AppBatteryExemptionTracker.AppBatteryExemptionPolicy; import com.android.server.am.AppBatteryExemptionTracker.UidBatteryStates; import com.android.server.am.AppBatteryTracker.AppBatteryPolicy; -import com.android.server.am.BaseAppStateDurationsTracker.EventListener; +import com.android.server.am.AppBatteryTracker.BatteryUsage; +import com.android.server.am.AppBatteryTracker.ImmutableBatteryUsage; import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent; import com.android.server.am.BaseAppStateTracker.Injector; +import com.android.server.am.BaseAppStateTracker.StateListener; import java.io.PrintWriter; import java.lang.reflect.Constructor; @@ -53,13 +57,13 @@ import java.util.LinkedList; */ final class AppBatteryExemptionTracker extends BaseAppStateDurationsTracker<AppBatteryExemptionPolicy, UidBatteryStates> - implements BaseAppStateEvents.Factory<UidBatteryStates>, EventListener { + implements BaseAppStateEvents.Factory<UidBatteryStates>, StateListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "AppBatteryExemptionTracker" : TAG_AM; private static final boolean DEBUG_BACKGROUND_BATTERY_EXEMPTION_TRACKER = false; // As it's a UID-based tracker, anywhere which requires a package name, use this default name. - private static final String DEFAULT_NAME = ""; + static final String DEFAULT_NAME = ""; AppBatteryExemptionTracker(Context context, AppRestrictionController controller) { this(context, controller, null, null); @@ -76,9 +80,7 @@ final class AppBatteryExemptionTracker void onSystemReady() { super.onSystemReady(); mAppRestrictionController.forEachTracker(tracker -> { - if (tracker instanceof BaseAppStateDurationsTracker) { - ((BaseAppStateDurationsTracker) tracker).registerEventListener(this); - } + tracker.registerStateListener(this); }); } @@ -93,18 +95,20 @@ final class AppBatteryExemptionTracker } @Override - public void onNewEvent(int uid, String packageName, boolean start, long now, int eventType) { + public void onStateChange(int uid, String packageName, boolean start, long now, int stateType) { if (!mInjector.getPolicy().isEnabled()) { return; } - final double batteryUsage = mAppRestrictionController.getUidBatteryUsage(uid); + final ImmutableBatteryUsage batteryUsage = mAppRestrictionController + .getUidBatteryUsage(uid); + final int stateTypeIndex = stateTypeToIndex(stateType); synchronized (mLock) { UidBatteryStates pkg = mPkgEvents.get(uid, DEFAULT_NAME); if (pkg == null) { pkg = createAppStateEvents(uid, DEFAULT_NAME); mPkgEvents.put(uid, DEFAULT_NAME, pkg); } - pkg.addEvent(start, now, batteryUsage, eventType); + pkg.addEvent(start, now, batteryUsage, stateTypeIndex); } } @@ -120,22 +124,24 @@ final class AppBatteryExemptionTracker * @return The to-be-exempted battery usage of the given UID in the given duration; it could * be considered as "exempted" due to various use cases, i.e. media playback. */ - double getUidBatteryExemptedUsageSince(int uid, long since, long now) { + ImmutableBatteryUsage getUidBatteryExemptedUsageSince(int uid, long since, long now, + int types) { if (!mInjector.getPolicy().isEnabled()) { - return 0.0d; + return BATTERY_USAGE_NONE; } - Pair<Double, Double> result; + Pair<ImmutableBatteryUsage, ImmutableBatteryUsage> result; synchronized (mLock) { final UidBatteryStates pkg = mPkgEvents.get(uid, DEFAULT_NAME); if (pkg == null) { - return 0.0d; + return BATTERY_USAGE_NONE; } - result = pkg.getBatteryUsageSince(since, now); + result = pkg.getBatteryUsageSince(since, now, types); } - if (result.second > 0.0d) { + if (!result.second.isEmpty()) { // We have an open event (just start, no stop), get the battery usage till now. - final double batteryUsage = mAppRestrictionController.getUidBatteryUsage(uid); - return result.first + batteryUsage - result.second; + final ImmutableBatteryUsage batteryUsage = mAppRestrictionController + .getUidBatteryUsage(uid); + return result.first.mutate().add(batteryUsage).subtract(result.second).unmutate(); } return result.first; } @@ -143,7 +149,7 @@ final class AppBatteryExemptionTracker static final class UidBatteryStates extends BaseAppStateDurations<UidStateEventWithBattery> { UidBatteryStates(int uid, @NonNull String tag, @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) { - super(uid, DEFAULT_NAME, EVENT_NUM, tag, maxTrackingDurationConfig); + super(uid, DEFAULT_NAME, STATE_TYPE_NUM, tag, maxTrackingDurationConfig); } UidBatteryStates(@NonNull UidBatteryStates other) { @@ -154,9 +160,9 @@ final class AppBatteryExemptionTracker * @param start {@code true} if it's a start event. * @param now The timestamp when this event occurred. * @param batteryUsage The background current drain since the system boots. - * @param eventType One of EVENT_TYPE_* defined in the class BaseAppStateDurationsTracker. + * @param eventType One of STATE_TYPE_INDEX_* defined in the class BaseAppStateTracker. */ - void addEvent(boolean start, long now, double batteryUsage, int eventType) { + void addEvent(boolean start, long now, ImmutableBatteryUsage batteryUsage, int eventType) { if (start) { addEvent(start, new UidStateEventWithBattery(start, now, batteryUsage, null), eventType); @@ -169,7 +175,8 @@ final class AppBatteryExemptionTracker return; } addEvent(start, new UidStateEventWithBattery(start, now, - batteryUsage - last.getBatteryUsage(), last), eventType); + batteryUsage.mutate().subtract(last.getBatteryUsage()).unmutate(), last), + eventType); } } @@ -177,43 +184,40 @@ final class AppBatteryExemptionTracker return mEvents[eventType] != null ? mEvents[eventType].peekLast() : null; } - /** - * @return The pair of bg battery usage of given duration; the first value in the pair - * is the aggregated battery usage of all event pairs in this duration; while - * the second value is the battery usage since the system boots, if there is - * an open event(just start, no stop) at the end of the duration. - */ - Pair<Double, Double> getBatteryUsageSince(long since, long now, int eventType) { - return getBatteryUsageSince(since, now, mEvents[eventType]); - } - - private Pair<Double, Double> getBatteryUsageSince(long since, long now, - LinkedList<UidStateEventWithBattery> events) { + private Pair<ImmutableBatteryUsage, ImmutableBatteryUsage> getBatteryUsageSince(long since, + long now, LinkedList<UidStateEventWithBattery> events) { if (events == null || events.size() == 0) { - return Pair.create(0.0d, 0.0d); + return Pair.create(BATTERY_USAGE_NONE, BATTERY_USAGE_NONE); } - double batteryUsage = 0.0d; + final BatteryUsage batteryUsage = new BatteryUsage(); UidStateEventWithBattery lastEvent = null; for (UidStateEventWithBattery event : events) { lastEvent = event; if (event.getTimestamp() < since || event.isStart()) { continue; } - batteryUsage += event.getBatteryUsage(since, Math.min(now, event.getTimestamp())); + batteryUsage.add(event.getBatteryUsage(since, Math.min(now, event.getTimestamp()))); if (now <= event.getTimestamp()) { break; } } - return Pair.create(batteryUsage, lastEvent.isStart() ? lastEvent.getBatteryUsage() : 0); + return Pair.create(batteryUsage.unmutate(), lastEvent.isStart() + ? lastEvent.getBatteryUsage() : BATTERY_USAGE_NONE); } /** - * @return The aggregated battery usage amongst all the event types we're tracking. + * @return The pair of bg battery usage of given duration; the first value in the pair + * is the aggregated battery usage of selected events in this duration; while + * the second value is the battery usage since the system boots, if there is + * an open event(just start, no stop) at the end of the duration. */ - Pair<Double, Double> getBatteryUsageSince(long since, long now) { + Pair<ImmutableBatteryUsage, ImmutableBatteryUsage> getBatteryUsageSince(long since, + long now, int types) { LinkedList<UidStateEventWithBattery> result = new LinkedList<>(); for (int i = 0; i < mEvents.length; i++) { - result = add(result, mEvents[i]); + if ((types & stateIndexToType(i)) != 0) { + result = add(result, mEvents[i]); + } } return getBatteryUsageSince(since, now, result); } @@ -236,7 +240,7 @@ final class AppBatteryExemptionTracker UidStateEventWithBattery l = itl.next(), r = itr.next(); LinkedList<UidStateEventWithBattery> dest = new LinkedList<>(); boolean actl = false, actr = false, overlapping = false; - double batteryUsage = 0.0d; + final BatteryUsage batteryUsage = new BatteryUsage(); long recentActTs = 0, overlappingDuration = 0; for (long lts = l.getTimestamp(), rts = r.getTimestamp(); lts != Long.MAX_VALUE || rts != Long.MAX_VALUE;) { @@ -245,8 +249,8 @@ final class AppBatteryExemptionTracker if (lts == rts) { earliest = l; // we'll deal with the double counting problem later. - batteryUsage += actl ? l.getBatteryUsage() : 0.0d; - batteryUsage += actr ? r.getBatteryUsage() : 0.0d; + if (actl) batteryUsage.add(l.getBatteryUsage()); + if (actr) batteryUsage.add(r.getBatteryUsage()); overlappingDuration += overlapping && (actl || actr) ? (lts - recentActTs) : 0; actl = !actl; @@ -255,13 +259,13 @@ final class AppBatteryExemptionTracker rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE; } else if (lts < rts) { earliest = l; - batteryUsage += actl ? l.getBatteryUsage() : 0.0d; + if (actl) batteryUsage.add(l.getBatteryUsage()); overlappingDuration += overlapping && actl ? (lts - recentActTs) : 0; actl = !actl; lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE; } else { earliest = r; - batteryUsage += actr ? r.getBatteryUsage() : 0.0d; + if (actr) batteryUsage.add(r.getBatteryUsage()); overlappingDuration += overlapping && actr ? (rts - recentActTs) : 0; actr = !actr; rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE; @@ -281,12 +285,12 @@ final class AppBatteryExemptionTracker final long durationWithOverlapping = duration + overlappingDuration; // Get the proportional batteryUsage. if (durationWithOverlapping != 0) { - batteryUsage *= duration * 1.0d / durationWithOverlapping; + batteryUsage.scale(duration * 1.0d / durationWithOverlapping); + event.update(lastEvent, new ImmutableBatteryUsage(batteryUsage)); } else { - batteryUsage = 0.0d; + event.update(lastEvent, BATTERY_USAGE_NONE); } - event.update(lastEvent, batteryUsage); - batteryUsage = 0.0d; + batteryUsage.setTo(BATTERY_USAGE_NONE); overlappingDuration = 0; } dest.add(event); @@ -322,14 +326,15 @@ final class AppBatteryExemptionTracker * the system boots if the {@link #mIsStart} is true, but will be the delta of the bg * battery usage since the start event if the {@link #mIsStart} is false. */ - private double mBatteryUsage; + private @NonNull ImmutableBatteryUsage mBatteryUsage; /** * The peer event of this pair (a pair of start/stop events). */ private @Nullable UidStateEventWithBattery mPeer; - UidStateEventWithBattery(boolean isStart, long now, double batteryUsage, + UidStateEventWithBattery(boolean isStart, long now, + @NonNull ImmutableBatteryUsage batteryUsage, @Nullable UidStateEventWithBattery peer) { super(now); mIsStart = isStart; @@ -355,15 +360,19 @@ final class AppBatteryExemptionTracker } if (mPeer != null) { // Reduce the bg battery usage proportionally. - final double batteryUsage = mPeer.getBatteryUsage(); + final ImmutableBatteryUsage batteryUsage = mPeer.getBatteryUsage(); mPeer.mBatteryUsage = mPeer.getBatteryUsage(timestamp, mPeer.mTimestamp); // Update the battery data of the start event too. - mBatteryUsage += batteryUsage - mPeer.mBatteryUsage; + mBatteryUsage = mBatteryUsage.mutate() + .add(batteryUsage) + .subtract(mPeer.mBatteryUsage) + .unmutate(); } mTimestamp = timestamp; } - void update(@NonNull UidStateEventWithBattery peer, double batteryUsage) { + void update(@NonNull UidStateEventWithBattery peer, + @NonNull ImmutableBatteryUsage batteryUsage) { mPeer = peer; peer.mPeer = this; mBatteryUsage = batteryUsage; @@ -373,18 +382,19 @@ final class AppBatteryExemptionTracker return mIsStart; } - double getBatteryUsage(long start, long end) { + @NonNull ImmutableBatteryUsage getBatteryUsage(long start, long end) { if (mIsStart || start >= mTimestamp || end <= start) { - return 0.0d; + return BATTERY_USAGE_NONE; } start = Math.max(start, mPeer.mTimestamp); end = Math.min(end, mTimestamp); final long totalDur = mTimestamp - mPeer.mTimestamp; final long inputDur = end - start; - return totalDur != 0 ? mBatteryUsage * (1.0d * inputDur) / totalDur : 0.0d; + return totalDur != 0 ? (totalDur == inputDur ? mBatteryUsage : mBatteryUsage.mutate() + .scale((1.0d * inputDur) / totalDur).unmutate()) : BATTERY_USAGE_NONE; } - double getBatteryUsage() { + @NonNull ImmutableBatteryUsage getBatteryUsage() { return mBatteryUsage; } @@ -404,14 +414,20 @@ final class AppBatteryExemptionTracker final UidStateEventWithBattery otherEvent = (UidStateEventWithBattery) other; return otherEvent.mIsStart == mIsStart && otherEvent.mTimestamp == mTimestamp - && Double.compare(otherEvent.mBatteryUsage, mBatteryUsage) == 0; + && mBatteryUsage.equals(otherEvent.mBatteryUsage); + } + + @Override + public String toString() { + return "UidStateEventWithBattery(" + mIsStart + ", " + mTimestamp + + ", " + mBatteryUsage + ")"; } @Override public int hashCode() { return (Boolean.hashCode(mIsStart) * 31 + Long.hashCode(mTimestamp)) * 31 - + Double.hashCode(mBatteryUsage); + + mBatteryUsage.hashCode(); } } @@ -433,7 +449,8 @@ final class AppBatteryExemptionTracker super(injector, tracker, KEY_BG_BATTERY_EXEMPTION_ENABLED, DEFAULT_BG_BATTERY_EXEMPTION_ENABLED, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW, - AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS); + tracker.mContext.getResources() + .getInteger(R.integer.config_bg_current_drain_window)); } @Override diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java index b8f5c50b2e1b..655e30982091 100644 --- a/services/core/java/com/android/server/am/AppBatteryTracker.java +++ b/services/core/java/com/android/server/am/AppBatteryTracker.java @@ -28,23 +28,28 @@ import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACT import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.BatteryConsumer.POWER_COMPONENT_ANY; import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND; +import static android.os.BatteryConsumer.PROCESS_STATE_COUNT; import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND; import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE; +import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED; import static android.os.PowerExemptionManager.REASON_DENIED; import static android.util.TimeUtils.formatTime; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX; -import static com.android.server.am.BaseAppStateTracker.ONE_DAY; import static com.android.server.am.BaseAppStateTracker.ONE_MINUTE; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager.RestrictionLevel; import android.content.Context; import android.content.pm.ServiceInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; import android.os.BatteryConsumer; +import android.os.BatteryConsumer.Dimensions; import android.os.BatteryStatsInternal; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; @@ -58,9 +63,9 @@ import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; -import android.util.SparseDoubleArray; import android.util.TimeUtils; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; @@ -98,12 +103,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> static final long BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_LONG = 5 * ONE_MINUTE; // 5 mins static final long BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_DEBUG = 2_000L; // 2s - static final BatteryConsumer.Dimensions BATT_DIMEN_FG = - new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_FOREGROUND); - static final BatteryConsumer.Dimensions BATT_DIMEN_BG = - new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_BACKGROUND); - static final BatteryConsumer.Dimensions BATT_DIMEN_FGS = - new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_FOREGROUND_SERVICE); + static final ImmutableBatteryUsage BATTERY_USAGE_NONE = new ImmutableBatteryUsage(); private final Runnable mBgBatteryUsageStatsPolling = this::updateBatteryUsageStatsAndCheck; private final Runnable mBgBatteryUsageStatsCheck = this::checkBatteryUsageStats; @@ -132,29 +132,30 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> * the last battery stats reset prior to that (whoever is earlier). */ @GuardedBy("mLock") - private final SparseDoubleArray mUidBatteryUsage = new SparseDoubleArray(); + private final SparseArray<BatteryUsage> mUidBatteryUsage = new SparseArray<>(); /** * The battery usage for each UID, in the rolling window of the past. */ @GuardedBy("mLock") - private final SparseDoubleArray mUidBatteryUsageInWindow = new SparseDoubleArray(); + private final SparseArray<ImmutableBatteryUsage> mUidBatteryUsageInWindow = new SparseArray<>(); /** * The uid battery usage stats data from our last query, it consists of the data since * last battery stats reset. */ @GuardedBy("mLock") - private final SparseDoubleArray mLastUidBatteryUsage = new SparseDoubleArray(); + private final SparseArray<ImmutableBatteryUsage> mLastUidBatteryUsage = new SparseArray<>(); // No lock is needed. - private final SparseDoubleArray mTmpUidBatteryUsage = new SparseDoubleArray(); + private final SparseArray<BatteryUsage> mTmpUidBatteryUsage = new SparseArray<>(); // No lock is needed. - private final SparseDoubleArray mTmpUidBatteryUsage2 = new SparseDoubleArray(); + private final SparseArray<ImmutableBatteryUsage> mTmpUidBatteryUsage2 = new SparseArray<>(); // No lock is needed. - private final SparseDoubleArray mTmpUidBatteryUsageInWindow = new SparseDoubleArray(); + private final SparseArray<ImmutableBatteryUsage> mTmpUidBatteryUsageInWindow = + new SparseArray<>(); // No lock is needed. private final ArraySet<UserHandle> mTmpUserIds = new ArraySet<>(); @@ -166,7 +167,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> private long mLastUidBatteryUsageStartTs; // For debug only. - final SparseDoubleArray mDebugUidPercentages = new SparseDoubleArray(); + private final SparseArray<BatteryUsage> mDebugUidPercentages = new SparseArray<>(); AppBatteryTracker(Context context, AppRestrictionController controller) { this(context, controller, null, null); @@ -277,18 +278,24 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> * </p> */ @Override - public double getUidBatteryUsage(int uid) { + @NonNull + public ImmutableBatteryUsage getUidBatteryUsage(int uid) { final long now = mInjector.currentTimeMillis(); final boolean updated = updateBatteryUsageStatsIfNecessary(now, false); synchronized (mLock) { if (updated) { // We just got fresh data, schedule a check right a way. mBgHandler.removeCallbacks(mBgBatteryUsageStatsPolling); - if (!mBgHandler.hasCallbacks(mBgBatteryUsageStatsCheck)) { - mBgHandler.post(mBgBatteryUsageStatsCheck); - } + scheduleBgBatteryUsageStatsCheck(); } - return mUidBatteryUsage.get(uid, 0.0d); + final BatteryUsage usage = mUidBatteryUsage.get(uid); + return usage != null ? new ImmutableBatteryUsage(usage) : BATTERY_USAGE_NONE; + } + } + + private void scheduleBgBatteryUsageStatsCheck() { + if (!mBgHandler.hasCallbacks(mBgBatteryUsageStatsCheck)) { + mBgHandler.post(mBgBatteryUsageStatsCheck); } } @@ -309,27 +316,33 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> final long now = SystemClock.elapsedRealtime(); final AppBatteryPolicy bgPolicy = mInjector.getPolicy(); try { - final SparseDoubleArray uidConsumers = mTmpUidBatteryUsageInWindow; + final SparseArray<ImmutableBatteryUsage> uidConsumers = mTmpUidBatteryUsageInWindow; synchronized (mLock) { copyUidBatteryUsage(mUidBatteryUsageInWindow, uidConsumers); } final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs); for (int i = 0, size = uidConsumers.size(); i < size; i++) { final int uid = uidConsumers.keyAt(i); - final double actualUsage = uidConsumers.valueAt(i); - final double exemptedUsage = mAppRestrictionController - .getUidBatteryExemptedUsageSince(uid, since, now); + final ImmutableBatteryUsage actualUsage = uidConsumers.valueAt(i); + final ImmutableBatteryUsage exemptedUsage = mAppRestrictionController + .getUidBatteryExemptedUsageSince(uid, since, now, + bgPolicy.mBgCurrentDrainExemptedTypes); // It's possible the exemptedUsage could be larger than actualUsage, // as the former one is an approximate value. - final double bgUsage = Math.max(0.0d, actualUsage - exemptedUsage); - final double percentage = bgPolicy.getPercentage(uid, bgUsage); + final BatteryUsage bgUsage = actualUsage.mutate() + .subtract(exemptedUsage) + .calcPercentage(uid, bgPolicy); if (DEBUG_BACKGROUND_BATTERY_TRACKER) { Slog.i(TAG, String.format( - "UID %d: %.3f mAh (or %4.2f%%) %.3f %.3f over the past %s", - uid, bgUsage, percentage, exemptedUsage, actualUsage, + "UID %d: %s (%s) | %s | %s over the past %s", + uid, + bgUsage.toString(), + bgUsage.percentageToString(), + exemptedUsage.toString(), + actualUsage.toString(), TimeUtils.formatDuration(bgPolicy.mBgCurrentDrainWindowMs))); } - bgPolicy.handleUidBatteryUsage(uid, percentage); + bgPolicy.handleUidBatteryUsage(uid, bgUsage); } // For debugging only. for (int i = 0, size = mDebugUidPercentages.size(); i < size; i++) { @@ -384,7 +397,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> private void updateBatteryUsageStatsOnce(long now) { final AppBatteryPolicy bgPolicy = mInjector.getPolicy(); final ArraySet<UserHandle> userIds = mTmpUserIds; - final SparseDoubleArray buf = mTmpUidBatteryUsage; + final SparseArray<BatteryUsage> buf = mTmpUidBatteryUsage; final BatteryStatsInternal batteryStatsInternal = mInjector.getBatteryStatsInternal(); final long windowSize = bgPolicy.mBgCurrentDrainWindowMs; @@ -453,26 +466,26 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> for (int i = 0, size = buf.size(); i < size; i++) { final int uid = buf.keyAt(i); final int index = mUidBatteryUsage.indexOfKey(uid); - final double lastUsage = mLastUidBatteryUsage.get(uid, 0.0d); - final double curUsage = buf.valueAt(i); - final double before; + final BatteryUsage lastUsage = mLastUidBatteryUsage.get(uid, BATTERY_USAGE_NONE); + final BatteryUsage curUsage = buf.valueAt(i); + final BatteryUsage before; if (index >= 0) { before = mUidBatteryUsage.valueAt(index); - mUidBatteryUsage.setValueAt(index, before - lastUsage + curUsage); + before.subtract(lastUsage).add(curUsage); } else { - before = 0.0d; + before = BATTERY_USAGE_NONE; mUidBatteryUsage.put(uid, curUsage); } if (DEBUG_BACKGROUND_BATTERY_TRACKER) { - final double actualDelta = curUsage - lastUsage; + final BatteryUsage actualDelta = new BatteryUsage(curUsage).subtract(lastUsage); String msg = "Updating mUidBatteryUsage uid=" + uid + ", before=" + before - + ", after=" + mUidBatteryUsage.get(uid, 0.0d) + + ", after=" + mUidBatteryUsage.get(uid, BATTERY_USAGE_NONE) + ", delta=" + actualDelta + ", last=" + lastUsage + ", curStart=" + curStart + ", lastLastStart=" + lastUidBatteryUsageStartTs + ", thisLastStart=" + mLastUidBatteryUsageStartTs; - if (actualDelta < 0.0d) { + if (!actualDelta.isValid()) { // Something is wrong, the battery usage shouldn't be negative. Slog.e(TAG, msg); } else { @@ -508,8 +521,8 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> } } - private static BatteryUsageStats updateBatteryUsageStatsOnceInternal(long expectedDuration, - SparseDoubleArray buf, BatteryUsageStatsQuery.Builder builder, + private BatteryUsageStats updateBatteryUsageStatsOnceInternal(long expectedDuration, + SparseArray<BatteryUsage> buf, BatteryUsageStatsQuery.Builder builder, ArraySet<UserHandle> userIds, BatteryStatsInternal batteryStatsInternal) { for (int i = 0, size = userIds.size(); i < size; i++) { builder.addUser(userIds.valueAt(i)); @@ -527,16 +540,19 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> final long end = stats.getStatsEndTimestamp(); final double scale = expectedDuration > 0 ? (expectedDuration * 1.0d) / (end - start) : 1.0d; + final AppBatteryPolicy bgPolicy = mInjector.getPolicy(); for (UidBatteryConsumer uidConsumer : uidConsumers) { // TODO: b/200326767 - as we are not supporting per proc state attribution yet, // we couldn't distinguish between a real FGS vs. a bound FGS proc state. final int uid = uidConsumer.getUid(); - final double bgUsage = getBgUsage(uidConsumer) * scale; + final BatteryUsage bgUsage = new BatteryUsage(uidConsumer, bgPolicy) + .scale(scale); int index = buf.indexOfKey(uid); if (index < 0) { buf.put(uid, bgUsage); } else { - buf.setValueAt(index, buf.valueAt(index) + bgUsage); + final BatteryUsage before = buf.valueAt(index); + before.add(bgUsage); } if (DEBUG_BACKGROUND_BATTERY_TRACKER) { Slog.i(TAG, "updateBatteryUsageStatsOnceInternal uid=" + uid @@ -549,32 +565,19 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> return stats; } - private static void copyUidBatteryUsage(SparseDoubleArray source, SparseDoubleArray dest) { + private static void copyUidBatteryUsage(SparseArray<? extends BatteryUsage> source, + SparseArray<ImmutableBatteryUsage> dest) { dest.clear(); for (int i = source.size() - 1; i >= 0; i--) { - dest.put(source.keyAt(i), source.valueAt(i)); + dest.put(source.keyAt(i), new ImmutableBatteryUsage(source.valueAt(i))); } } - private static void copyUidBatteryUsage(SparseDoubleArray source, SparseDoubleArray dest, - double scale) { + private static void copyUidBatteryUsage(SparseArray<? extends BatteryUsage> source, + SparseArray<ImmutableBatteryUsage> dest, double scale) { dest.clear(); for (int i = source.size() - 1; i >= 0; i--) { - dest.put(source.keyAt(i), source.valueAt(i) * scale); - } - } - - private static double getBgUsage(final UidBatteryConsumer uidConsumer) { - return getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_BG) - + getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_FGS); - } - - private static double getConsumedPowerNoThrow(final UidBatteryConsumer uidConsumer, - final BatteryConsumer.Dimensions dimens) { - try { - return uidConsumer.getConsumedPower(dimens); - } catch (IllegalArgumentException e) { - return 0.0d; + dest.put(source.keyAt(i), new ImmutableBatteryUsage(source.valueAt(i), scale)); } } @@ -602,6 +605,19 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> } } + void setDebugUidPercentage(int[] uids, double[][] percentages) { + mDebugUidPercentages.clear(); + for (int i = 0; i < uids.length; i++) { + mDebugUidPercentages.put(uids[i], new BatteryUsage().setPercentage(percentages[i])); + } + scheduleBgBatteryUsageStatsCheck(); + } + + void clearDebugUidPercentage() { + mDebugUidPercentages.clear(); + scheduleBgBatteryUsageStatsCheck(); + } + @VisibleForTesting void reset() { synchronized (mLock) { @@ -620,7 +636,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> pw.println("APP BATTERY STATE TRACKER:"); updateBatteryUsageStatsIfNecessary(mInjector.currentTimeMillis(), true); synchronized (mLock) { - final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow; + final SparseArray<ImmutableBatteryUsage> uidConsumers = mUidBatteryUsageInWindow; pw.print(" " + prefix); pw.print(" Last battery usage start="); TimeUtils.dumpTime(pw, mLastUidBatteryUsageStartTs); @@ -638,26 +654,286 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> } else { for (int i = 0, size = uidConsumers.size(); i < size; i++) { final int uid = uidConsumers.keyAt(i); - final double bgUsage = uidConsumers.valueAt(i); - final double exemptedUsage = mAppRestrictionController - .getUidBatteryExemptedUsageSince(uid, since, now); - final double reportedUsage = Math.max(0.0d, bgUsage - exemptedUsage); - pw.format("%s%s: [%s] %.3f mAh (%4.2f%%) | %.3f mAh (%4.2f%%) | " - + "%.3f mAh (%4.2f%%) | %.3f mAh\n", + final BatteryUsage bgUsage = uidConsumers.valueAt(i) + .calcPercentage(uid, bgPolicy); + final BatteryUsage exemptedUsage = mAppRestrictionController + .getUidBatteryExemptedUsageSince(uid, since, now, + bgPolicy.mBgCurrentDrainExemptedTypes) + .calcPercentage(uid, bgPolicy); + final BatteryUsage reportedUsage = new BatteryUsage(bgUsage) + .subtract(exemptedUsage) + .calcPercentage(uid, bgPolicy); + pw.format("%s%s: [%s] %s (%s) | %s (%s) | %s (%s) | %s\n", newPrefix, UserHandle.formatUid(uid), PowerExemptionManager.reasonCodeToString(bgPolicy.shouldExemptUid(uid)), - bgUsage , bgPolicy.getPercentage(uid, bgUsage), - exemptedUsage, bgPolicy.getPercentage(-1, exemptedUsage), - reportedUsage, bgPolicy.getPercentage(-1, reportedUsage), - mUidBatteryUsage.get(uid, 0.0d)); + bgUsage.toString(), + bgUsage.percentageToString(), + exemptedUsage.toString(), + exemptedUsage.percentageToString(), + reportedUsage.toString(), + reportedUsage.percentageToString(), + mUidBatteryUsage.get(uid, BATTERY_USAGE_NONE).toString()); } } } super.dump(pw, prefix); } + static class BatteryUsage { + static final int BATTERY_USAGE_INDEX_UNSPECIFIED = PROCESS_STATE_UNSPECIFIED; + static final int BATTERY_USAGE_INDEX_FOREGROUND = PROCESS_STATE_FOREGROUND; + static final int BATTERY_USAGE_INDEX_BACKGROUND = PROCESS_STATE_BACKGROUND; + static final int BATTERY_USAGE_INDEX_FOREGROUND_SERVICE = PROCESS_STATE_FOREGROUND_SERVICE; + static final int BATTERY_USAGE_COUNT = PROCESS_STATE_COUNT; + + static final Dimensions[] BATT_DIMENS = new Dimensions[] { + new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS, + PROCESS_STATE_UNSPECIFIED), + new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS, + PROCESS_STATE_FOREGROUND), + new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS, + PROCESS_STATE_BACKGROUND), + new Dimensions(AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS, + PROCESS_STATE_FOREGROUND_SERVICE), + }; + + @NonNull double[] mUsage; + @Nullable double[] mPercentage; + + BatteryUsage() { + this(0.0d, 0.0d, 0.0d, 0.0d); + } + + BatteryUsage(double unspecifiedUsage, double fgUsage, double bgUsage, double fgsUsage) { + mUsage = new double[] {unspecifiedUsage, fgUsage, bgUsage, fgsUsage}; + } + + BatteryUsage(@NonNull double[] usage) { + mUsage = usage; + } + + BatteryUsage(@NonNull BatteryUsage other, double scale) { + this(other); + scaleInternal(scale); + } + + BatteryUsage(@NonNull BatteryUsage other) { + mUsage = new double[other.mUsage.length]; + setToInternal(other); + } + + BatteryUsage(@NonNull UidBatteryConsumer consumer, @NonNull AppBatteryPolicy policy) { + final Dimensions[] dims = policy.mBatteryDimensions; + mUsage = new double[] { + getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_UNSPECIFIED]), + getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_FOREGROUND]), + getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_BACKGROUND]), + getConsumedPowerNoThrow(consumer, dims[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE]) + }; + } + + BatteryUsage setTo(@NonNull BatteryUsage other) { + return setToInternal(other); + } + + private BatteryUsage setToInternal(@NonNull BatteryUsage other) { + for (int i = 0; i < other.mUsage.length; i++) { + mUsage[i] = other.mUsage[i]; + } + return this; + } + + BatteryUsage add(@NonNull BatteryUsage other) { + for (int i = 0; i < other.mUsage.length; i++) { + mUsage[i] += other.mUsage[i]; + } + return this; + } + + BatteryUsage subtract(@NonNull BatteryUsage other) { + for (int i = 0; i < other.mUsage.length; i++) { + mUsage[i] = Math.max(0.0d, mUsage[i] - other.mUsage[i]); + } + return this; + } + + BatteryUsage scale(double scale) { + return scaleInternal(scale); + } + + private BatteryUsage scaleInternal(double scale) { + for (int i = 0; i < mUsage.length; i++) { + mUsage[i] *= scale; + } + return this; + } + + ImmutableBatteryUsage unmutate() { + return new ImmutableBatteryUsage(this); + } + + BatteryUsage calcPercentage(int uid, @NonNull AppBatteryPolicy policy) { + if (mPercentage == null || mPercentage.length != mUsage.length) { + mPercentage = new double[mUsage.length]; + } + policy.calcPercentage(uid, mUsage, mPercentage); + return this; + } + + BatteryUsage setPercentage(@NonNull double[] percentage) { + mPercentage = percentage; + return this; + } + + double[] getPercentage() { + return mPercentage; + } + + String percentageToString() { + return formatBatteryUsagePercentage(mPercentage); + } + + @Override + public String toString() { + return formatBatteryUsage(mUsage); + } + + boolean isValid() { + for (int i = 0; i < mUsage.length; i++) { + if (mUsage[i] < 0.0d) { + return false; + } + } + return true; + } + + boolean isEmpty() { + for (int i = 0; i < mUsage.length; i++) { + if (mUsage[i] > 0.0d) { + return false; + } + } + return true; + } + + @Override + public boolean equals(Object other) { + if (other == null) { + return false; + } + final BatteryUsage otherUsage = (BatteryUsage) other; + for (int i = 0; i < mUsage.length; i++) { + if (Double.compare(mUsage[i], otherUsage.mUsage[i]) != 0) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int hashCode = 0; + for (int i = 0; i < mUsage.length; i++) { + hashCode = Double.hashCode(mUsage[i]) + hashCode * 31; + } + return hashCode; + } + + private static String formatBatteryUsage(double[] usage) { + return String.format("%.3f %.3f %.3f %.3f mAh", + usage[BATTERY_USAGE_INDEX_UNSPECIFIED], + usage[BATTERY_USAGE_INDEX_FOREGROUND], + usage[BATTERY_USAGE_INDEX_BACKGROUND], + usage[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE]); + } + + static String formatBatteryUsagePercentage(double[] percentage) { + return String.format("%4.2f%% %4.2f%% %4.2f%% %4.2f%%", + percentage[BATTERY_USAGE_INDEX_UNSPECIFIED], + percentage[BATTERY_USAGE_INDEX_FOREGROUND], + percentage[BATTERY_USAGE_INDEX_BACKGROUND], + percentage[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE]); + } + + private static double getConsumedPowerNoThrow(final UidBatteryConsumer uidConsumer, + final Dimensions dimens) { + try { + return uidConsumer.getConsumedPower(dimens); + } catch (IllegalArgumentException e) { + return 0.0d; + } + } + } + + static final class ImmutableBatteryUsage extends BatteryUsage { + ImmutableBatteryUsage() { + super(); + } + + ImmutableBatteryUsage(double unspecifiedUsage, double fgUsage, double bgUsage, + double fgsUsage) { + super(unspecifiedUsage, fgUsage, bgUsage, fgsUsage); + } + + ImmutableBatteryUsage(@NonNull double[] usage) { + super(usage); + } + + ImmutableBatteryUsage(@NonNull BatteryUsage other, double scale) { + super(other, scale); + } + + ImmutableBatteryUsage(@NonNull BatteryUsage other) { + super(other); + } + + ImmutableBatteryUsage(@NonNull UidBatteryConsumer consumer, + @NonNull AppBatteryPolicy policy) { + super(consumer, policy); + } + + @Override + BatteryUsage setTo(@NonNull BatteryUsage other) { + throw new RuntimeException("Readonly"); + } + + @Override + BatteryUsage add(@NonNull BatteryUsage other) { + throw new RuntimeException("Readonly"); + } + + @Override + BatteryUsage subtract(@NonNull BatteryUsage other) { + throw new RuntimeException("Readonly"); + } + + @Override + BatteryUsage scale(double scale) { + throw new RuntimeException("Readonly"); + } + + @Override + BatteryUsage setPercentage(@NonNull double[] percentage) { + throw new RuntimeException("Readonly"); + } + + BatteryUsage mutate() { + return new BatteryUsage(this); + } + } + static final class AppBatteryPolicy extends BaseAppStatePolicy<AppBatteryTracker> { /** + * The type of battery usage we could choose to apply the policy on. + * + * Must be in sync with android.os.BatteryConsumer.PROCESS_STATE_*. + */ + static final int BATTERY_USAGE_TYPE_UNSPECIFIED = 1; + static final int BATTERY_USAGE_TYPE_FOREGROUND = 1 << 1; + static final int BATTERY_USAGE_TYPE_BACKGROUND = 1 << 2; + static final int BATTERY_USAGE_TYPE_FOREGROUND_SERVICE = 1 << 3; + + /** * Whether or not we should enable the monitoring on background current drains. */ static final String KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED = @@ -729,58 +1005,115 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> + "current_drain_event_duration_based_threshold_enabled"; /** - * Default value to {@link #mTrackerEnabled}. + * The types of battery drain we're checking on each app; if the sum of the battery drain + * exceeds the threshold, it'll be moved to restricted standby bucket; the type here + * must be one of, or combination of {@link #BATTERY_USAGE_TYPE_BACKGROUND} and + * {@link #BATTERY_USAGE_TYPE_FOREGROUND_SERVICE}. + */ + static final String KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_types_to_restricted_bucket"; + + /** + * The types of battery drain we're checking on each app; if the sum of the battery drain + * exceeds the threshold, it'll be moved to background restricted level; the type here + * must be one of, or combination of {@link #BATTERY_USAGE_TYPE_BACKGROUND} and + * {@link #BATTERY_USAGE_TYPE_FOREGROUND_SERVICE}. */ - static final boolean DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED = true; + static final String KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_types_to_bg_restricted"; + + /** + * The power usage components we're monitoring. + */ + static final String KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_power_components"; + + /** + * The types of state where we'll exempt its battery usage when it's in that state. + * The state here must be one or a combination of STATE_TYPE_* in BaseAppStateTracker. + */ + static final String KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_exempted_types"; + + /** + * The behavior when an app has the permission ACCESS_BACKGROUND_LOCATION granted, + * whether or not the system will use a higher threshold towards its background battery + * usage because of it. + */ + static final String KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_high_threshold_by_bg_location"; /** * Default value to the {@link #INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD} of * the {@link #mBgCurrentDrainRestrictedBucketThreshold}. */ - static final float DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD = - isLowRamDeviceStatic() ? 4.0f : 2.0f; + final float mDefaultBgCurrentDrainRestrictedBucket; /** * Default value to the {@link #INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD} of * the {@link #mBgCurrentDrainBgRestrictedThreshold}. */ - static final float DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD = - isLowRamDeviceStatic() ? 8.0f : 4.0f; + final float mDefaultBgCurrentDrainBgRestrictedThreshold; /** * Default value to {@link #mBgCurrentDrainWindowMs}. */ - static final long DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS = ONE_DAY; + final long mDefaultBgCurrentDrainWindowMs; /** * Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of * the {@link #mBgCurrentDrainRestrictedBucketThreshold}. */ - static final float DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_HIGH_THRESHOLD = - isLowRamDeviceStatic() ? 60.0f : 30.0f; + final float mDefaultBgCurrentDrainRestrictedBucketHighThreshold; /** * Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of * the {@link #mBgCurrentDrainBgRestrictedThreshold}. */ - static final float DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD = - isLowRamDeviceStatic() ? 60.0f : 30.0f; + final float mDefaultBgCurrentDrainBgRestrictedHighThreshold; /** * Default value to {@link #mBgCurrentDrainMediaPlaybackMinDuration}. */ - static final long DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION = 30 * ONE_MINUTE; + final long mDefaultBgCurrentDrainMediaPlaybackMinDuration; /** * Default value to {@link #mBgCurrentDrainLocationMinDuration}. */ - static final long DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION = 30 * ONE_MINUTE; + final long mDefaultBgCurrentDrainLocationMinDuration; /** * Default value to {@link #mBgCurrentDrainEventDurationBasedThresholdEnabled}. */ - static final boolean DEFAULT_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED = - false; + final boolean mDefaultBgCurrentDrainEventDurationBasedThresholdEnabled; + + /** + * Default value to {@link #mBgCurrentDrainRestrictedBucketTypes}. + */ + final int mDefaultCurrentDrainTypesToRestrictedBucket; + + /** + * Default value to {@link #mBgCurrentDrainBgRestrictedTypes}. + */ + final int mDefaultBgCurrentDrainTypesToBgRestricted; + + /** + * Default value to {@link #mBgCurrentDrainPowerComponents}. + **/ + @BatteryConsumer.PowerComponent + static final int DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS = POWER_COMPONENT_ANY; + + final int mDefaultBgCurrentDrainPowerComponent; + + /** + * Default value to {@link #mBgCurrentDrainExmptedTypes}. + **/ + final int mDefaultBgCurrentDrainExemptedTypes; + + /** + * Default value to {@link #mBgCurrentDrainHighThresholdByBgLocation}. + */ + final boolean mDefaultBgCurrentDrainHighThresholdByBgLocation; /** * The index to {@link #mBgCurrentDrainRestrictedBucketThreshold} @@ -793,36 +1126,28 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> * @see #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET. * @see #KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET. */ - volatile float[] mBgCurrentDrainRestrictedBucketThreshold = { - DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD, - DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD, - }; + volatile float[] mBgCurrentDrainRestrictedBucketThreshold = new float[2]; /** * @see #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED. * @see #KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED. */ - volatile float[] mBgCurrentDrainBgRestrictedThreshold = { - DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD, - DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD, - }; + volatile float[] mBgCurrentDrainBgRestrictedThreshold = new float[2]; /** * @see #KEY_BG_CURRENT_DRAIN_WINDOW. */ - volatile long mBgCurrentDrainWindowMs = DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS; + volatile long mBgCurrentDrainWindowMs; /** * @see #KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION. */ - volatile long mBgCurrentDrainMediaPlaybackMinDuration = - DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION; + volatile long mBgCurrentDrainMediaPlaybackMinDuration; /** * @see #KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION. */ - volatile long mBgCurrentDrainLocationMinDuration = - DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION; + volatile long mBgCurrentDrainLocationMinDuration; /** * @see #KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED. @@ -830,6 +1155,34 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> volatile boolean mBgCurrentDrainEventDurationBasedThresholdEnabled; /** + * @see #KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET. + */ + volatile int mBgCurrentDrainRestrictedBucketTypes; + + /** + * @see #KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED. + */ + volatile int mBgCurrentDrainBgRestrictedTypes; + + /** + * @see #KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS. + */ + @BatteryConsumer.PowerComponent + volatile int mBgCurrentDrainPowerComponents; + + volatile Dimensions[] mBatteryDimensions; + + /** + * @see #KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES. + */ + volatile int mBgCurrentDrainExemptedTypes; + + /** + * @see #KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION. + */ + volatile boolean mBgCurrentDrainHighThresholdByBgLocation; + + /** * The capacity of the battery when fully charged in mAh. */ private int mBatteryFullChargeMah; @@ -851,8 +1204,66 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> AppBatteryPolicy(@NonNull Injector injector, @NonNull AppBatteryTracker tracker) { super(injector, tracker, KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED, - DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED); + tracker.mContext.getResources() + .getBoolean(R.bool.config_bg_current_drain_monitor_enabled)); mLock = tracker.mLock; + final Resources resources = tracker.mContext.getResources(); + float[] val = getFloatArray(resources.obtainTypedArray( + R.array.config_bg_current_drain_threshold_to_restricted_bucket)); + mDefaultBgCurrentDrainRestrictedBucket = + isLowRamDeviceStatic() ? val[1] : val[0]; + val = getFloatArray(resources.obtainTypedArray( + R.array.config_bg_current_drain_threshold_to_bg_restricted)); + mDefaultBgCurrentDrainBgRestrictedThreshold = + isLowRamDeviceStatic() ? val[1] : val[0]; + mDefaultBgCurrentDrainWindowMs = resources.getInteger( + R.integer.config_bg_current_drain_window); + val = getFloatArray(resources.obtainTypedArray( + R.array.config_bg_current_drain_high_threshold_to_restricted_bucket)); + mDefaultBgCurrentDrainRestrictedBucketHighThreshold = + isLowRamDeviceStatic() ? val[1] : val[0]; + val = getFloatArray(resources.obtainTypedArray( + R.array.config_bg_current_drain_high_threshold_to_bg_restricted)); + mDefaultBgCurrentDrainBgRestrictedHighThreshold = + isLowRamDeviceStatic() ? val[1] : val[0]; + mDefaultBgCurrentDrainMediaPlaybackMinDuration = resources.getInteger( + R.integer.config_bg_current_drain_media_playback_min_duration); + mDefaultBgCurrentDrainLocationMinDuration = resources.getInteger( + R.integer.config_bg_current_drain_location_min_duration); + mDefaultBgCurrentDrainEventDurationBasedThresholdEnabled = resources.getBoolean( + R.bool.config_bg_current_drain_event_duration_based_threshold_enabled); + mDefaultCurrentDrainTypesToRestrictedBucket = resources.getInteger( + R.integer.config_bg_current_drain_types_to_restricted_bucket); + mDefaultBgCurrentDrainTypesToBgRestricted = resources.getInteger( + R.integer.config_bg_current_drain_types_to_bg_restricted); + mDefaultBgCurrentDrainPowerComponent = resources.getInteger( + R.integer.config_bg_current_drain_power_components); + mDefaultBgCurrentDrainExemptedTypes = resources.getInteger( + R.integer.config_bg_current_drain_exempted_types); + mDefaultBgCurrentDrainHighThresholdByBgLocation = resources.getBoolean( + R.bool.config_bg_current_drain_high_threshold_by_bg_location); + mBgCurrentDrainRestrictedBucketThreshold[0] = + mDefaultBgCurrentDrainRestrictedBucket; + mBgCurrentDrainRestrictedBucketThreshold[1] = + mDefaultBgCurrentDrainRestrictedBucketHighThreshold; + mBgCurrentDrainBgRestrictedThreshold[0] = + mDefaultBgCurrentDrainBgRestrictedThreshold; + mBgCurrentDrainBgRestrictedThreshold[1] = + mDefaultBgCurrentDrainBgRestrictedHighThreshold; + mBgCurrentDrainWindowMs = mDefaultBgCurrentDrainWindowMs; + mBgCurrentDrainMediaPlaybackMinDuration = + mDefaultBgCurrentDrainMediaPlaybackMinDuration; + mBgCurrentDrainLocationMinDuration = mDefaultBgCurrentDrainLocationMinDuration; + } + + static float[] getFloatArray(TypedArray array) { + int length = array.length(); + float[] floatArray = new float[length]; + for (int i = 0; i < length; i++) { + floatArray[i] = array.getFloat(i, Float.NaN); + } + array.recycle(); + return floatArray; } @Override @@ -860,8 +1271,12 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> switch (name) { case KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET: case KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED: + case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION: case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET: case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED: + case KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET: + case KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED: + case KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS: updateCurrentDrainThreshold(); break; case KEY_BG_CURRENT_DRAIN_WINDOW: @@ -876,6 +1291,9 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> case KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED: updateCurrentDrainEventDurationBasedThresholdEnabled(); break; + case KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES: + updateCurrentDrainExemptedTypes(); + break; default: super.onPropertiesChanged(name); break; @@ -899,48 +1317,78 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> mBgCurrentDrainRestrictedBucketThreshold[INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD] = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET, - DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD); + mDefaultBgCurrentDrainRestrictedBucket); mBgCurrentDrainRestrictedBucketThreshold[INDEX_HIGH_CURRENT_DRAIN_THRESHOLD] = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET, - DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_HIGH_THRESHOLD); + mDefaultBgCurrentDrainRestrictedBucketHighThreshold); mBgCurrentDrainBgRestrictedThreshold[INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD] = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED, - DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD); + mDefaultBgCurrentDrainBgRestrictedThreshold); mBgCurrentDrainBgRestrictedThreshold[INDEX_HIGH_CURRENT_DRAIN_THRESHOLD] = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED, - DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD); + mDefaultBgCurrentDrainBgRestrictedHighThreshold); + mBgCurrentDrainRestrictedBucketTypes = + DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET, + mDefaultCurrentDrainTypesToRestrictedBucket); + mBgCurrentDrainBgRestrictedTypes = + DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED, + mDefaultBgCurrentDrainTypesToBgRestricted); + mBgCurrentDrainPowerComponents = + DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS, + mDefaultBgCurrentDrainPowerComponent); + if (mBgCurrentDrainPowerComponents == DEFAULT_BG_CURRENT_DRAIN_POWER_COMPONENTS) { + mBatteryDimensions = BatteryUsage.BATT_DIMENS; + } else { + mBatteryDimensions = new Dimensions[BatteryUsage.BATTERY_USAGE_COUNT]; + for (int i = 0; i < BatteryUsage.BATTERY_USAGE_COUNT; i++) { + mBatteryDimensions[i] = new Dimensions(mBgCurrentDrainPowerComponents, i); + } + } + mBgCurrentDrainHighThresholdByBgLocation = + DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION, + mDefaultBgCurrentDrainHighThresholdByBgLocation); } private void updateCurrentDrainWindow() { mBgCurrentDrainWindowMs = DeviceConfig.getLong( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_BG_CURRENT_DRAIN_WINDOW, - mBgCurrentDrainWindowMs != DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS - ? mBgCurrentDrainWindowMs : DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS); + mDefaultBgCurrentDrainWindowMs); } private void updateCurrentDrainMediaPlaybackMinDuration() { mBgCurrentDrainMediaPlaybackMinDuration = DeviceConfig.getLong( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION, - DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION); + mDefaultBgCurrentDrainMediaPlaybackMinDuration); } private void updateCurrentDrainLocationMinDuration() { mBgCurrentDrainLocationMinDuration = DeviceConfig.getLong( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION, - DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION); + mDefaultBgCurrentDrainLocationMinDuration); } private void updateCurrentDrainEventDurationBasedThresholdEnabled() { mBgCurrentDrainEventDurationBasedThresholdEnabled = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED, - DEFAULT_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED); + mDefaultBgCurrentDrainEventDurationBasedThresholdEnabled); + } + + private void updateCurrentDrainExemptedTypes() { + mBgCurrentDrainExemptedTypes = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES, + mDefaultBgCurrentDrainExemptedTypes); } @Override @@ -953,6 +1401,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> updateCurrentDrainMediaPlaybackMinDuration(); updateCurrentDrainLocationMinDuration(); updateCurrentDrainEventDurationBasedThresholdEnabled(); + updateCurrentDrainExemptedTypes(); } @Override @@ -970,18 +1419,58 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> } } - double getBgUsage(final UidBatteryConsumer uidConsumer) { - return getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_BG) - + getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_FGS); + double[] calcPercentage(final int uid, final double[] usage, double[] percentage) { + final BatteryUsage debugUsage = uid > 0 ? mTracker.mDebugUidPercentages.get(uid) : null; + final double[] forced = debugUsage != null ? debugUsage.getPercentage() : null; + for (int i = 0; i < usage.length; i++) { + percentage[i] = forced != null ? forced[i] : usage[i] / mBatteryFullChargeMah * 100; + } + return percentage; } - double getPercentage(final int uid, final double usage) { - final double actualPercentage = usage / mBatteryFullChargeMah * 100; - return DEBUG_BACKGROUND_BATTERY_TRACKER - ? mTracker.mDebugUidPercentages.get(uid, actualPercentage) : actualPercentage; + private double sumPercentageOfTypes(double[] percentage, int types) { + double result = 0.0d; + for (int type = Integer.highestOneBit(types); type != 0; + type = Integer.highestOneBit(types)) { + final int index = Integer.numberOfTrailingZeros(type); + result += percentage[index]; + types &= ~type; + } + return result; } - void handleUidBatteryUsage(final int uid, final double percentage) { + private static String batteryUsageTypesToString(int types) { + final StringBuilder sb = new StringBuilder("["); + boolean needDelimiter = false; + for (int type = Integer.highestOneBit(types); type != 0; + type = Integer.highestOneBit(types)) { + if (needDelimiter) { + sb.append('|'); + } + needDelimiter = true; + switch (type) { + case BATTERY_USAGE_TYPE_UNSPECIFIED: + sb.append("UNSPECIFIED"); + break; + case BATTERY_USAGE_TYPE_FOREGROUND: + sb.append("FOREGROUND"); + break; + case BATTERY_USAGE_TYPE_BACKGROUND: + sb.append("BACKGROUND"); + break; + case BATTERY_USAGE_TYPE_FOREGROUND_SERVICE: + sb.append("FOREGROUND_SERVICE"); + break; + default: + return "[UNKNOWN(" + Integer.toHexString(types) + ")]"; + } + types &= ~type; + } + sb.append("]"); + return sb.toString(); + } + + void handleUidBatteryUsage(final int uid, final BatteryUsage usage) { final @ReasonCode int reason = shouldExemptUid(uid); if (reason != REASON_DENIED) { if (DEBUG_BACKGROUND_BATTERY_TRACKER) { @@ -992,6 +1481,10 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> } boolean notifyController = false; boolean excessive = false; + final double rbPercentage = sumPercentageOfTypes(usage.getPercentage(), + mBgCurrentDrainRestrictedBucketTypes); + final double brPercentage = sumPercentageOfTypes(usage.getPercentage(), + mBgCurrentDrainBgRestrictedTypes); synchronized (mLock) { final int curLevel = mTracker.mAppRestrictionController.getRestrictionLevel(uid); if (curLevel >= RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) { @@ -1003,7 +1496,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> mBgCurrentDrainWindowMs); final int index = mHighBgBatteryPackages.indexOfKey(uid); if (index < 0) { - if (percentage >= mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) { + if (rbPercentage >= mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) { // New findings to us, track it and let the controller know. final long[] ts = new long[TIME_STAMP_INDEX_LAST]; ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now; @@ -1012,22 +1505,24 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> } } else { final long[] ts = mHighBgBatteryPackages.valueAt(index); - if (percentage < mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) { + if (rbPercentage < mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) { // it's actually back to normal, but we don't untrack it until // explicit user interactions. notifyController = true; } else { excessive = true; - if (percentage >= mBgCurrentDrainBgRestrictedThreshold[thresholdIndex]) { + if (brPercentage >= mBgCurrentDrainBgRestrictedThreshold[thresholdIndex] + && curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET) { // If we're in the restricted standby bucket but still seeing high // current drains, tell the controller again. - if (curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET - && ts[TIME_STAMP_INDEX_BG_RESTRICTED] == 0) { - if (now > ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] - + mBgCurrentDrainWindowMs) { - ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now; - notifyController = true; - } + final long lastResbucket = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET]; + final long lastBgRes = ts[TIME_STAMP_INDEX_BG_RESTRICTED]; + // If it has been a while since restricting the app and since the last + // time we notify the controller, notify it again. + if ((now >= lastResbucket + mBgCurrentDrainWindowMs) && (lastBgRes == 0 + || (now >= lastBgRes + mBgCurrentDrainWindowMs))) { + ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now; + notifyController = true; } } } @@ -1037,7 +1532,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> if (excessive) { if (DEBUG_BACKGROUND_BATTERY_TRACKER) { Slog.i(TAG, "Excessive background current drain " + uid - + String.format(" %.2f%%", percentage) + " over " + + usage + " (" + usage.percentageToString() + " ) over " + TimeUtils.formatDuration(mBgCurrentDrainWindowMs)); } if (notifyController) { @@ -1048,7 +1543,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> } else { if (DEBUG_BACKGROUND_BATTERY_TRACKER) { Slog.i(TAG, "Background current drain backs to normal " + uid - + String.format(" %.2f%%", percentage) + " over " + + usage + " (" + usage.percentageToString() + " ) over " + TimeUtils.formatDuration(mBgCurrentDrainWindowMs)); } // For now, we're not lifting the restrictions if the bg current drain backs to @@ -1069,6 +1564,9 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> } private boolean hasLocation(int uid, long now, long window) { + if (!mBgCurrentDrainHighThresholdByBgLocation) { + return false; + } final AppRestrictionController controller = mTracker.mAppRestrictionController; if (mInjector.getPermissionManagerServiceInternal().checkUidPermission( uid, ACCESS_BACKGROUND_LOCATION) == PERMISSION_GRANTED) { @@ -1120,15 +1618,6 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> } } - private double getConsumedPowerNoThrow(final UidBatteryConsumer uidConsumer, - final BatteryConsumer.Dimensions dimens) { - try { - return uidConsumer.getConsumedPower(dimens); - } catch (IllegalArgumentException e) { - return 0.0d; - } - } - @VisibleForTesting void reset() { mHighBgBatteryPackages.clear(); @@ -1179,6 +1668,26 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> pw.print(KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED); pw.print('='); pw.println(mBgCurrentDrainEventDurationBasedThresholdEnabled); + pw.print(prefix); + pw.print(KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET); + pw.print('='); + pw.println(batteryUsageTypesToString(mBgCurrentDrainRestrictedBucketTypes)); + pw.print(prefix); + pw.print(KEY_BG_CURRENT_DRAIN_TYPES_TO_BG_RESTRICTED); + pw.print('='); + pw.println(batteryUsageTypesToString(mBgCurrentDrainBgRestrictedTypes)); + pw.print(prefix); + pw.print(KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS); + pw.print('='); + pw.println(mBgCurrentDrainPowerComponents); + pw.print(prefix); + pw.print(KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES); + pw.print('='); + pw.println(BaseAppStateTracker.stateTypesToString(mBgCurrentDrainExemptedTypes)); + pw.print(prefix); + pw.print(KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION); + pw.print('='); + pw.println(mBgCurrentDrainHighThresholdByBgLocation); pw.print(prefix); pw.println("Excessive current drain detected:"); diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java index 9c775b34f9c2..5ac5a70ccf74 100644 --- a/services/core/java/com/android/server/am/AppFGSTracker.java +++ b/services/core/java/com/android/server/am/AppFGSTracker.java @@ -16,8 +16,6 @@ package com.android.server.am; -import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE; @@ -43,6 +41,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.DeviceConfig; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; @@ -54,6 +53,7 @@ import com.android.server.am.AppFGSTracker.PackageDurations; import com.android.server.am.BaseAppStateEventsTracker.BaseAppStateEventsPolicy; import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent; import com.android.server.am.BaseAppStateTracker.Injector; +import com.android.server.notification.NotificationManagerInternal; import java.io.PrintWriter; import java.lang.reflect.Constructor; @@ -71,6 +71,9 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac private final MyHandler mHandler; + @GuardedBy("mLock") + private final UidProcessMap<ArraySet<Integer>> mFGSNotificationIDs = new UidProcessMap<>(); + // Unlocked since it's only accessed in single thread. private final ArrayMap<PackageDurations, Long> mTmpPkgDurations = new ArrayMap<>(); @@ -100,11 +103,19 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac : MyHandler.MSG_FOREGROUND_SERVICES_STOPPED, pid, uid, packageName).sendToTarget(); } + @Override + public void onForegroundServiceNotificationUpdated(String packageName, int uid, + int foregroundId) { + mHandler.obtainMessage(MyHandler.MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED, + uid, foregroundId, packageName).sendToTarget(); + } + private static class MyHandler extends Handler { static final int MSG_FOREGROUND_SERVICES_STARTED = 0; static final int MSG_FOREGROUND_SERVICES_STOPPED = 1; static final int MSG_FOREGROUND_SERVICES_CHANGED = 2; - static final int MSG_CHECK_LONG_RUNNING_FGS = 3; + static final int MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED = 3; + static final int MSG_CHECK_LONG_RUNNING_FGS = 4; private final AppFGSTracker mTracker; @@ -128,6 +139,10 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac mTracker.handleForegroundServicesChanged( (String) msg.obj, msg.arg1, msg.arg2); break; + case MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED: + mTracker.handleForegroundServiceNotificationUpdated( + (String) msg.obj, msg.arg1, msg.arg2); + break; case MSG_CHECK_LONG_RUNNING_FGS: mTracker.checkLongRunningFgs(); break; @@ -205,6 +220,44 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac } } + private void handleForegroundServiceNotificationUpdated(String packageName, int uid, + int notificationId) { + synchronized (mLock) { + if (notificationId > 0) { + ArraySet<Integer> notificationIDs = mFGSNotificationIDs.get(uid, packageName); + if (notificationIDs == null) { + notificationIDs = new ArraySet<>(); + mFGSNotificationIDs.put(uid, packageName, notificationIDs); + } + notificationIDs.add(notificationId); + } else if (notificationId < 0) { + final ArraySet<Integer> notificationIDs = mFGSNotificationIDs.get(uid, packageName); + if (notificationIDs != null) { + notificationIDs.remove(-notificationId); + if (notificationIDs.isEmpty()) { + mFGSNotificationIDs.remove(uid, packageName); + } + } + } + } + } + + @GuardedBy("mLock") + private boolean hasForegroundServiceNotificationsLocked(String packageName, int uid) { + final ArraySet<Integer> notificationIDs = mFGSNotificationIDs.get(uid, packageName); + if (notificationIDs == null || notificationIDs.isEmpty()) { + return false; + } + final NotificationManagerInternal nm = mInjector.getNotificationManagerInternal(); + final int userId = UserHandle.getUserId(uid); + for (int i = notificationIDs.size() - 1; i >= 0; i--) { + if (nm.isNotificationShown(packageName, null, notificationIDs.valueAt(i), userId)) { + return true; + } + } + return false; + } + @GuardedBy("mLock") private void scheduleDurationCheckLocked(long now) { // Look for the active FGS with longest running time till now. @@ -375,6 +428,28 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac } } + boolean hasForegroundServiceNotifications(String packageName, int uid) { + synchronized (mLock) { + return hasForegroundServiceNotificationsLocked(packageName, uid); + } + } + + boolean hasForegroundServiceNotifications(int uid) { + synchronized (mLock) { + final SparseArray<ArrayMap<String, ArraySet<Integer>>> map = + mFGSNotificationIDs.getMap(); + final ArrayMap<String, ArraySet<Integer>> pkgs = map.get(uid); + if (pkgs != null) { + for (int i = pkgs.size() - 1; i >= 0; i--) { + if (hasForegroundServiceNotificationsLocked(pkgs.keyAt(i), uid)) { + return true; + } + } + } + } + return false; + } + @Override void dump(PrintWriter pw, String prefix) { pw.print(prefix); @@ -382,6 +457,35 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac super.dump(pw, " " + prefix); } + @Override + void dumpOthers(PrintWriter pw, String prefix) { + pw.print(prefix); + pw.println("APPS WITH ACTIVE FOREGROUND SERVICES:"); + prefix = " " + prefix; + synchronized (mLock) { + final SparseArray<ArrayMap<String, ArraySet<Integer>>> map = + mFGSNotificationIDs.getMap(); + if (map.size() == 0) { + pw.print(prefix); + pw.println("(none)"); + } + for (int i = 0, size = map.size(); i < size; i++) { + final int uid = map.keyAt(i); + final String uidString = UserHandle.formatUid(uid); + final ArrayMap<String, ArraySet<Integer>> pkgs = map.valueAt(i); + for (int j = 0, numOfPkgs = pkgs.size(); j < numOfPkgs; j++) { + final String pkgName = pkgs.keyAt(j); + pw.print(prefix); + pw.print(pkgName); + pw.print('/'); + pw.print(uidString); + pw.print(" notification="); + pw.println(hasForegroundServiceNotificationsLocked(pkgName, uid)); + } + } + } + } + /** * Tracks the durations with active FGS for a given package. */ @@ -437,7 +541,7 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac } if (isActive(i)) { mEvents[i].add(new BaseTimeEvent(now)); - notifyListenersOnEventIfNecessary(false, now, + notifyListenersOnStateChangeIfNecessary(false, now, indexToForegroundServiceType(i)); } } @@ -463,13 +567,13 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac } if (!isActive(i)) { mEvents[i].add(new BaseTimeEvent(now)); - notifyListenersOnEventIfNecessary(true, now, serviceType); + notifyListenersOnStateChangeIfNecessary(true, now, serviceType); } } else { // Stop this type. if (mEvents[i] != null && isActive(i)) { mEvents[i].add(new BaseTimeEvent(now)); - notifyListenersOnEventIfNecessary(false, now, serviceType); + notifyListenersOnStateChangeIfNecessary(false, now, serviceType); } } changes &= ~serviceType; @@ -478,20 +582,20 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac mForegroundServiceTypes = serviceTypes; } - private void notifyListenersOnEventIfNecessary(boolean start, long now, + private void notifyListenersOnStateChangeIfNecessary(boolean start, long now, @ForegroundServiceType int serviceType) { - int eventType; + int stateType; switch (serviceType) { case FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK: - eventType = BaseAppStateDurationsTracker.EVENT_TYPE_FGS_MEDIA_PLAYBACK; + stateType = BaseAppStateDurationsTracker.STATE_TYPE_FGS_MEDIA_PLAYBACK; break; case FOREGROUND_SERVICE_TYPE_LOCATION: - eventType = BaseAppStateDurationsTracker.EVENT_TYPE_FGS_LOCATION; + stateType = BaseAppStateDurationsTracker.STATE_TYPE_FGS_LOCATION; break; default: return; } - mTracker.notifyListenersOnEvent(mUid, mPackageName, start, now, eventType); + mTracker.notifyListenersOnStateChange(mUid, mPackageName, start, now, stateType); } void setIsLongRunning(boolean isLongRunning) { @@ -687,10 +791,6 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac if (shouldExemptLocationFGS(packageName, uid, now, since)) { return; } - if (hasBackgroundLocationPermission(packageName, uid)) { - // This package has background location permission, ignore it. - return; - } mTracker.mAppRestrictionController.postLongRunningFgsIfNecessary(packageName, uid); } @@ -723,19 +823,6 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac return false; } - boolean hasBackgroundLocationPermission(String packageName, int uid) { - if (mInjector.getPermissionManagerServiceInternal().checkPermission( - packageName, ACCESS_BACKGROUND_LOCATION, UserHandle.getUserId(uid)) - == PERMISSION_GRANTED) { - if (DEBUG_BACKGROUND_FGS_TRACKER) { - Slog.i(TAG, "Ignoring bg-location FGS in " + packageName + "/" - + UserHandle.formatUid(uid)); - } - return true; - } - return false; - } - @Override String getExemptionReasonString(String packageName, int uid, @ReasonCode int reason) { if (reason != REASON_DENIED) { @@ -745,8 +832,7 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac final long window = getFgsLongRunningWindowSize(); final long since = Math.max(0, now - getFgsLongRunningWindowSize()); return "{mediaPlayback=" + shouldExemptMediaPlaybackFGS(packageName, uid, now, window) - + ", location=" + shouldExemptLocationFGS(packageName, uid, now, since) - + ", bgLocationPerm=" + hasBackgroundLocationPermission(packageName, uid) + "}"; + + ", location=" + shouldExemptLocationFGS(packageName, uid, now, since) + "}"; } void onLongRunningFgsGone(String packageName, int uid) { diff --git a/services/core/java/com/android/server/am/AppMediaSessionTracker.java b/services/core/java/com/android/server/am/AppMediaSessionTracker.java index 3914f6f10ab0..4ce23f7c02fb 100644 --- a/services/core/java/com/android/server/am/AppMediaSessionTracker.java +++ b/services/core/java/com/android/server/am/AppMediaSessionTracker.java @@ -19,8 +19,8 @@ package com.android.server.am; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX; -import static com.android.server.am.BaseAppStateDurationsTracker.EVENT_TYPE_MEDIA_SESSION; import static com.android.server.am.BaseAppStateTracker.ONE_DAY; +import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_MEDIA_SESSION; import android.annotation.NonNull; import android.content.Context; @@ -104,8 +104,8 @@ final class AppMediaSessionTracker } if (!pkg.isActive()) { pkg.addEvent(true, now); - notifyListenersOnEvent(pkg.mUid, pkg.mPackageName, true, now, - EVENT_TYPE_MEDIA_SESSION); + notifyListenersOnStateChange(pkg.mUid, pkg.mPackageName, true, now, + STATE_TYPE_MEDIA_SESSION); } // Mark it as active, so we could filter out inactive ones below. mTmpMediaControllers.put(packageName, uid, Boolean.TRUE); @@ -127,8 +127,8 @@ final class AppMediaSessionTracker && mTmpMediaControllers.get(pkg.mPackageName, pkg.mUid) == null) { // This package has removed its controller, issue a stop event. pkg.addEvent(false, now); - notifyListenersOnEvent(pkg.mUid, pkg.mPackageName, false, now, - EVENT_TYPE_MEDIA_SESSION); + notifyListenersOnStateChange(pkg.mUid, pkg.mPackageName, false, now, + STATE_TYPE_MEDIA_SESSION); } } } @@ -146,8 +146,8 @@ final class AppMediaSessionTracker final SimplePackageDurations pkg = val.valueAt(j); if (pkg.isActive()) { pkg.addEvent(false, now); - notifyListenersOnEvent(pkg.mUid, pkg.mPackageName, false, now, - EVENT_TYPE_MEDIA_SESSION); + notifyListenersOnStateChange(pkg.mUid, pkg.mPackageName, false, now, + STATE_TYPE_MEDIA_SESSION); } } } diff --git a/services/core/java/com/android/server/am/AppPermissionTracker.java b/services/core/java/com/android/server/am/AppPermissionTracker.java new file mode 100644 index 000000000000..7f48d527ac77 --- /dev/null +++ b/services/core/java/com/android/server/am/AppPermissionTracker.java @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.Process.SYSTEM_UID; + +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.AppBatteryExemptionTracker.DEFAULT_NAME; +import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX; +import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_PERMISSION; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager.OnPermissionsChangedListener; +import android.content.pm.PackageManagerInternal; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.os.UserHandle; +import android.permission.PermissionManager; +import android.provider.DeviceConfig; +import android.util.ArraySet; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.am.AppPermissionTracker.AppPermissionPolicy; +import com.android.server.pm.permission.PermissionManagerServiceInternal; + +import java.io.PrintWriter; +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.List; + +/** + * The tracker for monitoring selected permission state of apps. + */ +final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy> + implements OnPermissionsChangedListener { + static final String TAG = TAG_WITH_CLASS_NAME ? "AppPermissionTracker" : TAG_AM; + + static final boolean DEBUG_PERMISSION_TRACKER = false; + + private final MyHandler mHandler; + + @GuardedBy("mLock") + private SparseArray<ArraySet<String>> mUidGrantedPermissionsInMonitor = new SparseArray<>(); + + AppPermissionTracker(Context context, AppRestrictionController controller) { + this(context, controller, null, null); + } + + AppPermissionTracker(Context context, AppRestrictionController controller, + Constructor<? extends Injector<AppPermissionPolicy>> injector, Object outerContext) { + super(context, controller, injector, outerContext); + mHandler = new MyHandler(this); + mInjector.setPolicy(new AppPermissionPolicy(mInjector, this)); + } + + @Override + public void onPermissionsChanged(int uid) { + mHandler.obtainMessage(MyHandler.MSG_PERMISSIONS_CHANGED, uid, 0).sendToTarget(); + } + + private void handlePermissionsInit() { + final int[] allUsers = mInjector.getUserManagerInternal().getUserIds(); + final PackageManagerInternal pmi = mInjector.getPackageManagerInternal(); + final PermissionManagerServiceInternal pm = mInjector.getPermissionManagerServiceInternal(); + final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); + for (int userId : allUsers) { + final List<ApplicationInfo> apps = pmi.getInstalledApplications(0, userId, SYSTEM_UID); + if (apps == null) { + continue; + } + synchronized (mLock) { + final SparseArray<ArraySet<String>> uidPerms = mUidGrantedPermissionsInMonitor; + final long now = SystemClock.elapsedRealtime(); + for (int i = 0, size = apps.size(); i < size; i++) { + final ApplicationInfo ai = apps.get(i); + for (String permission : permissions) { + if (pm.checkUidPermission(ai.uid, permission) != PERMISSION_GRANTED) { + continue; + } + ArraySet<String> grantedPermissions = uidPerms.get(ai.uid); + if (grantedPermissions == null) { + grantedPermissions = new ArraySet<String>(); + uidPerms.put(ai.uid, grantedPermissions); + } + grantedPermissions.add(permission); + notifyListenersOnStateChange(ai.uid, DEFAULT_NAME, true, now, + STATE_TYPE_PERMISSION); + } + } + } + } + } + + private void handlePermissionsDestroy() { + synchronized (mLock) { + final SparseArray<ArraySet<String>> uidPerms = mUidGrantedPermissionsInMonitor; + final long now = SystemClock.elapsedRealtime(); + for (int i = 0, size = uidPerms.size(); i < size; i++) { + final int uid = uidPerms.keyAt(i); + final ArraySet<String> grantedPermissions = uidPerms.valueAt(i); + for (int j = 0, numOfPerms = grantedPermissions.size(); j < numOfPerms; j++) { + notifyListenersOnStateChange(uid, DEFAULT_NAME, false, now, + STATE_TYPE_PERMISSION); + } + } + uidPerms.clear(); + } + } + + private void handlePermissionsChanged(int uid) { + final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); + if (permissions != null && permissions.length > 0) { + synchronized (mLock) { + handlePermissionsChangedLocked(uid); + } + } + } + + @GuardedBy("mLock") + private void handlePermissionsChangedLocked(int uid) { + final PermissionManagerServiceInternal pm = mInjector.getPermissionManagerServiceInternal(); + final int index = mUidGrantedPermissionsInMonitor.indexOfKey(uid); + ArraySet<String> grantedPermissions = index >= 0 + ? mUidGrantedPermissionsInMonitor.valueAt(index) : null; + final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); + final long now = SystemClock.elapsedRealtime(); + for (String permission: permissions) { + boolean granted = pm.checkUidPermission(uid, permission) == PERMISSION_GRANTED; + if (DEBUG_PERMISSION_TRACKER) { + Slog.i(TAG, UserHandle.formatUid(uid) + " " + permission + "=" + granted); + } + boolean changed = false; + if (granted) { + if (grantedPermissions == null) { + grantedPermissions = new ArraySet<>(); + mUidGrantedPermissionsInMonitor.put(uid, grantedPermissions); + } + changed = grantedPermissions.add(permission); + } else if (grantedPermissions != null) { + changed = grantedPermissions.remove(permission); + if (grantedPermissions.isEmpty()) { + mUidGrantedPermissionsInMonitor.removeAt(index); + } + } + if (changed) { + notifyListenersOnStateChange(uid, DEFAULT_NAME, granted, now, + STATE_TYPE_PERMISSION); + } + } + } + + private static class MyHandler extends Handler { + static final int MSG_PERMISSIONS_INIT = 0; + static final int MSG_PERMISSIONS_DESTROY = 1; + static final int MSG_PERMISSIONS_CHANGED = 2; + + private @NonNull AppPermissionTracker mTracker; + + MyHandler(@NonNull AppPermissionTracker tracker) { + super(tracker.mBgHandler.getLooper()); + mTracker = tracker; + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_PERMISSIONS_INIT: + mTracker.handlePermissionsInit(); + break; + case MSG_PERMISSIONS_DESTROY: + mTracker.handlePermissionsDestroy(); + break; + case MSG_PERMISSIONS_CHANGED: + mTracker.handlePermissionsChanged(msg.arg1); + break; + } + } + } + + private void onPermissionTrackerEnabled(boolean enabled) { + final PermissionManager pm = mInjector.getPermissionManager(); + if (enabled) { + pm.addOnPermissionsChangeListener(this); + mHandler.obtainMessage(MyHandler.MSG_PERMISSIONS_INIT).sendToTarget(); + } else { + pm.removeOnPermissionsChangeListener(this); + mHandler.obtainMessage(MyHandler.MSG_PERMISSIONS_DESTROY).sendToTarget(); + } + } + + @Override + void dump(PrintWriter pw, String prefix) { + pw.print(prefix); + pw.println("APP PERMISSIONS TRACKER:"); + final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); + final String prefixMore = " " + prefix; + final String prefixMoreMore = " " + prefixMore; + for (String permission : permissions) { + pw.print(prefixMore); + pw.print(permission); + pw.println(':'); + synchronized (mLock) { + final SparseArray<ArraySet<String>> uidPerms = mUidGrantedPermissionsInMonitor; + pw.print(prefixMoreMore); + pw.print('['); + boolean needDelimiter = false; + for (int i = 0, size = uidPerms.size(); i < size; i++) { + if (uidPerms.valueAt(i).contains(permission)) { + if (needDelimiter) { + pw.print(','); + } + needDelimiter = true; + pw.print(UserHandle.formatUid(uidPerms.keyAt(i))); + } + } + pw.println(']'); + } + } + super.dump(pw, prefix); + } + + static final class AppPermissionPolicy extends BaseAppStatePolicy<AppPermissionTracker> { + /** + * Whether or not we should enable the monitoring on app permissions. + */ + static final String KEY_BG_PERMISSION_MONITOR_ENABLED = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "permission_monitor_enabled"; + + /** + * The names of the permissions we're monitoring its changes. + */ + static final String KEY_BG_PERMISSIONS_IN_MONITOR = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "permission_in_monitor"; + + /** + * Default value to {@link #mTrackerEnabled}. + */ + static final boolean DEFAULT_BG_PERMISSION_MONITOR_ENABLED = true; + + /** + * Default value to {@link #mBgPermissionsInMonitor}. + */ + static final String[] DEFAULT_BG_PERMISSIONS_IN_MONITOR = new String[] { + ACCESS_FINE_LOCATION, + }; + + /** + * @see #KEY_BG_PERMISSIONS_IN_MONITOR. + */ + volatile String[] mBgPermissionsInMonitor = DEFAULT_BG_PERMISSIONS_IN_MONITOR; + + AppPermissionPolicy(@NonNull Injector injector, @NonNull AppPermissionTracker tracker) { + super(injector, tracker, KEY_BG_PERMISSION_MONITOR_ENABLED, + DEFAULT_BG_PERMISSION_MONITOR_ENABLED); + } + + @Override + public void onSystemReady() { + super.onSystemReady(); + updateBgPermissionsInMonitor(); + } + + @Override + public void onPropertiesChanged(String name) { + switch (name) { + case KEY_BG_PERMISSIONS_IN_MONITOR: + updateBgPermissionsInMonitor(); + break; + default: + super.onPropertiesChanged(name); + break; + } + } + + String[] getBgPermissionsInMonitor() { + return mBgPermissionsInMonitor; + } + + private void updateBgPermissionsInMonitor() { + final String config = DeviceConfig.getString( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_BG_PERMISSIONS_IN_MONITOR, + null); + mBgPermissionsInMonitor = config != null + ? config.split(",") : DEFAULT_BG_PERMISSIONS_IN_MONITOR; + } + + @Override + public void onTrackerEnabled(boolean enabled) { + mTracker.onPermissionTrackerEnabled(enabled); + } + + @Override + void dump(PrintWriter pw, String prefix) { + pw.print(prefix); + pw.println("APP PERMISSION TRACKER POLICY SETTINGS:"); + prefix = " " + prefix; + super.dump(pw, prefix); + pw.print(prefix); + pw.print(KEY_BG_PERMISSIONS_IN_MONITOR); + pw.print('='); + pw.println(Arrays.toString(mBgPermissionsInMonitor)); + } + } +} diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index 8cff13e88fd2..1129c19bce87 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -69,6 +69,7 @@ import static android.os.PowerExemptionManager.REASON_ROLE_EMERGENCY; import static android.os.PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED; import static android.os.PowerExemptionManager.REASON_SYSTEM_MODULE; import static android.os.PowerExemptionManager.REASON_SYSTEM_UID; +import static android.os.PowerExemptionManager.reasonCodeToString; import static android.os.Process.SYSTEM_UID; import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS; @@ -142,6 +143,7 @@ import com.android.internal.util.function.TriConsumer; import com.android.server.AppStateTracker; import com.android.server.LocalServices; import com.android.server.SystemConfig; +import com.android.server.am.AppBatteryTracker.ImmutableBatteryUsage; import com.android.server.apphibernation.AppHibernationManagerInternal; import com.android.server.pm.UserManagerInternal; import com.android.server.usage.AppStandbyInternal; @@ -156,6 +158,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; +import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Consumer; @@ -328,6 +331,8 @@ public final class AppRestrictionController { } } } + pw.print(" effectiveExemption="); + pw.print(reasonCodeToString(getBackgroundRestrictionExemptionReason(mUid))); } String getPackageName() { @@ -546,17 +551,50 @@ public final class AppRestrictionController { static final String KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL = DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "abusive_notification_minimal_interval"; + /** + * The behavior for an app with a FGS and its notification is still showing, when the system + * detects it's abusive and should be put into bg restricted level. {@code true} - we'll + * show the prompt to user, {@code false} - we'll not show it. + */ + static final String KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "prompt_fgs_with_noti_to_bg_restricted"; + + /** + * The list of packages to be exempted from all these background restrictions. + */ + static final String KEY_BG_RESTRICTION_EXEMPTED_PACKAGES = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "restriction_exempted_packages"; + static final boolean DEFAULT_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION = false; static final long DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS = 24 * 60 * 60 * 1000; + /** + * Default value to {@link #mBgPromptFgsWithNotiToBgRestricted}. + */ + final boolean mDefaultBgPromptFgsWithNotiToBgRestricted; + volatile boolean mBgAutoRestrictedBucket; volatile boolean mRestrictedBucketEnabled; volatile long mBgNotificationMinIntervalMs; - ConstantsObserver(Handler handler) { + /** + * @see #KEY_BG_RESTRICTION_EXEMPTED_PACKAGES. + * + *<p> Mutations on them would result in copy-on-write.</p> + */ + volatile Set<String> mBgRestrictionExemptedPackages = Collections.emptySet(); + + /** + * @see #KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED. + */ + volatile boolean mBgPromptFgsWithNotiToBgRestricted; + + ConstantsObserver(Handler handler, Context context) { super(handler); + mDefaultBgPromptFgsWithNotiToBgRestricted = context.getResources().getBoolean( + com.android.internal.R.bool.config_bg_prompt_fgs_with_noti_to_bg_restricted); } @Override @@ -572,6 +610,12 @@ public final class AppRestrictionController { case KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL: updateBgAbusiveNotificationMinimalInterval(); break; + case KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED: + updateBgPromptFgsWithNotiToBgRestricted(); + break; + case KEY_BG_RESTRICTION_EXEMPTED_PACKAGES: + updateBgRestrictionExemptedPackages(); + break; } AppRestrictionController.this.onPropertiesChanged(name); } @@ -603,6 +647,8 @@ public final class AppRestrictionController { void updateDeviceConfig() { updateBgAutoRestrictedBucketChanged(); updateBgAbusiveNotificationMinimalInterval(); + updateBgPromptFgsWithNotiToBgRestricted(); + updateBgRestrictionExemptedPackages(); } private void updateBgAutoRestrictedBucketChanged() { @@ -622,6 +668,53 @@ public final class AppRestrictionController { KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL, DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS); } + + private void updateBgPromptFgsWithNotiToBgRestricted() { + mBgPromptFgsWithNotiToBgRestricted = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED, + mDefaultBgPromptFgsWithNotiToBgRestricted); + } + + private void updateBgRestrictionExemptedPackages() { + final String settings = DeviceConfig.getString( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_BG_RESTRICTION_EXEMPTED_PACKAGES, + null); + if (settings == null) { + mBgRestrictionExemptedPackages = Collections.emptySet(); + return; + } + final String[] settingsList = settings.split(","); + final ArraySet<String> packages = new ArraySet<>(); + for (String pkg : settingsList) { + packages.add(pkg); + } + mBgRestrictionExemptedPackages = Collections.unmodifiableSet(packages); + } + + void dump(PrintWriter pw, String prefix) { + pw.print(prefix); + pw.println("BACKGROUND RESTRICTION POLICY SETTINGS:"); + final String indent = " "; + prefix = indent + prefix; + pw.print(prefix); + pw.print(KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION); + pw.print('='); + pw.println(mBgAutoRestrictedBucket); + pw.print(prefix); + pw.print(KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL); + pw.print('='); + pw.println(mBgNotificationMinIntervalMs); + pw.print(prefix); + pw.print(KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED); + pw.print('='); + pw.println(mBgPromptFgsWithNotiToBgRestricted); + pw.print(prefix); + pw.print(KEY_BG_RESTRICTION_EXEMPTED_PACKAGES); + pw.print('='); + pw.println(mBgRestrictionExemptedPackages.toString()); + } } private final ConstantsObserver mConstantsObserver; @@ -703,7 +796,7 @@ public final class AppRestrictionController { mBgHandlerThread.start(); mBgHandler = new BgHandler(mBgHandlerThread.getLooper(), injector); mBgExecutor = new HandlerExecutor(mBgHandler); - mConstantsObserver = new ConstantsObserver(mBgHandler); + mConstantsObserver = new ConstantsObserver(mBgHandler, mContext); mNotificationHelper = new NotificationHelper(this); injector.initAppStateTrackers(this); } @@ -1073,18 +1166,33 @@ public final class AppRestrictionController { } /** + * @return If the given package/uid has a foreground service notification or not. + */ + boolean hasForegroundServiceNotifications(String packageName, int uid) { + return mInjector.getAppFGSTracker().hasForegroundServiceNotifications(packageName, uid); + } + + /** + * @return If the given uid has a foreground service notification or not. + */ + boolean hasForegroundServiceNotifications(int uid) { + return mInjector.getAppFGSTracker().hasForegroundServiceNotifications(uid); + } + + /** * @return The to-be-exempted battery usage of the given UID in the given duration; it could * be considered as "exempted" due to various use cases, i.e. media playback. */ - double getUidBatteryExemptedUsageSince(int uid, long since, long now) { + ImmutableBatteryUsage getUidBatteryExemptedUsageSince(int uid, long since, long now, + int types) { return mInjector.getAppBatteryExemptionTracker() - .getUidBatteryExemptedUsageSince(uid, since, now); + .getUidBatteryExemptedUsageSince(uid, since, now, types); } /** * @return The total battery usage of the given UID since the system boots. */ - double getUidBatteryUsage(int uid) { + @NonNull ImmutableBatteryUsage getUidBatteryUsage(int uid) { return mInjector.getUidBatteryUsageProvider().getUidBatteryUsage(uid); } @@ -1092,16 +1200,19 @@ public final class AppRestrictionController { /** * @return The total battery usage of the given UID since the system boots. */ - double getUidBatteryUsage(int uid); + @NonNull ImmutableBatteryUsage getUidBatteryUsage(int uid); } void dump(PrintWriter pw, String prefix) { pw.print(prefix); - pw.println("BACKGROUND RESTRICTION LEVEL SETTINGS"); + pw.println("APP BACKGROUND RESTRICTIONS"); prefix = " " + prefix; + pw.print(prefix); + pw.println("BACKGROUND RESTRICTION LEVEL SETTINGS"); synchronized (mLock) { - mRestrictionSettings.dumpLocked(pw, prefix); + mRestrictionSettings.dumpLocked(pw, " " + prefix); } + mConstantsObserver.dump(pw, " " + prefix); for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) { pw.println(); mAppStateTrackers.get(i).dump(pw, prefix); @@ -1365,8 +1476,21 @@ public final class AppRestrictionController { intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, null, UserHandle.of(UserHandle.getUserId(uid))); Notification.Action[] actions = null; - if (ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER - && mBgController.hasForegroundServices(packageName, uid)) { + final boolean hasForegroundServices = + mBgController.hasForegroundServices(packageName, uid); + final boolean hasForegroundServiceNotifications = + mBgController.hasForegroundServiceNotifications(packageName, uid); + if (!mBgController.mConstantsObserver.mBgPromptFgsWithNotiToBgRestricted) { + // We're not going to prompt the user if the FGS is active and its notification + // is still showing (not dismissed/silenced/denied). + if (hasForegroundServices && hasForegroundServiceNotifications) { + if (DEBUG_BG_RESTRICTION_CONTROLLER) { + Slog.i(TAG, "Not requesting bg-restriction due to FGS with notification"); + } + return; + } + } + if (ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER && hasForegroundServices) { final Intent trampoline = new Intent(ACTION_FGS_MANAGER_TRAMPOLINE); trampoline.setPackage("android"); trampoline.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); @@ -1645,6 +1769,8 @@ public final class AppRestrictionController { return REASON_CARRIER_PRIVILEGED_APP; } else if (isExemptedFromSysConfig(pkg)) { return REASON_SYSTEM_ALLOW_LISTED; + } else if (mConstantsObserver.mBgRestrictionExemptedPackages.contains(pkg)) { + return REASON_ALLOWLISTED_PACKAGE; } } } @@ -1863,6 +1989,7 @@ public final class AppRestrictionController { private AppBatteryExemptionTracker mAppBatteryExemptionTracker; private AppFGSTracker mAppFGSTracker; private AppMediaSessionTracker mAppMediaSessionTracker; + private AppPermissionTracker mAppPermissionTracker; private TelephonyManager mTelephonyManager; Injector(Context context) { @@ -1879,10 +2006,12 @@ public final class AppRestrictionController { mAppBatteryExemptionTracker = new AppBatteryExemptionTracker(mContext, controller); mAppFGSTracker = new AppFGSTracker(mContext, controller); mAppMediaSessionTracker = new AppMediaSessionTracker(mContext, controller); + mAppPermissionTracker = new AppPermissionTracker(mContext, controller); controller.mAppStateTrackers.add(mAppBatteryTracker); controller.mAppStateTrackers.add(mAppBatteryExemptionTracker); controller.mAppStateTrackers.add(mAppFGSTracker); controller.mAppStateTrackers.add(mAppMediaSessionTracker); + controller.mAppStateTrackers.add(mAppPermissionTracker); controller.mAppStateTrackers.add(new AppBroadcastEventsTracker(mContext, controller)); controller.mAppStateTrackers.add(new AppBindServiceEventsTracker(mContext, controller)); } @@ -1990,6 +2119,10 @@ public final class AppRestrictionController { return mAppBatteryExemptionTracker; } + AppPermissionTracker getAppPermissionTracker() { + return mAppPermissionTracker; + } + String getPackageName(int pid) { final ActivityManagerService am = getActivityManagerService(); final ProcessRecord app; diff --git a/services/core/java/com/android/server/am/BaseAppStateDurationsTracker.java b/services/core/java/com/android/server/am/BaseAppStateDurationsTracker.java index cc89e847023d..80a219d44668 100644 --- a/services/core/java/com/android/server/am/BaseAppStateDurationsTracker.java +++ b/services/core/java/com/android/server/am/BaseAppStateDurationsTracker.java @@ -19,7 +19,6 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; -import android.annotation.NonNull; import android.content.Context; import android.os.SystemClock; import android.util.SparseArray; @@ -32,7 +31,6 @@ import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent; import java.io.PrintWriter; import java.lang.reflect.Constructor; -import java.util.ArrayList; import java.util.LinkedList; /** @@ -43,20 +41,9 @@ abstract class BaseAppStateDurationsTracker extends BaseAppStateEventsTracker<T, U> { static final boolean DEBUG_BASE_APP_STATE_DURATION_TRACKER = false; - static final int EVENT_TYPE_MEDIA_SESSION = 0; - static final int EVENT_TYPE_FGS_MEDIA_PLAYBACK = 1; - static final int EVENT_TYPE_FGS_LOCATION = 2; - static final int EVENT_NUM = 3; - - final ArrayList<EventListener> mEventListeners = new ArrayList<>(); - @GuardedBy("mLock") final SparseArray<UidStateDurations> mUidStateDurations = new SparseArray<>(); - interface EventListener { - void onNewEvent(int uid, String packageName, boolean start, long now, int eventType); - } - BaseAppStateDurationsTracker(Context context, AppRestrictionController controller, Constructor<? extends Injector<T>> injector, Object outerContext) { super(context, controller, injector, outerContext); @@ -104,21 +91,6 @@ abstract class BaseAppStateDurationsTracker mUidStateDurations.remove(uid); } - void registerEventListener(@NonNull EventListener listener) { - synchronized (mLock) { - mEventListeners.add(listener); - } - } - - void notifyListenersOnEvent(int uid, String packageName, - boolean start, long now, int eventType) { - synchronized (mLock) { - for (int i = 0, size = mEventListeners.size(); i < size; i++) { - mEventListeners.get(i).onNewEvent(uid, packageName, start, now, eventType); - } - } - } - long getTotalDurations(String packageName, int uid, long now, int index, boolean bgOnly) { synchronized (mLock) { final U durations = mPkgEvents.get(uid, packageName); diff --git a/services/core/java/com/android/server/am/BaseAppStateEventsTracker.java b/services/core/java/com/android/server/am/BaseAppStateEventsTracker.java index 3e1bcae76719..c6900b279c45 100644 --- a/services/core/java/com/android/server/am/BaseAppStateEventsTracker.java +++ b/services/core/java/com/android/server/am/BaseAppStateEventsTracker.java @@ -184,9 +184,13 @@ abstract class BaseAppStateEventsTracker } } } + dumpOthers(pw, prefix); policy.dump(pw, prefix); } + void dumpOthers(PrintWriter pw, String prefix) { + } + @GuardedBy("mLock") void dumpEventHeaderLocked(PrintWriter pw, String prefix, String packageName, int uid, U events, T policy) { diff --git a/services/core/java/com/android/server/am/BaseAppStateTracker.java b/services/core/java/com/android/server/am/BaseAppStateTracker.java index 2846f6c15165..482d69751d70 100644 --- a/services/core/java/com/android/server/am/BaseAppStateTracker.java +++ b/services/core/java/com/android/server/am/BaseAppStateTracker.java @@ -28,19 +28,23 @@ import android.app.AppOpsManager; import android.app.role.RoleManager; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.media.session.MediaSessionManager; import android.os.BatteryManagerInternal; import android.os.BatteryStatsInternal; import android.os.Handler; +import android.permission.PermissionManager; import android.util.Slog; import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; +import com.android.server.notification.NotificationManagerInternal; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.permission.PermissionManagerServiceInternal; import java.io.PrintWriter; import java.lang.reflect.Constructor; +import java.util.ArrayList; /** * Base class to track certain state of the app, could be used to determine the restriction level. @@ -54,11 +58,27 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> { static final long ONE_HOUR = 60 * ONE_MINUTE; static final long ONE_DAY = 24 * ONE_HOUR; + static final int STATE_TYPE_MEDIA_SESSION = 1; + static final int STATE_TYPE_FGS_MEDIA_PLAYBACK = 1 << 1; + static final int STATE_TYPE_FGS_LOCATION = 1 << 2; + static final int STATE_TYPE_PERMISSION = 1 << 3; + static final int STATE_TYPE_NUM = 4; + + static final int STATE_TYPE_INDEX_MEDIA_SESSION = 0; + static final int STATE_TYPE_INDEX_FGS_MEDIA_PLAYBACK = 1; + static final int STATE_TYPE_INDEX_FGS_LOCATION = 2; + static final int STATE_TYPE_INDEX_PERMISSION = 3; + protected final AppRestrictionController mAppRestrictionController; protected final Injector<T> mInjector; protected final Context mContext; protected final Handler mBgHandler; protected final Object mLock; + protected final ArrayList<StateListener> mStateListeners = new ArrayList<>(); + + interface StateListener { + void onStateChange(int uid, String packageName, boolean start, long now, int stateType); + } BaseAppStateTracker(Context context, AppRestrictionController controller, @Nullable Constructor<? extends Injector<T>> injector, Object outerContext) { @@ -79,6 +99,60 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> { } } + static int stateTypeToIndex(int stateType) { + return Integer.numberOfTrailingZeros(stateType); + } + + static int stateIndexToType(int stateTypeIndex) { + return 1 << stateTypeIndex; + } + + static String stateTypesToString(int stateTypes) { + final StringBuilder sb = new StringBuilder("["); + boolean needDelimiter = false; + for (int stateType = Integer.highestOneBit(stateTypes); stateType != 0; + stateType = Integer.highestOneBit(stateTypes)) { + if (needDelimiter) { + sb.append('|'); + } + needDelimiter = true; + switch (stateType) { + case STATE_TYPE_MEDIA_SESSION: + sb.append("MEDIA_SESSION"); + break; + case STATE_TYPE_FGS_MEDIA_PLAYBACK: + sb.append("FGS_MEDIA_PLAYBACK"); + break; + case STATE_TYPE_FGS_LOCATION: + sb.append("FGS_LOCATION"); + break; + case STATE_TYPE_PERMISSION: + sb.append("PERMISSION"); + break; + default: + return "[UNKNOWN(" + Integer.toHexString(stateTypes) + ")]"; + } + stateTypes &= ~stateType; + } + sb.append("]"); + return sb.toString(); + } + + void registerStateListener(@NonNull StateListener listener) { + synchronized (mLock) { + mStateListeners.add(listener); + } + } + + void notifyListenersOnStateChange(int uid, String packageName, + boolean start, long now, int stateType) { + synchronized (mLock) { + for (int i = 0, size = mStateListeners.size(); i < size; i++) { + mStateListeners.get(i).onStateChange(uid, packageName, start, now, stateType); + } + } + } + /** * Return the policy holder of this tracker. */ @@ -179,10 +253,13 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> { DeviceIdleInternal mDeviceIdleInternal; UserManagerInternal mUserManagerInternal; PackageManager mPackageManager; + PackageManagerInternal mPackageManagerInternal; + PermissionManager mPermissionManager; PermissionManagerServiceInternal mPermissionManagerServiceInternal; AppOpsManager mAppOpsManager; MediaSessionManager mMediaSessionManager; RoleManager mRoleManager; + NotificationManagerInternal mNotificationManagerInternal; void setPolicy(T policy) { mAppStatePolicy = policy; @@ -194,13 +271,17 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> { mBatteryStatsInternal = LocalServices.getService(BatteryStatsInternal.class); mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class); mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mPermissionManagerServiceInternal = LocalServices.getService( PermissionManagerServiceInternal.class); final Context context = mAppStatePolicy.mTracker.mContext; mPackageManager = context.getPackageManager(); mAppOpsManager = context.getSystemService(AppOpsManager.class); mMediaSessionManager = context.getSystemService(MediaSessionManager.class); + mPermissionManager = context.getSystemService(PermissionManager.class); mRoleManager = context.getSystemService(RoleManager.class); + mNotificationManagerInternal = LocalServices.getService( + NotificationManagerInternal.class); getPolicy().onSystemReady(); } @@ -240,6 +321,14 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> { return mPackageManager; } + PackageManagerInternal getPackageManagerInternal() { + return mPackageManagerInternal; + } + + PermissionManager getPermissionManager() { + return mPermissionManager; + } + PermissionManagerServiceInternal getPermissionManagerServiceInternal() { return mPermissionManagerServiceInternal; } @@ -259,5 +348,9 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> { RoleManager getRoleManager() { return mRoleManager; } + + NotificationManagerInternal getNotificationManagerInternal() { + return mNotificationManagerInternal; + } } } diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index e580327631eb..2ebe0b48296b 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -16,6 +16,10 @@ package com.android.server.am; +import android.annotation.IntDef; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; +import android.compat.annotation.Overridable; import android.content.ContentResolver; import android.database.ContentObserver; import android.os.Build; @@ -26,6 +30,8 @@ import android.util.Slog; import android.util.TimeUtils; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Tunable parameters for broadcast dispatch policy @@ -51,6 +57,47 @@ public class BroadcastConstants { private static final long DEFAULT_ALLOW_BG_ACTIVITY_START_TIMEOUT = 10_000 * Build.HW_TIMEOUT_MULTIPLIER; + /** + * Defer LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts until the first time any process in + * the UID is started. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.S_V2) + @Overridable + static final long DEFER_BOOT_COMPLETED_BROADCAST_CHANGE_ID = 203704822L; + + /** + * Do not defer LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts. + */ + public static final int DEFER_BOOT_COMPLETED_BROADCAST_NONE = 0; + /** + * Defer all LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts. + */ + public static final int DEFER_BOOT_COMPLETED_BROADCAST_ALL = 1 << 0; + /** + * Defer LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts if app is background restricted. + */ + public static final int DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY = 1 << 1; + /** + * Defer LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts if app's targetSdkVersion is T + * and above. + */ + public static final int DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY = 1 << 2; + + /** + * The list of DEFER_BOOT_COMPLETED_BROADCAST types. + * If multiple flags are selected, all conditions must be met to defer the broadcast. + * @hide + */ + @IntDef(flag = true, prefix = { "DEFER_BOOT_COMPLETED_BROADCAST_" }, value = { + DEFER_BOOT_COMPLETED_BROADCAST_NONE, + DEFER_BOOT_COMPLETED_BROADCAST_ALL, + DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY, + DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeferBootCompletedBroadcastType {} + // All time constants are in milliseconds // Timeout period for this broadcast queue diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java index 8afd52e82e5e..01d8109a82a0 100644 --- a/services/core/java/com/android/server/am/BroadcastDispatcher.java +++ b/services/core/java/com/android/server/am/BroadcastDispatcher.java @@ -18,14 +18,21 @@ package com.android.server.am; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL; +import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE; +import android.annotation.Nullable; import android.content.Intent; +import android.content.pm.ResolveInfo; import android.os.Handler; import android.os.SystemClock; +import android.os.UserHandle; import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.AlarmManagerInternal; import com.android.server.LocalServices; @@ -232,6 +239,253 @@ public class BroadcastDispatcher { // Next outbound broadcast, established by getNextBroadcastLocked() private BroadcastRecord mCurrentBroadcast; + // Map userId to its deferred boot completed broadcasts. + private SparseArray<DeferredBootCompletedBroadcastPerUser> mUser2Deferred = new SparseArray<>(); + + /** + * Deferred LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts that is sent to a user. + */ + static class DeferredBootCompletedBroadcastPerUser { + private int mUserId; + // UID that has process started at least once, ready to execute LOCKED_BOOT_COMPLETED + // receivers. + @VisibleForTesting + SparseBooleanArray mUidReadyForLockedBootCompletedBroadcast = new SparseBooleanArray(); + // UID that has process started at least once, ready to execute BOOT_COMPLETED receivers. + @VisibleForTesting + SparseBooleanArray mUidReadyForBootCompletedBroadcast = new SparseBooleanArray(); + // Map UID to deferred LOCKED_BOOT_COMPLETED broadcasts. + // LOCKED_BOOT_COMPLETED broadcast receivers are deferred until the first time the uid has + // any process started. + @VisibleForTesting + SparseArray<BroadcastRecord> mDeferredLockedBootCompletedBroadcasts = new SparseArray<>(); + // is the LOCKED_BOOT_COMPLETED broadcast received by the user. + @VisibleForTesting + boolean mLockedBootCompletedBroadcastReceived; + // Map UID to deferred BOOT_COMPLETED broadcasts. + // BOOT_COMPLETED broadcast receivers are deferred until the first time the uid has any + // process started. + @VisibleForTesting + SparseArray<BroadcastRecord> mDeferredBootCompletedBroadcasts = new SparseArray<>(); + // is the BOOT_COMPLETED broadcast received by the user. + @VisibleForTesting + boolean mBootCompletedBroadcastReceived; + + DeferredBootCompletedBroadcastPerUser(int userId) { + this.mUserId = userId; + } + + public void updateUidReady(int uid) { + if (!mLockedBootCompletedBroadcastReceived + || mDeferredLockedBootCompletedBroadcasts.size() != 0) { + mUidReadyForLockedBootCompletedBroadcast.put(uid, true); + } + if (!mBootCompletedBroadcastReceived + || mDeferredBootCompletedBroadcasts.size() != 0) { + mUidReadyForBootCompletedBroadcast.put(uid, true); + } + } + + public void enqueueBootCompletedBroadcasts(String action, + SparseArray<BroadcastRecord> deferred) { + if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) { + enqueueBootCompletedBroadcasts(deferred, mDeferredLockedBootCompletedBroadcasts, + mUidReadyForLockedBootCompletedBroadcast); + mLockedBootCompletedBroadcastReceived = true; + if (DEBUG_BROADCAST_DEFERRAL) { + dumpBootCompletedBroadcastRecord(mDeferredLockedBootCompletedBroadcasts); + } + } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { + enqueueBootCompletedBroadcasts(deferred, mDeferredBootCompletedBroadcasts, + mUidReadyForBootCompletedBroadcast); + mBootCompletedBroadcastReceived = true; + if (DEBUG_BROADCAST_DEFERRAL) { + dumpBootCompletedBroadcastRecord(mDeferredBootCompletedBroadcasts); + } + } + } + + /** + * Merge UID to BroadcastRecord map into {@link #mDeferredBootCompletedBroadcasts} or + * {@link #mDeferredLockedBootCompletedBroadcasts} + * @param from the UID to BroadcastRecord map. + * @param into The UID to list of BroadcastRecord map. + */ + private void enqueueBootCompletedBroadcasts(SparseArray<BroadcastRecord> from, + SparseArray<BroadcastRecord> into, SparseBooleanArray uidReadyForReceiver) { + // remove unwanted uids from uidReadyForReceiver. + for (int i = uidReadyForReceiver.size() - 1; i >= 0; i--) { + if (from.indexOfKey(uidReadyForReceiver.keyAt(i)) < 0) { + uidReadyForReceiver.removeAt(i); + } + } + for (int i = 0, size = from.size(); i < size; i++) { + final int uid = from.keyAt(i); + into.put(uid, from.valueAt(i)); + if (uidReadyForReceiver.indexOfKey(uid) < 0) { + // uid is wanted but not ready. + uidReadyForReceiver.put(uid, false); + } + } + } + + public @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast( + boolean isAllUidReady) { + BroadcastRecord next = dequeueDeferredBootCompletedBroadcast( + mDeferredLockedBootCompletedBroadcasts, + mUidReadyForLockedBootCompletedBroadcast, isAllUidReady); + if (next == null) { + next = dequeueDeferredBootCompletedBroadcast(mDeferredBootCompletedBroadcasts, + mUidReadyForBootCompletedBroadcast, isAllUidReady); + } + return next; + } + + private @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast( + SparseArray<BroadcastRecord> uid2br, SparseBooleanArray uidReadyForReceiver, + boolean isAllUidReady) { + for (int i = 0, size = uid2br.size(); i < size; i++) { + final int uid = uid2br.keyAt(i); + if (isAllUidReady || uidReadyForReceiver.get(uid)) { + final BroadcastRecord br = uid2br.valueAt(i); + if (DEBUG_BROADCAST_DEFERRAL) { + final Object receiver = br.receivers.get(0); + if (receiver instanceof BroadcastFilter) { + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "getDeferredBootCompletedBroadcast uid:" + uid + + " BroadcastFilter:" + (BroadcastFilter) receiver + + " broadcast:" + br.intent.getAction()); + } + } else /* if (receiver instanceof ResolveInfo) */ { + ResolveInfo info = (ResolveInfo) receiver; + String packageName = info.activityInfo.applicationInfo.packageName; + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "getDeferredBootCompletedBroadcast uid:" + uid + + " packageName:" + packageName + + " broadcast:" + br.intent.getAction()); + } + } + } + // remove the BroadcastRecord. + uid2br.removeAt(i); + if (uid2br.size() == 0) { + // All deferred receivers are executed, do not need uidReadyForReceiver + // any more. + uidReadyForReceiver.clear(); + } + return br; + } + } + return null; + } + + private @Nullable SparseArray<BroadcastRecord> getDeferredList(String action) { + SparseArray<BroadcastRecord> brs = null; + if (action.equals(Intent.ACTION_LOCKED_BOOT_COMPLETED)) { + brs = mDeferredLockedBootCompletedBroadcasts; + } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { + brs = mDeferredBootCompletedBroadcasts; + } + return brs; + } + + /** + * Return the total number of UIDs in all BroadcastRecord in + * {@link #mDeferredBootCompletedBroadcasts} or + * {@link #mDeferredLockedBootCompletedBroadcasts} + */ + private int getBootCompletedBroadcastsUidsSize(String action) { + SparseArray<BroadcastRecord> brs = getDeferredList(action); + return brs != null ? brs.size() : 0; + } + + /** + * Return the total number of receivers in all BroadcastRecord in + * {@link #mDeferredBootCompletedBroadcasts} or + * {@link #mDeferredLockedBootCompletedBroadcasts} + */ + private int getBootCompletedBroadcastsReceiversSize(String action) { + SparseArray<BroadcastRecord> brs = getDeferredList(action); + if (brs == null) { + return 0; + } + int size = 0; + for (int i = 0, s = brs.size(); i < s; i++) { + size += brs.valueAt(i).receivers.size(); + } + return size; + } + + public void dump(Dumper dumper, String action) { + SparseArray<BroadcastRecord> brs = getDeferredList(action); + if (brs == null) { + return; + } + for (int i = 0, size = brs.size(); i < size; i++) { + dumper.dump(brs.valueAt(i)); + } + } + + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + for (int i = 0, size = mDeferredLockedBootCompletedBroadcasts.size(); i < size; i++) { + mDeferredLockedBootCompletedBroadcasts.valueAt(i).dumpDebug(proto, fieldId); + } + for (int i = 0, size = mDeferredBootCompletedBroadcasts.size(); i < size; i++) { + mDeferredBootCompletedBroadcasts.valueAt(i).dumpDebug(proto, fieldId); + } + } + + private void dumpBootCompletedBroadcastRecord(SparseArray<BroadcastRecord> brs) { + for (int i = 0, size = brs.size(); i < size; i++) { + final Object receiver = brs.valueAt(i).receivers.get(0); + String packageName = null; + if (receiver instanceof BroadcastFilter) { + BroadcastFilter recv = (BroadcastFilter) receiver; + packageName = recv.receiverList.app.processName; + } else /* if (receiver instanceof ResolveInfo) */ { + ResolveInfo info = (ResolveInfo) receiver; + packageName = info.activityInfo.applicationInfo.packageName; + } + Slog.i(TAG, "uid:" + brs.keyAt(i) + + " packageName:" + packageName + + " receivers:" + brs.valueAt(i).receivers.size()); + } + } + } + + private DeferredBootCompletedBroadcastPerUser getDeferredPerUser(int userId) { + if (mUser2Deferred.contains(userId)) { + return mUser2Deferred.get(userId); + } else { + final DeferredBootCompletedBroadcastPerUser temp = + new DeferredBootCompletedBroadcastPerUser(userId); + mUser2Deferred.put(userId, temp); + return temp; + } + } + + /** + * ActivityManagerService.attachApplication() call this method to notify that the UID is ready + * to accept deferred LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts. + * @param uid + */ + public void updateUidReadyForBootCompletedBroadcastLocked(int uid) { + getDeferredPerUser(UserHandle.getUserId(uid)).updateUidReady(uid); + } + + private @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast() { + final boolean isAllUidReady = (mQueue.mService.mConstants.mDeferBootCompletedBroadcast + == DEFER_BOOT_COMPLETED_BROADCAST_NONE); + BroadcastRecord next = null; + for (int i = 0, size = mUser2Deferred.size(); i < size; i++) { + next = mUser2Deferred.valueAt(i).dequeueDeferredBootCompletedBroadcast(isAllUidReady); + if (next != null) { + break; + } + } + return next; + } + /** * Constructed & sharing a lock with its associated BroadcastQueue instance */ @@ -261,7 +515,9 @@ public class BroadcastDispatcher { return mCurrentBroadcast == null && mOrderedBroadcasts.isEmpty() && isDeferralsListEmpty(mDeferredBroadcasts) - && isDeferralsListEmpty(mAlarmBroadcasts); + && isDeferralsListEmpty(mAlarmBroadcasts) + && getBootCompletedBroadcastsUidsSize(Intent.ACTION_LOCKED_BOOT_COMPLETED) == 0 + && getBootCompletedBroadcastsUidsSize(Intent.ACTION_BOOT_COMPLETED) == 0; } } @@ -301,14 +557,78 @@ public class BroadcastDispatcher { sb.append(n); sb.append(" deferred"); } + n = getBootCompletedBroadcastsUidsSize(Intent.ACTION_LOCKED_BOOT_COMPLETED); + if (n > 0) { + sb.append(", "); + sb.append(n); + sb.append(" deferred LOCKED_BOOT_COMPLETED/"); + sb.append(getBootCompletedBroadcastsReceiversSize(Intent.ACTION_LOCKED_BOOT_COMPLETED)); + sb.append(" receivers"); + } + + n = getBootCompletedBroadcastsUidsSize(Intent.ACTION_BOOT_COMPLETED); + if (n > 0) { + sb.append(", "); + sb.append(n); + sb.append(" deferred BOOT_COMPLETED/"); + sb.append(getBootCompletedBroadcastsReceiversSize(Intent.ACTION_BOOT_COMPLETED)); + sb.append(" receivers"); + } return sb.toString(); } // ---------------------------------- // BroadcastQueue operation support - void enqueueOrderedBroadcastLocked(BroadcastRecord r) { - mOrderedBroadcasts.add(r); + if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(r.intent.getAction())) { + // Create one BroadcastRecord for each UID that can be deferred. + final SparseArray<BroadcastRecord> deferred = + r.splitDeferredBootCompletedBroadcastLocked(mQueue.mService.mInternal, + mQueue.mService.mConstants.mDeferBootCompletedBroadcast); + getDeferredPerUser(r.userId).enqueueBootCompletedBroadcasts( + Intent.ACTION_LOCKED_BOOT_COMPLETED, deferred); + if (!r.receivers.isEmpty()) { + // The non-deferred receivers. + mOrderedBroadcasts.add(r); + return; + } + } else if (Intent.ACTION_BOOT_COMPLETED.equals(r.intent.getAction())) { + // Create one BroadcastRecord for each UID that can be deferred. + final SparseArray<BroadcastRecord> deferred = + r.splitDeferredBootCompletedBroadcastLocked(mQueue.mService.mInternal, + mQueue.mService.mConstants.mDeferBootCompletedBroadcast); + getDeferredPerUser(r.userId).enqueueBootCompletedBroadcasts( + Intent.ACTION_BOOT_COMPLETED, deferred); + if (!r.receivers.isEmpty()) { + // The non-deferred receivers. + mOrderedBroadcasts.add(r); + return; + } + } else { + mOrderedBroadcasts.add(r); + } + } + + /** + * Return the total number of UIDs in all deferred boot completed BroadcastRecord. + */ + private int getBootCompletedBroadcastsUidsSize(String action) { + int size = 0; + for (int i = 0, s = mUser2Deferred.size(); i < s; i++) { + size += mUser2Deferred.valueAt(i).getBootCompletedBroadcastsUidsSize(action); + } + return size; + } + + /** + * Return the total number of receivers in all deferred boot completed BroadcastRecord. + */ + private int getBootCompletedBroadcastsReceiversSize(String action) { + int size = 0; + for (int i = 0, s = mUser2Deferred.size(); i < s; i++) { + size += mUser2Deferred.valueAt(i).getBootCompletedBroadcastsReceiversSize(action); + } + return size; } // Returns the now-replaced broadcast record, or null if none @@ -369,6 +689,31 @@ public class BroadcastDispatcher { boolean didSomething = cleanupBroadcastListDisabledReceiversLocked(mOrderedBroadcasts, packageName, filterByClasses, userId, doit); if (doit || !didSomething) { + ArrayList<BroadcastRecord> lockedBootCompletedBroadcasts = new ArrayList<>(); + for (int u = 0, usize = mUser2Deferred.size(); u < usize; u++) { + SparseArray<BroadcastRecord> brs = + mUser2Deferred.valueAt(u).mDeferredLockedBootCompletedBroadcasts; + for (int i = 0, size = brs.size(); i < size; i++) { + lockedBootCompletedBroadcasts.add(brs.valueAt(i)); + } + } + didSomething = cleanupBroadcastListDisabledReceiversLocked( + lockedBootCompletedBroadcasts, + packageName, filterByClasses, userId, doit); + } + if (doit || !didSomething) { + ArrayList<BroadcastRecord> bootCompletedBroadcasts = new ArrayList<>(); + for (int u = 0, usize = mUser2Deferred.size(); u < usize; u++) { + SparseArray<BroadcastRecord> brs = + mUser2Deferred.valueAt(u).mDeferredBootCompletedBroadcasts; + for (int i = 0, size = brs.size(); i < size; i++) { + bootCompletedBroadcasts.add(brs.valueAt(i)); + } + } + didSomething = cleanupBroadcastListDisabledReceiversLocked(bootCompletedBroadcasts, + packageName, filterByClasses, userId, doit); + } + if (doit || !didSomething) { didSomething |= cleanupDeferralsListDisabledReceiversLocked(mAlarmBroadcasts, packageName, filterByClasses, userId, doit); } @@ -428,6 +773,10 @@ public class BroadcastDispatcher { for (Deferrals d : mDeferredBroadcasts) { d.dumpDebug(proto, fieldId); } + + for (int i = 0, size = mUser2Deferred.size(); i < size; i++) { + mUser2Deferred.valueAt(i).dumpDebug(proto, fieldId); + } } // ---------------------------------- @@ -453,7 +802,12 @@ public class BroadcastDispatcher { final boolean someQueued = !mOrderedBroadcasts.isEmpty(); BroadcastRecord next = null; - if (!mAlarmBroadcasts.isEmpty()) { + + if (next == null) { + next = dequeueDeferredBootCompletedBroadcast(); + } + + if (next == null && !mAlarmBroadcasts.isEmpty()) { next = popLocked(mAlarmBroadcasts); if (DEBUG_BROADCAST_DEFERRAL && next != null) { Slog.i(TAG, "Next broadcast from alarm targets: " + next); @@ -752,6 +1106,20 @@ public class BroadcastDispatcher { } printed |= dumper.didPrint(); + dumper.setHeading("Deferred LOCKED_BOOT_COMPLETED broadcasts"); + dumper.setLabel("Deferred LOCKED_BOOT_COMPLETED Broadcast"); + for (int i = 0, size = mUser2Deferred.size(); i < size; i++) { + mUser2Deferred.valueAt(i).dump(dumper, Intent.ACTION_LOCKED_BOOT_COMPLETED); + } + printed |= dumper.didPrint(); + + dumper.setHeading("Deferred BOOT_COMPLETED broadcasts"); + dumper.setLabel("Deferred BOOT_COMPLETED Broadcast"); + for (int i = 0, size = mUser2Deferred.size(); i < size; i++) { + mUser2Deferred.valueAt(i).dump(dumper, Intent.ACTION_BOOT_COMPLETED); + } + printed |= dumper.didPrint(); + return printed; } } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index a83fdd0e74cd..ea63c080cf68 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -249,11 +249,13 @@ public final class BroadcastQueue { } public void enqueueParallelBroadcastLocked(BroadcastRecord r) { + r.enqueueClockTime = System.currentTimeMillis(); mParallelBroadcasts.add(r); enqueueBroadcastHelper(r); } public void enqueueOrderedBroadcastLocked(BroadcastRecord r) { + r.enqueueClockTime = System.currentTimeMillis(); mDispatcher.enqueueOrderedBroadcastLocked(r); enqueueBroadcastHelper(r); } @@ -263,8 +265,6 @@ public final class BroadcastQueue { * enqueueOrderedBroadcastLocked. */ private void enqueueBroadcastHelper(BroadcastRecord r) { - r.enqueueClockTime = System.currentTimeMillis(); - if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING), @@ -368,6 +368,15 @@ public final class BroadcastQueue { } } + /** + * Called by ActivityManagerService to notify that the uid has process started, if there is any + * deferred BOOT_COMPLETED broadcast, the BroadcastDispatcher can dispatch the broadcast now. + * @param uid + */ + public void updateUidReadyForBootCompletedBroadcastLocked(int uid) { + mDispatcher.updateUidReadyForBootCompletedBroadcastLocked(uid); + } + public boolean sendPendingBroadcastsLocked(ProcessRecord app) { boolean didSomething = false; final BroadcastRecord br = mPendingBroadcast; @@ -1858,9 +1867,7 @@ public final class BroadcastQueue { } private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) { - // TODO (206518114): Only allow apps with ACCESS_PACKAGE_USAGE_STATS to set - // getIdForResponseEvent. - // TODO (217251579): Temporarily use temp-allowlist reason to identify + // STOPSHIP (217251579): Temporarily use temp-allowlist reason to identify // push messages and record response events. useTemporaryAllowlistReasonAsSignal(r); if (r.options == null || r.options.getIdForResponseEvent() <= 0) { diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 801559620457..8b1e829ad836 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -16,9 +16,20 @@ package com.android.server.am; +import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED; + +import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL; +import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY; +import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_CHANGE_ID; +import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE; +import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY; + +import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.BroadcastOptions; +import android.app.compat.CompatChanges; import android.content.ComponentName; import android.content.IIntentReceiver; import android.content.Intent; @@ -30,6 +41,7 @@ import android.os.IBinder; import android.os.SystemClock; import android.os.UserHandle; import android.util.PrintWriterPrinter; +import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -371,6 +383,80 @@ final class BroadcastRecord extends Binder { return split; } + /** + * Split a BroadcastRecord to a map of deferred receiver UID to deferred BroadcastRecord. + * + * The receivers that are deferred are removed from original BroadcastRecord's receivers list. + * The receivers that are not deferred are kept in original BroadcastRecord's receivers list. + * + * Only used to split LOCKED_BOOT_COMPLETED or BOOT_COMPLETED BroadcastRecord. + * LOCKED_BOOT_COMPLETED or BOOT_COMPLETED broadcast can be deferred until the first time + * the receiver's UID has a process started. + * + * @param ams The ActivityManagerService object. + * @param deferType Defer what UID? + * @return the deferred UID to BroadcastRecord map, the BroadcastRecord has the list of + * receivers in that UID. + */ + @NonNull SparseArray<BroadcastRecord> splitDeferredBootCompletedBroadcastLocked( + ActivityManagerInternal activityManagerInternal, + @BroadcastConstants.DeferBootCompletedBroadcastType int deferType) { + final SparseArray<BroadcastRecord> ret = new SparseArray<>(); + if (deferType == DEFER_BOOT_COMPLETED_BROADCAST_NONE) { + return ret; + } + + final String action = intent.getAction(); + if (!Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action) + && !Intent.ACTION_BOOT_COMPLETED.equals(action)) { + return ret; + } + + final SparseArray<List<Object>> uid2receiverList = new SparseArray<>(); + for (int i = receivers.size() - 1; i >= 0; i--) { + final Object receiver = receivers.get(i); + final int uid = getReceiverUid(receiver); + if (deferType != DEFER_BOOT_COMPLETED_BROADCAST_ALL) { + if ((deferType & DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY) != 0) { + if (activityManagerInternal.getRestrictionLevel(uid) + < RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) { + // skip if the UID is not background restricted. + continue; + } + } + if ((deferType & DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY) != 0) { + if (!CompatChanges.isChangeEnabled(DEFER_BOOT_COMPLETED_BROADCAST_CHANGE_ID, + uid)) { + // skip if the UID is not targetSdkVersion T+. + continue; + } + } + } + // Remove receiver from original BroadcastRecord's receivers list. + receivers.remove(i); + final List<Object> receiverList = uid2receiverList.get(uid); + if (receiverList != null) { + receiverList.add(0, receiver); + } else { + ArrayList<Object> splitReceivers = new ArrayList<>(); + splitReceivers.add(0, receiver); + uid2receiverList.put(uid, splitReceivers); + } + } + final int uidSize = uid2receiverList.size(); + for (int i = 0; i < uidSize; i++) { + final BroadcastRecord br = new BroadcastRecord(queue, intent, callerApp, callerPackage, + callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType, + requiredPermissions, excludedPermissions, appOp, options, + uid2receiverList.valueAt(i), resultTo, + resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId, + allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt); + br.enqueueClockTime = this.enqueueClockTime; + ret.put(uid2receiverList.keyAt(i), br); + } + return ret; + } + int getReceiverUid(Object receiver) { if (receiver instanceof BroadcastFilter) { return ((BroadcastFilter) receiver).owningUid; diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 1ad0bcea711c..c4163e6acb2f 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1723,6 +1723,12 @@ public final class ProcessList { return Zygote.MEMORY_TAG_LEVEL_TBI; } + String defaultLevel = SystemProperties.get("persist.arm64.memtag.app_default"); + if ("sync".equals(defaultLevel)) { + return Zygote.MEMORY_TAG_LEVEL_SYNC; + } else if ("async".equals(defaultLevel)) { + return Zygote.MEMORY_TAG_LEVEL_ASYNC; + } return Zygote.MEMORY_TAG_LEVEL_NONE; } @@ -1918,7 +1924,7 @@ public final class ProcessList { if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL) != 0) { runtimeFlags |= Zygote.PROFILE_FROM_SHELL; } - if ((app.info.privateFlagsExt & ApplicationInfo.PRIVATE_FLAG_EXT_PROFILEABLE) != 0) { + if (app.info.isProfileable()) { runtimeFlags |= Zygote.PROFILEABLE; } if ("1".equals(SystemProperties.get("debug.checkjni"))) { @@ -2525,6 +2531,7 @@ public final class ProcessList { ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord, int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated, int isolatedUid, + boolean supplemental, int supplementalUid, String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) { long startTime = SystemClock.uptimeMillis(); ProcessRecord app; @@ -2618,7 +2625,8 @@ public final class ProcessList { if (app == null) { checkSlow(startTime, "startProcess: creating new process record"); - app = newProcessRecordLocked(info, processName, isolated, isolatedUid, hostingRecord); + app = newProcessRecordLocked(info, processName, isolated, isolatedUid, supplemental, + supplementalUid, hostingRecord); if (app == null) { Slog.w(TAG, "Failed making new process record for " + processName + "/" + info.uid + " isolated=" + isolated); @@ -3113,10 +3121,14 @@ public final class ProcessList { @GuardedBy("mService") ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess, - boolean isolated, int isolatedUid, HostingRecord hostingRecord) { + boolean isolated, int isolatedUid, boolean supplemental, int supplementalUid, + HostingRecord hostingRecord) { String proc = customProcess != null ? customProcess : info.processName; final int userId = UserHandle.getUserId(info.uid); int uid = info.uid; + if (supplemental) { + uid = supplementalUid; + } if (isolated) { if (isolatedUid == 0) { IsolatedUidRange uidRange = getOrCreateIsolatedUidRangeLocked(info, hostingRecord); diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java index 256cffde3455..84d2b1fff0e9 100644 --- a/services/core/java/com/android/server/am/ProcessStatsService.java +++ b/services/core/java/com/android/server/am/ProcessStatsService.java @@ -787,7 +787,6 @@ public final class ProcessStatsService extends IProcessStats.Stub { } private SparseArray<long[]> getUidProcStateStatsOverTime(long minTime) { - final Parcel current = Parcel.obtain(); final ProcessStats stats = new ProcessStats(); long curTime; synchronized (mLock) { diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index d3b57529834a..bf2876f4a755 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -94,6 +94,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN final boolean exported; // from ServiceInfo.exported final Runnable restarter; // used to schedule retries of starting the service final long createRealTime; // when this service was created + final boolean supplemental; // whether this is a supplemental service + final int supplementedAppUid; // the app uid for which this supplemental service is running final ArrayMap<Intent.FilterComparison, IntentBindRecord> bindings = new ArrayMap<Intent.FilterComparison, IntentBindRecord>(); // All active bindings to the service. @@ -571,13 +573,13 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg, Runnable restarter) { this(ams, name, instanceName, definingPackageName, definingUid, intent, sInfo, callerIsFg, - restarter, null); + restarter, null, 0); } ServiceRecord(ActivityManagerService ams, ComponentName name, ComponentName instanceName, String definingPackageName, int definingUid, Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg, - Runnable restarter, String supplementalProcessName) { + Runnable restarter, String supplementalProcessName, int supplementedAppUid) { this.ams = ams; this.name = name; this.instanceName = instanceName; @@ -588,6 +590,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN serviceInfo = sInfo; appInfo = sInfo.applicationInfo; packageName = sInfo.applicationInfo.packageName; + supplemental = supplementalProcessName != null; + this.supplementedAppUid = supplementedAppUid; if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) { processName = sInfo.processName + ":" + instanceName.getClassName(); } else if (supplementalProcessName != null) { @@ -1094,6 +1098,10 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN userId); foregroundNoti = localForegroundNoti; // save it for amending next time + + signalForegroundServiceNotification(packageName, appInfo.uid, + localForegroundId); + } catch (RuntimeException e) { Slog.w(TAG, "Error showing notification for service", e); // If it gave us a garbage notification, it doesn't @@ -1127,10 +1135,21 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN } catch (RuntimeException e) { Slog.w(TAG, "Error canceling notification for service", e); } + signalForegroundServiceNotification(packageName, appInfo.uid, -localForegroundId); } }); } + private void signalForegroundServiceNotification(String packageName, int uid, + int foregroundId) { + synchronized (ams) { + for (int i = ams.mForegroundServiceStateListeners.size() - 1; i >= 0; i--) { + ams.mForegroundServiceStateListeners.get(i).onForegroundServiceNotificationUpdated( + packageName, appInfo.uid, foregroundId); + } + } + } + public void stripForegroundServiceFlagFromNotification() { final int localForegroundId = foregroundId; final int localUserId = userId; diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java index ab4d856c1197..b73cf5b20ec0 100644 --- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java @@ -29,6 +29,7 @@ import android.app.PendingIntent; import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEventRequest; import android.app.ambientcontext.AmbientContextManager; +import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -39,7 +40,6 @@ import android.os.Binder; import android.os.Bundle; import android.os.RemoteCallback; import android.os.RemoteException; -import android.provider.Settings; import android.service.ambientcontext.AmbientContextDetectionResult; import android.service.ambientcontext.AmbientContextDetectionServiceStatus; import android.text.TextUtils; @@ -289,7 +289,7 @@ final class AmbientContextManagerPerUserService extends return; } - if ((recentTasks != null) || recentTasks.getList().isEmpty()) { + if ((recentTasks == null) || recentTasks.getList().isEmpty()) { Slog.e(TAG, "Recent task list is empty!"); return; } @@ -297,40 +297,48 @@ final class AmbientContextManagerPerUserService extends task = recentTasks.getList().get(0); if (!callingPackage.equals(task.topActivityInfo.packageName)) { Slog.e(TAG, "Recent task package name: " + task.topActivityInfo.packageName - + " doesn't match with camera client package name: " + callingPackage); + + " doesn't match with client package name: " + callingPackage); return; } // Start activity as the same task from the callingPackage - Intent intent = new Intent(); ComponentName consentComponent = getConsentComponent(); - if (consentComponent != null) { - Slog.e(TAG, "Starting consent activity for " + callingPackage); + if (consentComponent == null) { + Slog.e(TAG, "Consent component not found!"); + return; + } + + Slog.d(TAG, "Starting consent activity for " + callingPackage); + Intent intent = new Intent(); + final long identity = Binder.clearCallingIdentity(); + try { Context context = getContext(); - String packageNameExtraKey = Settings.Secure.getStringForUser( - context.getContentResolver(), - Settings.Secure.AMBIENT_CONTEXT_PACKAGE_NAME_EXTRA_KEY, - userId); - String eventArrayExtraKey = Settings.Secure.getStringForUser( - context.getContentResolver(), - Settings.Secure.AMBIENT_CONTEXT_EVENT_ARRAY_EXTRA_KEY, - userId); - - // Create consent activity intent with the calling package name and requested events. + String packageNameExtraKey = context.getResources().getString( + com.android.internal.R.string.config_ambientContextPackageNameExtraKey); + String eventArrayExtraKey = context.getResources().getString( + com.android.internal.R.string.config_ambientContextEventArrayExtraKey); + + // Create consent activity intent with the calling package name and requested events intent.setComponent(consentComponent); if (packageNameExtraKey != null) { intent.putExtra(packageNameExtraKey, callingPackage); + } else { + Slog.d(TAG, "Missing packageNameExtraKey for consent activity"); } if (eventArrayExtraKey != null) { intent.putExtra(eventArrayExtraKey, eventTypes); + } else { + Slog.d(TAG, "Missing eventArrayExtraKey for consent activity"); } // Set parent to the calling app's task ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchTaskId(task.taskId); - context.startActivity(intent, options.toBundle()); - } else { - Slog.e(TAG, "Consent component not found!"); + context.startActivityAsUser(intent, options.toBundle(), context.getUser()); + } catch (ActivityNotFoundException e) { + Slog.e(TAG, "unable to start consent activity"); + } finally { + Binder.restoreCallingIdentity(identity); } } @@ -339,13 +347,12 @@ final class AmbientContextManagerPerUserService extends */ private ComponentName getConsentComponent() { Context context = getContext(); - String consentComponent = Settings.Secure.getStringForUser( - context.getContentResolver(), - Settings.Secure.AMBIENT_CONTEXT_CONSENT_COMPONENT, - getUserId()); + String consentComponent = context.getResources().getString( + com.android.internal.R.string.config_defaultAmbientContextConsentComponent); if (TextUtils.isEmpty(consentComponent)) { return null; } + Slog.i(TAG, "Consent component name: " + consentComponent); return ComponentName.unflattenFromString(consentComponent); } diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 551773e3e834..9ba9d78591dd 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -17,8 +17,8 @@ package com.android.server.app; import static android.content.Intent.ACTION_PACKAGE_ADDED; -import static android.content.Intent.ACTION_PACKAGE_CHANGED; import static android.content.Intent.ACTION_PACKAGE_REMOVED; +import static android.content.Intent.EXTRA_REPLACING; import static com.android.internal.R.styleable.GameModeConfig_allowGameAngleDriver; import static com.android.internal.R.styleable.GameModeConfig_allowGameDownscaling; @@ -1467,7 +1467,6 @@ public final class GameManagerService extends IGameManagerService.Stub { private void registerPackageReceiver() { final IntentFilter packageFilter = new IntentFilter(); packageFilter.addAction(ACTION_PACKAGE_ADDED); - packageFilter.addAction(ACTION_PACKAGE_CHANGED); packageFilter.addAction(ACTION_PACKAGE_REMOVED); packageFilter.addDataScheme("package"); final BroadcastReceiver packageReceiver = new BroadcastReceiver() { @@ -1492,16 +1491,29 @@ public final class GameManagerService extends IGameManagerService.Stub { } switch (intent.getAction()) { case ACTION_PACKAGE_ADDED: - case ACTION_PACKAGE_CHANGED: updateConfigsForUser(userId, packageName); break; case ACTION_PACKAGE_REMOVED: disableCompatScale(packageName); - synchronized (mOverrideConfigLock) { - mOverrideConfigs.remove(packageName); - } - synchronized (mDeviceConfigLock) { - mConfigs.remove(packageName); + // If EXTRA_REPLACING is true, it means there will be an + // ACTION_PACKAGE_ADDED triggered after this because this + // is an updated package that gets installed. Hence, disable + // resolution downscaling effort but avoid removing the server + // or commandline overriding configurations because those will + // not change but the package game mode configurations may change + // which may opt in and/or opt out some game mode configurations. + if (!intent.getBooleanExtra(EXTRA_REPLACING, false)) { + synchronized (mOverrideConfigLock) { + mOverrideConfigs.remove(packageName); + } + synchronized (mDeviceConfigLock) { + mConfigs.remove(packageName); + } + synchronized (mLock) { + if (mSettings.containsKey(userId)) { + mSettings.get(userId).removeGame(packageName); + } + } } break; default: diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java index 2982545a5e6f..1455a6119c7e 100644 --- a/services/core/java/com/android/server/app/GameManagerSettings.java +++ b/services/core/java/com/android/server/app/GameManagerSettings.java @@ -93,6 +93,14 @@ public class GameManagerSettings { } /** + * Remove the game mode of a given package. + * This operation must be synced with an external lock. + */ + void removeGame(String packageName) { + mGameModes.remove(packageName); + } + + /** * Write all current game service settings into disk. * This operation must be synced with an external lock. */ diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 40fda4cbec5e..cebcc64f7d5e 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -4549,6 +4549,26 @@ public class AppOpsService extends IAppOpsService.Stub { return new PackageVerificationResult(null, /* isAttributionTagValid */ true); } + if (Process.isSupplemental(uid)) { + // Supplemental processes run in their own UID range, but their associated + // UID for checks should always be the UID of the supplemental package. + // TODO: We will need to modify the callers of this function instead, so + // modifications and checks against the app ops state are done with the + // correct UID. + try { + final PackageManager pm = mContext.getPackageManager(); + final String supplementalPackageName = pm.getSupplementalProcessPackageName(); + if (Objects.equals(packageName, supplementalPackageName)) { + int supplementalAppId = pm.getPackageUid(supplementalPackageName, + PackageManager.PackageInfoFlags.of(0)); + uid = UserHandle.getUid(UserHandle.getUserId(uid), supplementalAppId); + } + } catch (PackageManager.NameNotFoundException e) { + // Shouldn't happen for the supplemental package + e.printStackTrace(); + } + } + // Do not check if uid/packageName/attributionTag is already known. synchronized (this) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 05955c3cab44..3e5e4357b8f4 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -22,6 +22,7 @@ import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; import static android.media.AudioManager.STREAM_SYSTEM; import static android.os.Process.FIRST_APPLICATION_UID; +import static android.os.Process.INVALID_UID; import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE; import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE; @@ -147,6 +148,7 @@ import android.service.notification.ZenModeConfig; import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.AndroidRuntimeException; +import android.util.ArraySet; import android.util.IntArray; import android.util.Log; import android.util.MathUtils; @@ -329,6 +331,9 @@ public class AudioService extends IAudioService.Stub private static final int MSG_ROUTING_UPDATED = 41; private static final int MSG_INIT_HEADTRACKING_SENSORS = 42; private static final int MSG_PERSIST_SPATIAL_AUDIO_ENABLED = 43; + private static final int MSG_ADD_ASSISTANT_SERVICE_UID = 44; + private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45; + private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), @@ -341,6 +346,9 @@ public class AudioService extends IAudioService.Stub // retry delay in case of failure to indicate system ready to AudioFlinger private static final int INDICATE_SYSTEM_READY_RETRY_DELAY_MS = 1000; + // List of empty UIDs used to reset the active assistant list + private static final int[] NO_ACTIVE_ASSISTANT_SERVICE_UIDS = new int[0]; + /** @see AudioSystemThread */ private AudioSystemThread mAudioSystemThread; /** @see AudioHandler */ @@ -756,10 +764,15 @@ public class AudioService extends IAudioService.Stub private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT; private long mLoweredFromNormalToVibrateTime; - // Uid of the active hotword detection service to check if caller is the one or not. - @GuardedBy("mHotwordDetectionServiceUidLock") - private int mHotwordDetectionServiceUid = android.os.Process.INVALID_UID; - private final Object mHotwordDetectionServiceUidLock = new Object(); + // Array of Uids of valid assistant services to check if caller is one of them + @GuardedBy("mSettingsLock") + private final ArraySet<Integer> mAssistantUids = new ArraySet<>(); + @GuardedBy("mSettingsLock") + private int mPrimaryAssistantUid = INVALID_UID; + + // Array of Uids of valid active assistant service to check if caller is one of them + @GuardedBy("mSettingsLock") + private int[] mActiveAssistantServiceUids = NO_ACTIVE_ASSISTANT_SERVICE_UIDS; // Array of Uids of valid accessibility services to check if caller is one of them private final Object mAccessibilityServiceUidsLock = new Object(); @@ -787,9 +800,6 @@ public class AudioService extends IAudioService.Stub private boolean mHomeSoundEffectEnabled; @GuardedBy("mSettingsLock") - private int mAssistantUid; - - @GuardedBy("mSettingsLock") private int mCurrentImeUid; private final Object mSupportedSystemUsagesLock = new Object(); @@ -1395,12 +1405,10 @@ public class AudioService extends IAudioService.Stub mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, forDock, "onAudioServerDied"); sendEncodedSurroundMode(mContentResolver, "onAudioServerDied"); sendEnabledSurroundFormats(mContentResolver, true); - updateAssistantUId(true); AudioSystem.setRttEnabled(mRttEnabled); + updateAssistantServicesUidsLocked(); } - synchronized (mHotwordDetectionServiceUidLock) { - AudioSystem.setHotwordDetectionServiceUid(mHotwordDetectionServiceUid); - } + synchronized (mAccessibilityServiceUidsLock) { AudioSystem.setA11yServicesUids(mAccessibilityServiceUids); } @@ -1478,6 +1486,68 @@ public class AudioService extends IAudioService.Stub updateVibratorInfos(); } + private void onRemoveAssistantServiceUids(int[] uids) { + synchronized (mSettingsLock) { + removeAssistantServiceUidsLocked(uids); + } + } + + @GuardedBy("mSettingsLock") + private void removeAssistantServiceUidsLocked(int[] uids) { + boolean changed = false; + for (int index = 0; index < uids.length; index++) { + if (!mAssistantUids.remove(uids[index])) { + Slog.e(TAG, TextUtils.formatSimple( + "Cannot remove assistant service, uid(%d) not present", uids[index])); + continue; + } + changed = true; + } + if (changed) { + updateAssistantServicesUidsLocked(); + } + } + + private void onAddAssistantServiceUids(int[] uids) { + synchronized (mSettingsLock) { + addAssistantServiceUidsLocked(uids); + } + } + + @GuardedBy("mSettingsLock") + private void addAssistantServiceUidsLocked(int[] uids) { + boolean changed = false; + for (int index = 0; index < uids.length; index++) { + if (uids[index] == INVALID_UID) { + continue; + } + if (!mAssistantUids.add(uids[index])) { + Slog.e(TAG, TextUtils.formatSimple( + "Cannot add assistant service, uid(%d) already present", + uids[index])); + continue; + } + changed = true; + } + if (changed) { + updateAssistantServicesUidsLocked(); + } + } + + @GuardedBy("mSettingsLock") + private void updateAssistantServicesUidsLocked() { + int[] assistantUids = mAssistantUids.stream().mapToInt(Integer::intValue).toArray(); + AudioSystem.setAssistantServicesUids(assistantUids); + } + + private void updateActiveAssistantServiceUids() { + int [] activeAssistantServiceUids; + synchronized (mSettingsLock) { + activeAssistantServiceUids = mActiveAssistantServiceUids; + } + AudioSystem.setActiveAssistantServicesUids(activeAssistantServiceUids); + } + private void onReinitVolumes(@NonNull String caller) { final int numStreamTypes = AudioSystem.getNumStreamTypes(); // keep track of any error during stream volume initialization @@ -2243,8 +2313,7 @@ public class AudioService extends IAudioService.Stub @GuardedBy("mSettingsLock") private void updateAssistantUId(boolean forceUpdate) { - int assistantUid = 0; - + int assistantUid = INVALID_UID; // Consider assistants in the following order of priority: // 1) apk in assistant role // 2) voice interaction service @@ -2288,10 +2357,10 @@ public class AudioService extends IAudioService.Stub } } } - - if (assistantUid != mAssistantUid || forceUpdate) { - AudioSystem.setAssistantUid(assistantUid); - mAssistantUid = assistantUid; + if ((mPrimaryAssistantUid != assistantUid) || forceUpdate) { + mAssistantUids.remove(mPrimaryAssistantUid); + mPrimaryAssistantUid = assistantUid; + addAssistantServiceUidsLocked(new int[]{mPrimaryAssistantUid}); } } @@ -2342,6 +2411,7 @@ public class AudioService extends IAudioService.Stub sendEncodedSurroundMode(cr, "readPersistedSettings"); sendEnabledSurroundFormats(cr, true); updateAssistantUId(true); + resetActiveAssistantUidsLocked(); AudioSystem.setRttEnabled(mRttEnabled); } @@ -2367,6 +2437,12 @@ public class AudioService extends IAudioService.Stub mVolumeController.loadSettings(cr); } + @GuardedBy("mSettingsLock") + private void resetActiveAssistantUidsLocked() { + mActiveAssistantServiceUids = NO_ACTIVE_ASSISTANT_SERVICE_UIDS; + updateActiveAssistantServiceUids(); + } + private void readUserRestrictions() { if (!mSystemServer.isPrivileged()) { return; @@ -2892,8 +2968,9 @@ public class AudioService extends IAudioService.Stub int step; // skip a2dp absolute volume control request when the device - // is not an a2dp device - if (!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device) + // is neither an a2dp device nor BLE device + if ((!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device) + && !AudioSystem.DEVICE_OUT_ALL_BLE_SET.contains(device)) && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) { return; } @@ -3031,7 +3108,8 @@ public class AudioService extends IAudioService.Stub } if (device == AudioSystem.DEVICE_OUT_BLE_HEADSET - && streamType == getBluetoothContextualVolumeStream()) { + && streamType == getBluetoothContextualVolumeStream() + && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { if (DEBUG_VOL) { Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index=" + newIndex + " stream=" + streamType); @@ -3631,8 +3709,9 @@ public class AudioService extends IAudioService.Stub int oldIndex; // skip a2dp absolute volume control request when the device - // is not an a2dp device - if (!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device) + // is neither an a2dp device nor BLE device + if ((!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device) + && !AudioSystem.DEVICE_OUT_ALL_BLE_SET.contains(device)) && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) { return; } @@ -3675,7 +3754,8 @@ public class AudioService extends IAudioService.Stub } if (device == AudioSystem.DEVICE_OUT_BLE_HEADSET - && streamType == getBluetoothContextualVolumeStream()) { + && streamType == getBluetoothContextualVolumeStream() + && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { if (DEBUG_VOL) { Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index=" + index + " stream=" + streamType); @@ -7843,6 +7923,17 @@ public class AudioService extends IAudioService.Stub case MSG_PERSIST_SPATIAL_AUDIO_ENABLED: onPersistSpatialAudioEnabled(msg.arg1 == 1); break; + + case MSG_ADD_ASSISTANT_SERVICE_UID: + onAddAssistantServiceUids(new int[]{msg.arg1}); + break; + + case MSG_REMOVE_ASSISTANT_SERVICE_UID: + onRemoveAssistantServiceUids(new int[]{msg.arg1}); + break; + case MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID: + updateActiveAssistantServiceUids(); + break; } } } @@ -8483,6 +8574,30 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.isAvailable(); } + /** @see Spatializer#isAvailableForDevice(AudioDeviceAttributes) */ + public boolean isSpatializerAvailableForDevice(@NonNull AudioDeviceAttributes device) { + enforceModifyDefaultAudioEffectsPermission(); + return mSpatializerHelper.isAvailableForDevice(Objects.requireNonNull(device)); + } + + /** @see Spatializer#hasHeadTracker(AudioDeviceAttributes) */ + public boolean hasHeadTracker(@NonNull AudioDeviceAttributes device) { + enforceModifyDefaultAudioEffectsPermission(); + return mSpatializerHelper.hasHeadTracker(Objects.requireNonNull(device)); + } + + /** @see Spatializer#setHeadTrackerEnabled(boolean, AudioDeviceAttributes) */ + public void setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes device) { + enforceModifyDefaultAudioEffectsPermission(); + mSpatializerHelper.setHeadTrackerEnabled(enabled, Objects.requireNonNull(device)); + } + + /** @see Spatializer#isHeadTrackerEnabled(AudioDeviceAttributes) */ + public boolean isHeadTrackerEnabled(@NonNull AudioDeviceAttributes device) { + enforceModifyDefaultAudioEffectsPermission(); + return mSpatializerHelper.isHeadTrackerEnabled(Objects.requireNonNull(device)); + } + /** @see Spatializer#setSpatializerEnabled(boolean) */ public void setSpatializerEnabled(boolean enabled) { enforceModifyDefaultAudioEffectsPermission(); @@ -9351,7 +9466,7 @@ public class AudioService extends IAudioService.Stub pw.print(" mPendingVolumeCommand="); pw.println(mPendingVolumeCommand); pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs); pw.print(" mMcc="); pw.println(mMcc); - pw.print(" mCameraSoundForced="); pw.println(mCameraSoundForced); + pw.print(" mCameraSoundForced="); pw.println(isCameraSoundForced()); pw.print(" mHasVibrator="); pw.println(mHasVibrator); pw.print(" mVolumePolicy="); pw.println(mVolumePolicy); pw.print(" mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported); @@ -9371,9 +9486,9 @@ public class AudioService extends IAudioService.Stub + " FromRestrictions=" + mMicMuteFromRestrictions + " FromApi=" + mMicMuteFromApi + " from system=" + mMicMuteFromSystemCached); - pw.print("\n mAssistantUid="); pw.println(mAssistantUid); pw.print(" mCurrentImeUid="); pw.println(mCurrentImeUid); dumpAccessibilityServiceUids(pw); + dumpAssistantServicesUids(pw); dumpAudioPolicies(pw); mDynPolicyLogger.dump(pw); @@ -9416,6 +9531,19 @@ public class AudioService extends IAudioService.Stub } } + private void dumpAssistantServicesUids(PrintWriter pw) { + synchronized (mSettingsLock) { + if (mAssistantUids.size() > 0) { + pw.println(" Assistant service UIDs:"); + for (int uid : mAssistantUids) { + pw.println(" - " + uid); + } + } else { + pw.println(" No Assistant service Uids."); + } + } + } + private void dumpAccessibilityServiceUids(PrintWriter pw) { synchronized (mSupportedSystemUsagesLock) { if (mAccessibilityServiceUids != null && mAccessibilityServiceUids.length > 0) { @@ -9714,13 +9842,40 @@ public class AudioService extends IAudioService.Stub } @Override - public void setHotwordDetectionServiceUid(int uid) { - synchronized (mHotwordDetectionServiceUidLock) { - if (mHotwordDetectionServiceUid != uid) { - mHotwordDetectionServiceUid = uid; - AudioSystem.setHotwordDetectionServiceUid(mHotwordDetectionServiceUid); + public void addAssistantServiceUid(int uid) { + sendMsg(mAudioHandler, MSG_ADD_ASSISTANT_SERVICE_UID, SENDMSG_QUEUE, + uid, 0, null, 0); + } + + @Override + public void removeAssistantServiceUid(int uid) { + sendMsg(mAudioHandler, MSG_REMOVE_ASSISTANT_SERVICE_UID, SENDMSG_QUEUE, + uid, 0, null, 0); + } + + @Override + public void setActiveAssistantServicesUids(IntArray activeUids) { + synchronized (mSettingsLock) { + if (activeUids.size() == 0) { + mActiveAssistantServiceUids = NO_ACTIVE_ASSISTANT_SERVICE_UIDS; + } else { + boolean changed = (mActiveAssistantServiceUids == null) + || (mActiveAssistantServiceUids.length != activeUids.size()); + if (!changed) { + for (int i = 0; i < mActiveAssistantServiceUids.length; i++) { + if (activeUids.get(i) != mActiveAssistantServiceUids[i]) { + changed = true; + break; + } + } + } + if (changed) { + mActiveAssistantServiceUids = activeUids.toArray(); + } } } + sendMsg(mAudioHandler, MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID, SENDMSG_REPLACE, + 0, 0, null, 0); } @Override @@ -11017,6 +11172,59 @@ public class AudioService extends IAudioService.Stub return delayMillis; } + /** @see AudioManager#addAssistantServicesUids(int []) */ + @Override + public void addAssistantServicesUids(int [] assistantUids) { + enforceModifyAudioRoutingPermission(); + Objects.requireNonNull(assistantUids); + + synchronized (mSettingsLock) { + addAssistantServiceUidsLocked(assistantUids); + } + } + + /** @see AudioManager#removeAssistantServicesUids(int []) */ + @Override + public void removeAssistantServicesUids(int [] assistantUids) { + enforceModifyAudioRoutingPermission(); + Objects.requireNonNull(assistantUids); + synchronized (mSettingsLock) { + removeAssistantServiceUidsLocked(assistantUids); + } + } + + /** @see AudioManager#getAssistantServicesUids() */ + @Override + public int[] getAssistantServicesUids() { + enforceModifyAudioRoutingPermission(); + int [] assistantUids; + synchronized (mSettingsLock) { + assistantUids = mAssistantUids.stream().mapToInt(Integer::intValue).toArray(); + } + return assistantUids; + } + + /** @see AudioManager#setActiveAssistantServiceUids(int []) */ + @Override + public void setActiveAssistantServiceUids(int [] activeAssistantUids) { + enforceModifyAudioRoutingPermission(); + synchronized (mSettingsLock) { + mActiveAssistantServiceUids = activeAssistantUids; + } + updateActiveAssistantServiceUids(); + } + + /** @see AudioManager#getActiveAssistantServiceUids() */ + @Override + public int[] getActiveAssistantServiceUids() { + enforceModifyAudioRoutingPermission(); + int [] activeAssistantUids; + synchronized (mSettingsLock) { + activeAssistantUids = mActiveAssistantServiceUids.clone(); + } + return activeAssistantUids; + } + UUID getDeviceSensorUuid(AudioDeviceAttributes device) { return mDeviceBroker.getDeviceSensorUuid(device); } diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index f572261c09e5..a70b4701bb7b 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -387,14 +387,6 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback { } /** - * Same as {@link AudioSystem#setHotwordDetectionServiceUid(int)} - * Communicate UID of current HotwordDetectionService to audio policy service. - */ - public int setHotwordDetectionServiceUid(int uid) { - return AudioSystem.setHotwordDetectionServiceUid(uid); - } - - /** * Same as {@link AudioSystem#setCurrentImeUid(int)} * Communicate UID of current InputMethodService to audio policy service. */ diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 106cbba913d6..63b27d82dab9 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -23,6 +23,7 @@ import android.hardware.Sensor; import android.hardware.SensorManager; import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioSystem; import android.media.INativeSpatializerCallback; @@ -33,11 +34,14 @@ import android.media.ISpatializerHeadTrackingCallback; import android.media.ISpatializerHeadTrackingModeCallback; import android.media.ISpatializerOutputCallback; import android.media.SpatializationLevel; +import android.media.SpatializationMode; import android.media.Spatializer; import android.media.SpatializerHeadTrackingMode; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; +import android.util.Pair; +import android.util.SparseIntArray; import java.io.PrintWriter; import java.util.ArrayList; @@ -69,10 +73,42 @@ public class SpatializerHelper { // TODO: replace with generic head tracker sensor name. // the current implementation refers to the "google" namespace but will be replaced // by an android name at the next API level revision, it is not Google-specific. - // Also see "TODO-HT" in onInitSensors() method private static final String HEADTRACKER_SENSOR = "com.google.hardware.sensor.hid_dynamic.headtracker"; + private static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(15) { + { + append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL); + append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL); + append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, SpatializationMode.SPATIALIZER_BINAURAL); + // assumption for A2DP: mostly headsets + append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, SpatializationMode.SPATIALIZER_BINAURAL); + append(AudioDeviceInfo.TYPE_DOCK, SpatializationMode.SPATIALIZER_TRANSAURAL); + append(AudioDeviceInfo.TYPE_USB_ACCESSORY, SpatializationMode.SPATIALIZER_TRANSAURAL); + append(AudioDeviceInfo.TYPE_USB_DEVICE, SpatializationMode.SPATIALIZER_TRANSAURAL); + append(AudioDeviceInfo.TYPE_USB_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL); + append(AudioDeviceInfo.TYPE_LINE_ANALOG, SpatializationMode.SPATIALIZER_TRANSAURAL); + append(AudioDeviceInfo.TYPE_LINE_DIGITAL, SpatializationMode.SPATIALIZER_TRANSAURAL); + append(AudioDeviceInfo.TYPE_AUX_LINE, SpatializationMode.SPATIALIZER_TRANSAURAL); + append(AudioDeviceInfo.TYPE_HEARING_AID, SpatializationMode.SPATIALIZER_BINAURAL); + append(AudioDeviceInfo.TYPE_BLE_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL); + append(AudioDeviceInfo.TYPE_BLE_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL); + // assumption that BLE broadcast would be mostly consumed on headsets + append(AudioDeviceInfo.TYPE_BLE_BROADCAST, SpatializationMode.SPATIALIZER_BINAURAL); + } + }; + + private static final int[] WIRELESS_TYPES = { AudioDeviceInfo.TYPE_BLUETOOTH_SCO, + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, + AudioDeviceInfo.TYPE_BLE_HEADSET, + AudioDeviceInfo.TYPE_BLE_SPEAKER, + AudioDeviceInfo.TYPE_BLE_BROADCAST + }; + + private static final int[] WIRELESS_SPEAKER_TYPES = { + AudioDeviceInfo.TYPE_BLE_SPEAKER, + }; + // Spatializer state machine private static final int STATE_UNINITIALIZED = 0; private static final int STATE_NOT_SUPPORTED = 1; @@ -82,11 +118,20 @@ public class SpatializerHelper { private static final int STATE_DISABLED_AVAILABLE = 6; private int mState = STATE_UNINITIALIZED; + private boolean mFeatureEnabled = false; /** current level as reported by native Spatializer in callback */ private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + private boolean mTransauralSupported = false; + private boolean mBinauralSupported = false; private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; + /** + * The desired head tracking mode when enabling head tracking, tracks mDesiredHeadTrackingMode, + * except when head tracking gets disabled through setting the desired mode to + * {@link Spatializer#HEAD_TRACKING_MODE_DISABLED}. + */ + private int mDesiredHeadTrackingModeWhenEnabled = Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; private int mSpatOutput = 0; private @Nullable ISpatializer mSpat; private @Nullable SpatializerCallback mSpatCallback; @@ -108,8 +153,18 @@ public class SpatializerHelper { //--------------------------------------------------------------- // audio device compatibility / enabled + /** + * List of device types that can be used on this device with Spatial Audio. + * It is initialized based on the transaural/binaural capabilities + * of the effect. + */ + private final ArrayList<Integer> mSACapableDeviceTypes = new ArrayList<>(0); - private final ArrayList<AudioDeviceAttributes> mCompatibleAudioDevices = new ArrayList<>(0); + /** + * List of devices where Spatial Audio is possible. Each device can be enabled or disabled + * (== user choice to use or not) + */ + private final ArrayList<SADeviceState> mSADevices = new ArrayList<>(0); //------------------------------------------------------ // initialization @@ -155,6 +210,46 @@ public class SpatializerHelper { break; } } + byte[] spatModes = spat.getSupportedModes(); + for (byte mode : spatModes) { + switch (mode) { + case SpatializationMode.SPATIALIZER_BINAURAL: + mBinauralSupported = true; + break; + case SpatializationMode.SPATIALIZER_TRANSAURAL: + mTransauralSupported = true; + break; + default: + Log.e(TAG, "Spatializer reports unknown supported mode:" + mode); + break; + } + } + // if neither transaural nor binaural is supported, bail + if (!mBinauralSupported && !mTransauralSupported) { + mState = STATE_NOT_SUPPORTED; + return; + } + + // initialize list of compatible devices + for (int i = 0; i < SPAT_MODE_FOR_DEVICE_TYPE.size(); i++) { + int mode = SPAT_MODE_FOR_DEVICE_TYPE.valueAt(i); + if ((mode == (int) SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported) + || (mode == (int) SpatializationMode.SPATIALIZER_TRANSAURAL + && mTransauralSupported)) { + mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i)); + } + } + if (mTransauralSupported) { + // TODO deal with persisted values + mSADevices.add( + new SADeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null)); + } + if (mBinauralSupported) { + // TODO deal with persisted values + mSADevices.add( + new SADeviceState(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, null)); + } + // TODO read persisted states } catch (RemoteException e) { /* capable level remains at NONE*/ } finally { @@ -184,12 +279,15 @@ public class SpatializerHelper { mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; init(true); - setFeatureEnabled(featureEnabled); + setSpatializerEnabledInt(featureEnabled); } //------------------------------------------------------ // routing monitoring - void onRoutingUpdated() { + synchronized void onRoutingUpdated() { + if (!mFeatureEnabled) { + return; + } switch (mState) { case STATE_UNINITIALIZED: case STATE_NOT_SUPPORTED: @@ -201,11 +299,36 @@ public class SpatializerHelper { break; } mASA.getDevicesForAttributes(DEFAULT_ATTRIBUTES).toArray(ROUTING_DEVICES); - final boolean able = - AudioSystem.canBeSpatialized(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, ROUTING_DEVICES); - logd("onRoutingUpdated: can spatialize media 5.1:" + able - + " on device:" + ROUTING_DEVICES[0]); - setDispatchAvailableState(able); + + // is media routed to a new device? + if (isWireless(ROUTING_DEVICES[0].getType())) { + addWirelessDeviceIfNew(ROUTING_DEVICES[0]); + } + + // find if media device enabled / available + final Pair<Boolean, Boolean> enabledAvailable = evaluateState(ROUTING_DEVICES[0]); + + boolean able = false; + if (enabledAvailable.second) { + // available for Spatial audio, check w/ effect + able = canBeSpatializedOnDevice(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, ROUTING_DEVICES); + Log.i(TAG, "onRoutingUpdated: can spatialize media 5.1:" + able + + " on device:" + ROUTING_DEVICES[0]); + setDispatchAvailableState(able); + } else { + Log.i(TAG, "onRoutingUpdated: device:" + ROUTING_DEVICES[0] + + " not available for Spatial Audio"); + setDispatchAvailableState(false); + } + + if (able && enabledAvailable.first) { + Log.i(TAG, "Enabling Spatial Audio since enabled for media device:" + + ROUTING_DEVICES[0]); + } else { + Log.i(TAG, "Disabling Spatial Audio since disabled for media device:" + + ROUTING_DEVICES[0]); + } + setDispatchFeatureEnabledState(able && enabledAvailable.first); if (mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED && mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_DISABLED) { @@ -298,20 +421,145 @@ public class SpatializerHelper { //------------------------------------------------------ // compatible devices /** - * @return a shallow copy of the list of compatible audio devices + * Return the list of compatible devices, which reflects the device compatible with the + * spatializer effect, and those that have been explicitly enabled or disabled + * @return the list of compatible audio devices */ synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() { - return (List<AudioDeviceAttributes>) mCompatibleAudioDevices.clone(); + // build unionOf(mCompatibleAudioDevices, mEnabledDevice) - mDisabledAudioDevices + ArrayList<AudioDeviceAttributes> compatList = new ArrayList<>(); + for (SADeviceState dev : mSADevices) { + if (dev.mEnabled) { + compatList.add(new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, + dev.mDeviceType, dev.mDeviceAddress == null ? "" : dev.mDeviceAddress)); + } + } + return compatList; } synchronized void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { - if (!mCompatibleAudioDevices.contains(ada)) { - mCompatibleAudioDevices.add(ada); + // TODO add log + final int deviceType = ada.getType(); + final boolean wireless = isWireless(deviceType); + boolean updateRouting = false; + boolean isInList = false; + + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) + || !wireless) { + isInList = true; + // state change? + updateRouting = !deviceState.mEnabled; + deviceState.mEnabled = true; + break; + } + } + if (!isInList) { + final SADeviceState dev = new SADeviceState(deviceType, + wireless ? ada.getAddress() : null); + dev.mEnabled = true; + mSADevices.add(dev); + updateRouting = true; + } + if (updateRouting) { + onRoutingUpdated(); } } synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { - mCompatibleAudioDevices.remove(ada); + // TODO add log + final int deviceType = ada.getType(); + final boolean wireless = isWireless(deviceType); + boolean updateRouting = false; + + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) + || !wireless) { + // state change? + updateRouting = deviceState.mEnabled; + deviceState.mEnabled = false; + break; + } + } + if (updateRouting) { + onRoutingUpdated(); + } + } + + /** + * Return if Spatial Audio is enabled and available for the given device + * @param ada + * @return a pair of boolean, 1/ enabled? 2/ available? + */ + private synchronized Pair<Boolean, Boolean> evaluateState(AudioDeviceAttributes ada) { + // if not a wireless device, this value will be overwritten to map the type + // to TYPE_BUILTIN_SPEAKER or TYPE_WIRED_HEADPHONES + int deviceType = ada.getType(); + final boolean wireless = isWireless(deviceType); + + // if not a wireless device: find if media device is in the speaker, wired headphones + if (!wireless) { + // is the device type capable of doing SA? + if (!mSACapableDeviceTypes.contains(deviceType)) { + Log.i(TAG, "Device incompatible with Spatial Audio dev:" + ada); + return new Pair<>(false, false); + } + // what spatialization mode to use for this device? + final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE); + if (spatMode == Integer.MIN_VALUE) { + // error case, device not found + Log.e(TAG, "no spatialization mode found for device type:" + deviceType); + return new Pair<>(false, false); + } + // map the spatialization mode to the SPEAKER or HEADPHONES device + if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) { + deviceType = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; + } else { + deviceType = AudioDeviceInfo.TYPE_WIRED_HEADPHONES; + } + } else { // wireless device + if (isWirelessSpeaker(deviceType) && !mTransauralSupported) { + Log.i(TAG, "Device incompatible with Spatial Audio (no transaural) dev:" + + ada); + return new Pair<>(false, false); + } + if (!mBinauralSupported) { + Log.i(TAG, "Device incompatible with Spatial Audio (no binaural) dev:" + + ada); + return new Pair<>(false, false); + } + } + + boolean enabled = false; + boolean available = false; + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) + || !wireless) { + available = true; + enabled = deviceState.mEnabled; + break; + } + } + return new Pair<>(enabled, available); + } + + private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) { + boolean knownDevice = false; + for (SADeviceState deviceState : mSADevices) { + // wireless device so always check address + if (ada.getType() == deviceState.mDeviceType + && ada.getAddress().equals(deviceState.mDeviceAddress)) { + knownDevice = true; + break; + } + } + if (!knownDevice) { + mSADevices.add(new SADeviceState(ada.getType(), ada.getAddress())); + //### TODO persist list + } } //------------------------------------------------------ @@ -345,7 +593,56 @@ public class SpatializerHelper { } } + synchronized boolean isAvailableForDevice(@NonNull AudioDeviceAttributes ada) { + if (ada.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) { + return false; + } + + final int deviceType = ada.getType(); + final boolean wireless = isWireless(deviceType); + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) + || !wireless) { + return true; + } + } + return false; + } + + private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes, + @NonNull AudioFormat format, @NonNull AudioDeviceAttributes[] devices) { + final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(devices[0].getType(), + /*default when type not found*/ SpatializationMode.SPATIALIZER_BINAURAL); + if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported) + || (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL + && mTransauralSupported)) { + return AudioSystem.canBeSpatialized(attributes, format, devices); + } + return false; + } + synchronized void setFeatureEnabled(boolean enabled) { + if (mFeatureEnabled == enabled) { + return; + } + mFeatureEnabled = enabled; + if (mFeatureEnabled) { + if (mState == STATE_NOT_SUPPORTED) { + Log.e(TAG, "Can't enabled Spatial Audio, unsupported"); + return; + } + if (mState == STATE_UNINITIALIZED) { + init(true); + } + setSpatializerEnabledInt(true); + onRoutingUpdated(); + } else { + setSpatializerEnabledInt(false); + } + } + + synchronized void setSpatializerEnabledInt(boolean enabled) { switch (mState) { case STATE_UNINITIALIZED: if (enabled) { @@ -397,8 +694,7 @@ public class SpatializerHelper { } /** - * precondition: mState = STATE_* - * isFeatureEnabled() != featureEnabled + * Update the feature state, no-op if no change * @param featureEnabled */ private synchronized void setDispatchFeatureEnabledState(boolean featureEnabled) { @@ -410,6 +706,10 @@ public class SpatializerHelper { case STATE_DISABLED_AVAILABLE: mState = STATE_ENABLED_AVAILABLE; break; + case STATE_ENABLED_AVAILABLE: + case STATE_ENABLED_UNAVAILABLE: + // already enabled: no-op + return; default: throw(new IllegalStateException("Invalid mState:" + mState + " for enabled true")); @@ -422,6 +722,10 @@ public class SpatializerHelper { case STATE_ENABLED_AVAILABLE: mState = STATE_DISABLED_AVAILABLE; break; + case STATE_DISABLED_AVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + // already disabled: no-op + return; default: throw (new IllegalStateException("Invalid mState:" + mState + " for enabled false")); @@ -562,7 +866,7 @@ public class SpatializerHelper { AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1]; // going through adapter to take advantage of routing cache mASA.getDevicesForAttributes(attributes).toArray(devices); - final boolean able = AudioSystem.canBeSpatialized(attributes, format, devices); + final boolean able = canBeSpatializedOnDevice(attributes, format, devices); logd("canBeSpatialized returning " + able); return able; } @@ -688,6 +992,9 @@ public class SpatializerHelper { if (!checkSpatForHeadTracking("setDesiredHeadTrackingMode")) { return; } + if (mode != Spatializer.HEAD_TRACKING_MODE_DISABLED) { + mDesiredHeadTrackingModeWhenEnabled = mode; + } try { if (mDesiredHeadTrackingMode != mode) { mDesiredHeadTrackingMode = mode; @@ -701,6 +1008,84 @@ public class SpatializerHelper { } } + synchronized void setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes ada) { + final int deviceType = ada.getType(); + final boolean wireless = isWireless(deviceType); + + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) + || !wireless) { + if (!deviceState.mHasHeadTracker) { + Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled + + " device:" + ada + " on a device without headtracker"); + return; + } + Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada); + deviceState.mHeadTrackerEnabled = enabled; + break; + } + } + // check current routing to see if it affects the headtracking mode + if (ROUTING_DEVICES[0].getType() == deviceType + && ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) { + setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled + : Spatializer.HEAD_TRACKING_MODE_DISABLED); + } + } + + synchronized boolean hasHeadTracker(@NonNull AudioDeviceAttributes ada) { + final int deviceType = ada.getType(); + final boolean wireless = isWireless(deviceType); + + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) + || !wireless) { + return deviceState.mHasHeadTracker; + } + } + return false; + } + + /** + * Configures device in list as having a head tracker + * @param ada + * @return true if the head tracker is enabled, false otherwise or if device not found + */ + synchronized boolean setHasHeadTracker(@NonNull AudioDeviceAttributes ada) { + final int deviceType = ada.getType(); + final boolean wireless = isWireless(deviceType); + + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) + || !wireless) { + deviceState.mHasHeadTracker = true; + return deviceState.mHeadTrackerEnabled; + } + } + Log.e(TAG, "setHasHeadTracker: device not found for:" + ada); + return false; + } + + synchronized boolean isHeadTrackerEnabled(@NonNull AudioDeviceAttributes ada) { + final int deviceType = ada.getType(); + final boolean wireless = isWireless(deviceType); + + for (SADeviceState deviceState : mSADevices) { + if (deviceType == deviceState.mDeviceType + && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) + || !wireless) { + if (!deviceState.mHasHeadTracker) { + return false; + } + return deviceState.mHeadTrackerEnabled; + } + } + return false; + } + private boolean checkSpatForHeadTracking(String funcName) { switch (mState) { case STATE_UNINITIALIZED: @@ -884,7 +1269,7 @@ public class SpatializerHelper { } synchronized void onInitSensors() { - final boolean init = (mSpatLevel != SpatializationLevel.NONE); + final boolean init = mFeatureEnabled && (mSpatLevel != SpatializationLevel.NONE); final String action = init ? "initializing" : "releasing"; if (mSpat == null) { Log.e(TAG, "not " + action + " sensors, null spatializer"); @@ -926,6 +1311,12 @@ public class SpatializerHelper { UUID uuid = sensor.getUuid(); if (uuid.equals(routingDeviceUuid)) { headHandle = sensor.getHandle(); + // TODO check risk of race condition: + // does this happen before routing is updated? + // avoid by supporting adding device here AND in onRoutingUpdated() + if (!setHasHeadTracker(ROUTING_DEVICES[0])) { + headHandle = -1; + } break; } if (uuid.equals(UuidUtils.STANDALONE_UUID)) { @@ -933,6 +1324,7 @@ public class SpatializerHelper { } } } + Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); screenHandle = screenSensor.getHandle(); } else { @@ -1017,7 +1409,50 @@ public class SpatializerHelper { for (int mode : modes) { modesString += Spatializer.headtrackingModeToString(mode) + " "; } + pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural" + + mTransauralSupported); pw.println("\tsupported head tracking modes:" + modesString); pw.println("\tmSpatOutput:" + mSpatOutput); + pw.println("\tdevices:\n"); + for (SADeviceState device : mSADevices) { + pw.println("\t\t" + device + "\n"); + } + } + + private static final class SADeviceState { + final int mDeviceType; + final @Nullable String mDeviceAddress; // non-null for wireless devices + boolean mEnabled = true; // by default, SA is enabled on any device + boolean mHasHeadTracker = false; + boolean mHeadTrackerEnabled = true; // by default, if head tracker is present, use it + + SADeviceState(int deviceType, @Nullable String address) { + mDeviceType = deviceType; + mDeviceAddress = address; + } + + @Override + public String toString() { + return "type:" + mDeviceType + " addr:" + mDeviceAddress + " enabled:" + mEnabled + + " HT:" + mHasHeadTracker + " HTenabled:" + mHeadTrackerEnabled; + } + } + + private static boolean isWireless(int deviceType) { + for (int type : WIRELESS_TYPES) { + if (type == deviceType) { + return true; + } + } + return false; + } + + private static boolean isWirelessSpeaker(int deviceType) { + for (int type: WIRELESS_SPEAKER_TYPES) { + if (type == deviceType) { + return true; + } + } + return false; } } diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index 05c3f68f355b..aec98f0ea426 100644 --- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java +++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java @@ -104,7 +104,8 @@ class PreAuthInfo { final int requestedStrength = Utils.getPublicBiometricStrength(promptInfo); final boolean credentialRequested = Utils.isCredentialRequested(promptInfo); - final boolean credentialAvailable = trustManager.isDeviceSecure(userId); + final boolean credentialAvailable = trustManager.isDeviceSecure(userId, + context.getAssociatedDisplayId()); // Assuming that biometric authenticators are listed in priority-order, the rest of this // function will attempt to find the first authenticator that's as strong or stronger than diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java index c5e266f87149..c86a8cb2c39d 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java @@ -44,7 +44,7 @@ public interface BiometricContext { @Nullable Integer getBiometricPromptSessionId(); /** If the display is in AOD. */ - boolean isAoD(); + boolean isAod(); /** * Subscribe to context changes. diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java index 70acaff05e30..9d2fde72ea2e 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java @@ -120,7 +120,7 @@ class BiometricContextProvider implements BiometricContext { @Override public OperationContext updateContext(@NonNull OperationContext operationContext, boolean isCryptoOperation) { - operationContext.isAoD = isAoD(); + operationContext.isAod = isAod(); operationContext.isCrypto = isCryptoOperation; setFirstSessionId(operationContext); return operationContext; @@ -160,7 +160,7 @@ class BiometricContextProvider implements BiometricContext { } @Override - public boolean isAoD() { + public boolean isAod() { return mIsDozing && mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT); } @@ -177,7 +177,7 @@ class BiometricContextProvider implements BiometricContext { private void notifySubscribers() { mSubscribers.forEach((context, consumer) -> { - context.isAoD = isAoD(); + context.isAod = isAod(); consumer.accept(context); }); } diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java index 8965227a1bb4..d6ca8a68145e 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java @@ -56,7 +56,7 @@ public class BiometricFrameworkStatsLogger { -1 /* sensorId */, operationContext.id, sessionType(operationContext.reason), - operationContext.isAoD); + operationContext.isAod); } /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */ @@ -77,7 +77,7 @@ public class BiometricFrameworkStatsLogger { ambientLightLux, operationContext.id, sessionType(operationContext.reason), - operationContext.isAoD); + operationContext.isAod); } /** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */ @@ -109,7 +109,7 @@ public class BiometricFrameworkStatsLogger { -1 /* sensorId */, operationContext.id, sessionType(operationContext.reason), - operationContext.isAoD); + operationContext.isAod); } /** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */ diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index d26a78008529..653776b3ca65 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -229,7 +229,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> context.y = y; context.minor = minor; context.major = major; - context.isAoD = getBiometricContext().isAoD(); + context.isAod = getBiometricContext().isAod(); session.getSession().onPointerDownWithContext(context); } else { session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index e21d901b135d..c92d599d68e6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -213,7 +213,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps context.y = y; context.minor = minor; context.major = major; - context.isAoD = getBiometricContext().isAoD(); + context.isAod = getBiometricContext().isAod(); session.getSession().onPointerDownWithContext(context); } else { session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java index 452c972ca784..67511533de66 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java @@ -204,8 +204,8 @@ public class TestHal extends IFingerprint.Stub { @Override public void onPointerDownWithContext(PointerContext context) { - onPointerDown( - context.pointerId, context.x, context.y, context.minor, context.major); + onPointerDown(context.pointerId, (int) context.x, (int) context.y, context.minor, + context.major); } @Override diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java index 135276e789c1..94d3d15aec33 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -67,4 +67,9 @@ public abstract class VirtualDeviceManagerInternal { * display. */ public abstract boolean isAppRunningOnAnyVirtualDevice(int uid); + + /** + * Returns true if the {@code displayId} is owned by any virtual device + */ + public abstract boolean isDisplayOwnedByAnyVirtualDevice(int displayId); } diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java index 603f20633cfb..108e7bcb23bb 100644 --- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java +++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java @@ -25,7 +25,6 @@ import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.WARNING_DISABLED; import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES; -import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH; import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN; @@ -191,6 +190,7 @@ public class MultipathPolicyTracker { class MultipathTracker { final Network network; final String subscriberId; + private final int mSubId; private long mQuota; /** Current multipath budget. Nonzero iff we have budget and a UsageCallback is armed. */ @@ -204,9 +204,8 @@ public class MultipathPolicyTracker { this.network = network; this.mNetworkCapabilities = new NetworkCapabilities(nc); NetworkSpecifier specifier = nc.getNetworkSpecifier(); - int subId = INVALID_SUBSCRIPTION_ID; if (specifier instanceof TelephonyNetworkSpecifier) { - subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId(); + mSubId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId(); } else { throw new IllegalStateException(String.format( "Can't get subId from mobile network %s (%s)", @@ -217,14 +216,14 @@ public class MultipathPolicyTracker { if (tele == null) { throw new IllegalStateException(String.format("Missing TelephonyManager")); } - tele = tele.createForSubscriptionId(subId); + tele = tele.createForSubscriptionId(mSubId); if (tele == null) { throw new IllegalStateException(String.format( - "Can't get TelephonyManager for subId %d", subId)); + "Can't get TelephonyManager for subId %d", mSubId)); } subscriberId = Objects.requireNonNull(tele.getSubscriberId(), - "Null subscriber Id for subId " + subId); + "Null subscriber Id for subId " + mSubId); mNetworkTemplate = new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE) .setSubscriberIds(Set.of(subscriberId)) .setMeteredness(NetworkStats.METERED_YES) @@ -282,6 +281,7 @@ public class MultipathPolicyTracker { .setSubscriberId(subscriberId) .setRoaming(!nc.hasCapability(NET_CAPABILITY_NOT_ROAMING)) .setMetered(!nc.hasCapability(NET_CAPABILITY_NOT_METERED)) + .setSubId(mSubId) .build(); } diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java index 1894c0fc6045..2c6257f7cba6 100644 --- a/services/core/java/com/android/server/content/SyncStorageEngine.java +++ b/services/core/java/com/android/server/content/SyncStorageEngine.java @@ -2421,10 +2421,10 @@ public class SyncStorageEngine { public static final int STATISTICS_FILE_ITEM = 101; private void readStatsParcelLocked(File parcel) { + Parcel in = Parcel.obtain(); try { final AtomicFile parcelFile = new AtomicFile(parcel); byte[] data = parcelFile.readFully(); - Parcel in = Parcel.obtain(); in.unmarshall(data, 0, data.length); in.setDataPosition(0); int token; @@ -2452,6 +2452,8 @@ public class SyncStorageEngine { } } catch (IOException e) { Slog.i(TAG, "No initial statistics"); + } finally { + in.recycle(); } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 7f1482e6dde1..f50011020694 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -78,6 +78,7 @@ import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; import android.hardware.display.WifiDisplayStatus; +import android.hardware.graphics.common.DisplayDecorationSupport; import android.hardware.input.InputManagerInternal; import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionManager; @@ -1860,10 +1861,10 @@ public final class DisplayManagerService extends SystemService { return mDisplayModeDirector.getModeSwitchingType(); } - private boolean getDisplayDecorationSupportInternal(int displayId) { + private DisplayDecorationSupport getDisplayDecorationSupportInternal(int displayId) { final IBinder displayToken = getDisplayToken(displayId); if (null == displayToken) { - return false; + return null; } return SurfaceControl.getDisplayDecorationSupport(displayToken); } @@ -3550,7 +3551,7 @@ public final class DisplayManagerService extends SystemService { } @Override // Binder call - public boolean getDisplayDecorationSupport(int displayId) { + public DisplayDecorationSupport getDisplayDecorationSupport(int displayId) { final long token = Binder.clearCallingIdentity(); try { return getDisplayDecorationSupportInternal(displayId); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index d71e07ab573f..418e91dd616e 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -75,6 +75,7 @@ import com.android.server.display.whitebalance.DisplayWhiteBalanceSettings; import com.android.server.policy.WindowManagerPolicy; import java.io.PrintWriter; +import java.util.Objects; /** * Controls the power state of the display. @@ -236,42 +237,42 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // True if we should fade the screen while turning it off, false if we should play // a stylish color fade animation instead. - private boolean mColorFadeFadesConfig; + private final boolean mColorFadeFadesConfig; // True if we need to fake a transition to off when coming out of a doze state. // Some display hardware will blank itself when coming out of doze in order to hide // artifacts. For these displays we fake a transition into OFF so that policy can appropriately // blank itself and begin an appropriate power on animation. - private boolean mDisplayBlanksAfterDozeConfig; + private final boolean mDisplayBlanksAfterDozeConfig; // True if there are only buckets of brightness values when the display is in the doze state, // rather than a full range of values. If this is true, then we'll avoid animating the screen // brightness since it'd likely be multiple jarring brightness transitions instead of just one // to reach the final state. - private boolean mBrightnessBucketsInDozeConfig; + private final boolean mBrightnessBucketsInDozeConfig; // The pending power request. // Initially null until the first call to requestPowerState. - // Guarded by mLock. + @GuardedBy("mLock") private DisplayPowerRequest mPendingRequestLocked; // True if a request has been made to wait for the proximity sensor to go negative. - // Guarded by mLock. + @GuardedBy("mLock") private boolean mPendingWaitForNegativeProximityLocked; // True if the pending power request or wait for negative proximity flag // has been changed since the last update occurred. - // Guarded by mLock. + @GuardedBy("mLock") private boolean mPendingRequestChangedLocked; // Set to true when the important parts of the pending power request have been applied. // The important parts are mainly the screen state. Brightness changes may occur // concurrently. - // Guarded by mLock. + @GuardedBy("mLock") private boolean mDisplayReadyLocked; // Set to true if a power state update is required. - // Guarded by mLock. + @GuardedBy("mLock") private boolean mPendingUpdatePowerStateLocked; /* The following state must only be accessed by the handler thread. */ @@ -352,8 +353,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // information. // At the time of this writing, this value is changed within updatePowerState() only, which is // limited to the thread used by DisplayControllerHandler. - private BrightnessReason mBrightnessReason = new BrightnessReason(); - private BrightnessReason mBrightnessReasonTemp = new BrightnessReason(); + private final BrightnessReason mBrightnessReason = new BrightnessReason(); + private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason(); // Brightness animation ramp rates in brightness units per second private float mBrightnessRampRateFastDecrease; @@ -849,6 +850,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } + @GuardedBy("mLock") private void sendUpdatePowerStateLocked() { if (!mStopped && !mPendingUpdatePowerStateLocked) { mPendingUpdatePowerStateLocked = true; @@ -2318,6 +2320,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return mAutomaticBrightnessController.convertToNits(brightness); } + @GuardedBy("mLock") private void updatePendingProximityRequestsLocked() { mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked; mPendingWaitForNegativeProximityLocked = false; @@ -2421,12 +2424,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig); pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig); - mHandler.runWithScissors(new Runnable() { - @Override - public void run() { - dumpLocal(pw); - } - }, 1000); + mHandler.runWithScissors(() -> dumpLocal(pw), 1000); } private void dumpLocal(PrintWriter pw) { @@ -2458,6 +2456,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mAppliedThrottling=" + mAppliedThrottling); pw.println(" mAppliedScreenBrightnessOverride=" + mAppliedScreenBrightnessOverride); pw.println(" mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness); + pw.println(" mAppliedTemporaryAutoBrightnessAdjustment=" + + mAppliedTemporaryAutoBrightnessAdjustment); + pw.println(" mAppliedBrightnessBoost=" + mAppliedBrightnessBoost); pw.println(" mDozing=" + mDozing); pw.println(" mSkipRampState=" + skipRampStateToString(mSkipRampState)); pw.println(" mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime); @@ -2465,21 +2466,21 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mPendingScreenOnUnblocker=" + mPendingScreenOnUnblocker); pw.println(" mPendingScreenOffUnblocker=" + mPendingScreenOffUnblocker); pw.println(" mPendingScreenOff=" + mPendingScreenOff); - pw.println(" mReportedToPolicy=" + - reportedToPolicyToString(mReportedScreenStateToPolicy)); + pw.println(" mReportedToPolicy=" + + reportedToPolicyToString(mReportedScreenStateToPolicy)); if (mScreenBrightnessRampAnimator != null) { - pw.println(" mScreenBrightnessRampAnimator.isAnimating()=" + - mScreenBrightnessRampAnimator.isAnimating()); + pw.println(" mScreenBrightnessRampAnimator.isAnimating()=" + + mScreenBrightnessRampAnimator.isAnimating()); } if (mColorFadeOnAnimator != null) { - pw.println(" mColorFadeOnAnimator.isStarted()=" + - mColorFadeOnAnimator.isStarted()); + pw.println(" mColorFadeOnAnimator.isStarted()=" + + mColorFadeOnAnimator.isStarted()); } if (mColorFadeOffAnimator != null) { - pw.println(" mColorFadeOffAnimator.isStarted()=" + - mColorFadeOffAnimator.isStarted()); + pw.println(" mColorFadeOffAnimator.isStarted()=" + + mColorFadeOffAnimator.isStarted()); } if (mPowerState != null) { @@ -2605,7 +2606,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } - private final void logHbmBrightnessStats(float brightness, int displayStatsId) { + private void logHbmBrightnessStats(float brightness, int displayStatsId) { synchronized (mHandler) { FrameworkStatsLog.write( FrameworkStatsLog.DISPLAY_HBM_BRIGHTNESS_CHANGED, displayStatsId, brightness); @@ -2824,7 +2825,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call @Override public boolean equals(Object obj) { - if (obj == null || !(obj instanceof BrightnessReason)) { + if (!(obj instanceof BrightnessReason)) { return false; } BrightnessReason other = (BrightnessReason) obj; @@ -2832,6 +2833,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } @Override + public int hashCode() { + return Objects.hash(reason, modifier); + } + + @Override public String toString() { return toString(0); } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index c02e725e4785..d233c5ee2ffc 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -182,8 +182,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { private final long mPhysicalDisplayId; private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>(); private final ArrayList<Integer> mSupportedColorModes = new ArrayList<>(); + private final DisplayModeDirector.DesiredDisplayModeSpecs mDisplayModeSpecs = + new DisplayModeDirector.DesiredDisplayModeSpecs(); private final boolean mIsDefaultDisplay; private final BacklightAdapter mBacklightAdapter; + private final SidekickInternal mSidekickInternal; private DisplayDeviceInfo mInfo; private boolean mHavePendingChanges; @@ -200,8 +203,6 @@ final class LocalDisplayAdapter extends DisplayAdapter { private int mActiveDisplayModeAtStartId = INVALID_MODE_ID; private Display.Mode mUserPreferredMode; private int mActiveModeId = INVALID_MODE_ID; - private DisplayModeDirector.DesiredDisplayModeSpecs mDisplayModeSpecs = - new DisplayModeDirector.DesiredDisplayModeSpecs(); private boolean mDisplayModeSpecsInvalid; private int mActiveColorMode; private Display.HdrCapabilities mHdrCapabilities; @@ -210,13 +211,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { private boolean mAllmRequested; private boolean mGameContentTypeRequested; private boolean mSidekickActive; - private SidekickInternal mSidekickInternal; private SurfaceControl.StaticDisplayInfo mStaticDisplayInfo; // The supported display modes according to SurfaceFlinger private SurfaceControl.DisplayMode[] mSfDisplayModes; // The active display mode in SurfaceFlinger private SurfaceControl.DisplayMode mActiveSfDisplayMode; - private DisplayDeviceConfig mDisplayDeviceConfig; private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides = new DisplayEventReceiver.FrameRateOverride[0]; @@ -233,7 +232,6 @@ final class LocalDisplayAdapter extends DisplayAdapter { mSidekickInternal = LocalServices.getService(SidekickInternal.class); mBacklightAdapter = new BacklightAdapter(displayToken, isDefaultDisplay, mSurfaceControlProxy); - mDisplayDeviceConfig = null; mActiveDisplayModeAtStartId = dynamicInfo.activeDisplayModeId; } @@ -459,9 +457,6 @@ final class LocalDisplayAdapter extends DisplayAdapter { final Context context = getOverlayContext(); mDisplayDeviceConfig = DisplayDeviceConfig.create(context, mPhysicalDisplayId, mIsDefaultDisplay); - if (mDisplayDeviceConfig == null) { - return; - } // Load brightness HWC quirk mBacklightAdapter.setForceSurfaceControl(mDisplayDeviceConfig.hasQuirk( @@ -1083,8 +1078,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { pw.println("mGameContentTypeRequested=" + mGameContentTypeRequested); pw.println("mStaticDisplayInfo=" + mStaticDisplayInfo); pw.println("mSfDisplayModes="); - for (int i = 0; i < mSfDisplayModes.length; i++) { - pw.println(" " + mSfDisplayModes[i]); + for (SurfaceControl.DisplayMode sfDisplayMode : mSfDisplayModes) { + pw.println(" " + sfDisplayMode); } pw.println("mActiveSfDisplayMode=" + mActiveSfDisplayMode); pw.println("mSupportedModes="); @@ -1238,6 +1233,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { } public static class Injector { + // Native callback. + @SuppressWarnings("unused") private ProxyDisplayEventReceiver mReceiver; public void setDisplayEventListenerLocked(Looper looper, DisplayEventListener listener) { mReceiver = new ProxyDisplayEventReceiver(looper, listener); diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 93c73be5021e..6f5729f89e0f 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -825,7 +825,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { * * @param device The device to associate with the LogicalDisplay. * @param displayId The display ID to give the new display. If invalid, a new ID is assigned. - * @param isDefault Indicates if we are creating the default display. * @return The new logical display if created, null otherwise. */ private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) { diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java index d8672fc07619..2567e4306293 100644 --- a/services/core/java/com/android/server/display/RampAnimator.java +++ b/services/core/java/com/android/server/display/RampAnimator.java @@ -55,7 +55,7 @@ class RampAnimator<T> { * If this is the first time the property is being set or if the rate is 0, * the value jumps directly to the target. * - * @param target The target value. + * @param targetLinear The target value. * @param rate The convergence rate in units per second, or 0 to set the value immediately. * @return True if the target differs from the previous target. */ diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index d48ccd5aa2dc..025ccd172ea9 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -45,7 +45,6 @@ import android.content.pm.ParceledListSlice; import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.SigningInfo; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.net.Uri; @@ -70,6 +69,7 @@ import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.PackageUserStateInternal; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import java.io.ByteArrayInputStream; import java.io.File; @@ -307,6 +307,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { } List<String> appCertificates = getCertificateFingerprint(packageInfo); + List<String> appCertificateLineage = getCertificateLineage(packageInfo); List<String> installerCertificates = getInstallerCertificateFingerprint(installerPackageName); @@ -314,6 +315,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { builder.setPackageName(getPackageNameNormalized(packageName)); builder.setAppCertificates(appCertificates); + builder.setAppCertificateLineage(appCertificateLineage); builder.setVersionCode(intent.getLongExtra(EXTRA_LONG_VERSION_CODE, -1)); builder.setInstallerName(getPackageNameNormalized(installerPackageName)); builder.setInstallerCertificates(installerCertificates); @@ -460,6 +462,14 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { return certificateFingerprints; } + private List<String> getCertificateLineage(@NonNull PackageInfo packageInfo) { + ArrayList<String> certificateLineage = new ArrayList(); + for (Signature signature : getSignatureLineage(packageInfo)) { + certificateLineage.add(getFingerprint(signature)); + } + return certificateLineage; + } + /** Get the allowed installers and their associated certificate hashes from <meta-data> tag. */ private Map<String, String> getAllowedInstallers(@NonNull PackageInfo packageInfo) { Map<String, String> packageCertMap = new HashMap<>(); @@ -541,6 +551,38 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { return signingInfo.getApkContentsSigners(); } + private static Signature[] getSignatureLineage(@NonNull PackageInfo packageInfo) { + // Obtain the signing info of the package. + SigningInfo signingInfo = packageInfo.signingInfo; + if (signingInfo == null) { + throw new IllegalArgumentException( + "Package signature not found in " + packageInfo); + } + + // Obtain the active signatures of the package. + Signature[] signatureLineage = getSignatures(packageInfo); + + // Obtain the past signatures of the package. + if (!signingInfo.hasMultipleSigners() && signingInfo.hasPastSigningCertificates()) { + Signature[] pastSignatures = signingInfo.getSigningCertificateHistory(); + + // Merge the signatures and return. + Signature[] allSignatures = + new Signature[signatureLineage.length + pastSignatures.length]; + int i; + for (i = 0; i < signatureLineage.length; i++) { + allSignatures[i] = signatureLineage[i]; + } + for (int j = 0; j < pastSignatures.length; j++) { + allSignatures[i] = pastSignatures[j]; + i++; + } + signatureLineage = allSignatures; + } + + return signatureLineage; + } + private static String getFingerprint(Signature cert) { InputStream input = new ByteArrayInputStream(cert.toByteArray()); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 4d3e4389783b..0f4648a3f0f8 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -1717,7 +1717,7 @@ public class LockSettingsService extends ILockSettings.Stub { return false; } setSeparateProfileChallengeEnabledLocked(userId, true, /* unused */ null); - notifyPasswordChanged(userId); + notifyPasswordChanged(credential, userId); } if (isCredentialSharedWithParent(userId)) { // Make sure the profile doesn't get locked straight after setting work challenge. @@ -2510,9 +2510,11 @@ public class LockSettingsService extends ILockSettings.Stub { * Call after {@link #setUserPasswordMetrics} so metrics are updated before * reporting the password changed. */ - private void notifyPasswordChanged(@UserIdInt int userId) { + private void notifyPasswordChanged(LockscreenCredential newCredential, @UserIdInt int userId) { mHandler.post(() -> { - mInjector.getDevicePolicyManager().reportPasswordChanged(userId); + mInjector.getDevicePolicyManager().reportPasswordChanged( + PasswordMetrics.computeForCredential(newCredential), + userId); LocalServices.getService(WindowManagerInternal.class).reportPasswordChanged(userId); }); } @@ -3447,7 +3449,7 @@ public class LockSettingsService extends ILockSettings.Stub { // the caller like DPMS), otherwise it can lead to deadlock. mHandler.post(() -> unlockUser(userId, null)); } - notifyPasswordChanged(userId); + notifyPasswordChanged(credential, userId); notifySeparateProfileChallengeChanged(userId); } return result; diff --git a/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java b/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java new file mode 100644 index 000000000000..6b442a6a395e --- /dev/null +++ b/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.logcat; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentSender; +import android.os.Bundle; +import android.os.ServiceManager; +import android.os.logcat.ILogcatManagerService; +import android.util.Slog; +import android.view.View; +import android.widget.TextView; + +import com.android.internal.R; +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; + + +/** + * This dialog is shown to the user before an activity in a harmful app is launched. + * + * See {@code PackageManager.setLogcatAppInfo} for more info. + */ +public class LogAccessConfirmationActivity extends AlertActivity implements + DialogInterface.OnClickListener { + private static final String TAG = LogAccessConfirmationActivity.class.getSimpleName(); + + private String mPackageName; + private IntentSender mTarget; + private final ILogcatManagerService mLogcatManagerService = + ILogcatManagerService.Stub.asInterface(ServiceManager.getService("logcat")); + + private int mUid; + private int mGid; + private int mPid; + private int mFd; + + private static final String EXTRA_UID = "uid"; + private static final String EXTRA_GID = "gid"; + private static final String EXTRA_PID = "pid"; + private static final String EXTRA_FD = "fd"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Intent intent = getIntent(); + mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); + mUid = intent.getIntExtra("uid", 0); + mGid = intent.getIntExtra("gid", 0); + mPid = intent.getIntExtra("pid", 0); + mFd = intent.getIntExtra("fd", 0); + + final AlertController.AlertParams p = mAlertParams; + p.mTitle = getString(R.string.log_access_confirmation_title); + p.mView = createView(); + + p.mPositiveButtonText = getString(R.string.log_access_confirmation_allow); + p.mPositiveButtonListener = this; + p.mNegativeButtonText = getString(R.string.log_access_confirmation_deny); + p.mNegativeButtonListener = this; + + mAlert.installContent(mAlertParams); + } + + private View createView() { + final View view = getLayoutInflater().inflate(R.layout.harmful_app_warning_dialog, + null /*root*/); + ((TextView) view.findViewById(R.id.app_name_text)) + .setText(mPackageName); + ((TextView) view.findViewById(R.id.message)) + .setText(getIntent().getExtras().getString("body")); + return view; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + try { + mLogcatManagerService.approve(mUid, mGid, mPid, mFd); + } catch (Throwable t) { + Slog.e(TAG, "Could not start the LogcatManagerService.", t); + } + finish(); + break; + case DialogInterface.BUTTON_NEGATIVE: + try { + mLogcatManagerService.decline(mUid, mGid, mPid, mFd); + } catch (Throwable t) { + Slog.e(TAG, "Could not start the LogcatManagerService.", t); + } + finish(); + break; + } + } + + /** + * Create the Intent for a LogAccessConfirmationActivity. + */ + public static Intent createIntent(Context context, String targetPackageName, + IntentSender target, int uid, int gid, int pid, int fd) { + final Intent intent = new Intent(); + intent.setClass(context, LogAccessConfirmationActivity.class); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName); + intent.putExtra(EXTRA_UID, uid); + intent.putExtra(EXTRA_GID, gid); + intent.putExtra(EXTRA_PID, pid); + intent.putExtra(EXTRA_FD, fd); + + return intent; + } + +} diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java index ff6372aec3bd..34614d50c028 100644 --- a/services/core/java/com/android/server/logcat/LogcatManagerService.java +++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java @@ -16,20 +16,36 @@ package com.android.server.logcat; +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.app.ActivityManagerInternal; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.os.ILogd; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.os.logcat.ILogcatManagerService; import android.util.Slog; +import com.android.internal.R; +import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.util.ArrayUtils; +import com.android.server.LocalServices; import com.android.server.SystemService; +import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** - * Service responsible for manage the access to Logcat. + * Service responsible for managing the access to Logcat. */ public final class LogcatManagerService extends SystemService { @@ -38,6 +54,43 @@ public final class LogcatManagerService extends SystemService { private final BinderService mBinderService; private final ExecutorService mThreadExecutor; private ILogd mLogdService; + private NotificationManager mNotificationManager; + private @NonNull ActivityManager mActivityManager; + private ActivityManagerInternal mActivityManagerInternal; + private static final int MAX_UID_IMPORTANCE_COUNT_LISTENER = 2; + private static int sUidImportanceListenerCount = 0; + private static final int AID_SHELL_UID = 2000; + + // TODO This allowlist is just a temporary workaround for the tests: + // FrameworksServicesTests + // PlatformRuleTests + // After adapting the test suites, the allowlist will be removed in + // the upcoming bug fix patches. + private static final String[] ALLOWABLE_TESTING_PACKAGES = { + "android.platform.test.rule.tests", + "com.android.frameworks.servicestests" + }; + + // TODO Same as the above ALLOWABLE_TESTING_PACKAGES. + private boolean isAllowableTestingPackage(int uid) { + PackageManager pm = mContext.getPackageManager(); + + String[] packageNames = pm.getPackagesForUid(uid); + + if (ArrayUtils.isEmpty(packageNames)) { + return false; + } + + for (String name : packageNames) { + Slog.e(TAG, "isAllowableTestingPackage: " + name); + + if (Arrays.asList(ALLOWABLE_TESTING_PACKAGES).contains(name)) { + return true; + } + } + + return false; + }; private final class BinderService extends ILogcatManagerService.Stub { @Override @@ -51,6 +104,197 @@ public final class LogcatManagerService extends SystemService { // the logd data access is finished. mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, false)); } + + @Override + public void approve(int uid, int gid, int pid, int fd) { + try { + getLogdService().approve(uid, gid, pid, fd); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @Override + public void decline(int uid, int gid, int pid, int fd) { + try { + getLogdService().decline(uid, gid, pid, fd); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + + private ILogd getLogdService() { + synchronized (LogcatManagerService.this) { + if (mLogdService == null) { + LogcatManagerService.this.addLogdService(); + } + return mLogdService; + } + } + + private String getBodyString(Context context, String callingPackage, int uid) { + PackageManager pm = context.getPackageManager(); + try { + return context.getString( + com.android.internal.R.string.log_access_confirmation_body, + pm.getApplicationInfoAsUser(callingPackage, PackageManager.MATCH_DIRECT_BOOT_AUTO, + UserHandle.getUserId(uid)).loadLabel(pm)); + } catch (NameNotFoundException e) { + // App name is unknown. + return null; + } + } + + private void sendNotification(int notificationId, String clientInfo, int uid, int gid, int pid, + int fd) { + + final ActivityManagerInternal activityManagerInternal = + LocalServices.getService(ActivityManagerInternal.class); + + PackageManager pm = mContext.getPackageManager(); + String packageName = activityManagerInternal.getPackageNameByPid(pid); + if (packageName != null) { + String notificationBody = getBodyString(mContext, packageName, uid); + + final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext, + packageName, null, uid, gid, pid, fd); + + if (notificationBody == null) { + // Decline the logd access if the nofitication body is unknown + Slog.e(TAG, "Unknown notification body, declining the logd access"); + declineLogdAccess(uid, gid, pid, fd); + return; + } + + // TODO Next version will replace notification with dialogue + // per UX guidance. + generateNotificationWithBodyContent(notificationId, clientInfo, notificationBody, + mIntent); + return; + + } + + String[] packageNames = pm.getPackagesForUid(uid); + + if (ArrayUtils.isEmpty(packageNames)) { + // Decline the logd access if the app name is unknown + Slog.e(TAG, "Unknown calling package name, declining the logd access"); + declineLogdAccess(uid, gid, pid, fd); + return; + } + + String firstPackageName = packageNames[0]; + + if (firstPackageName == null || firstPackageName.length() == 0) { + // Decline the logd access if the package name from uid is unknown + Slog.e(TAG, "Unknown calling package name, declining the logd access"); + declineLogdAccess(uid, gid, pid, fd); + return; + } + + String notificationBody = getBodyString(mContext, firstPackageName, uid); + + final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext, + firstPackageName, null, uid, gid, pid, fd); + + if (notificationBody == null) { + Slog.e(TAG, "Unknown notification body, declining the logd access"); + declineLogdAccess(uid, gid, pid, fd); + return; + } + + // TODO Next version will replace notification with dialogue + // per UX guidance. + generateNotificationWithBodyContent(notificationId, clientInfo, + notificationBody, mIntent); + } + + private void declineLogdAccess(int uid, int gid, int pid, int fd) { + try { + getLogdService().decline(uid, gid, pid, fd); + } catch (RemoteException ex) { + Slog.e(TAG, "Fails to call remote functions ", ex); + } + } + + private void generateNotificationWithBodyContent(int notificationId, String clientInfo, + String notificationBody, Intent intent) { + final Notification.Builder notificationBuilder = new Notification.Builder( + mContext, + SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY); + intent.setFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.setIdentifier(String.valueOf(notificationId) + clientInfo); + intent.putExtra("body", notificationBody); + + notificationBuilder + .setSmallIcon(R.drawable.ic_info) + .setContentTitle( + mContext.getString(R.string.log_access_confirmation_title)) + .setContentText(notificationBody) + .setContentIntent( + PendingIntent.getActivity(mContext, 0, intent, + PendingIntent.FLAG_IMMUTABLE)) + .setTicker(mContext.getString(R.string.log_access_confirmation_title)) + .setOnlyAlertOnce(true) + .setAutoCancel(true); + mNotificationManager.notify(notificationId, notificationBuilder.build()); + } + + /** + * A class which watches an uid for background access and notifies the logdMonitor when + * the package status becomes foreground (importance change) + */ + private class UidImportanceListener implements ActivityManager.OnUidImportanceListener { + private final int mExpectedUid; + private final int mExpectedGid; + private final int mExpectedPid; + private final int mExpectedFd; + private int mExpectedImportance; + private int mCurrentImportance = RunningAppProcessInfo.IMPORTANCE_GONE; + + UidImportanceListener(int uid, int gid, int pid, int fd, int importance) { + mExpectedUid = uid; + mExpectedGid = gid; + mExpectedPid = pid; + mExpectedFd = fd; + mExpectedImportance = importance; + } + + @Override + public void onUidImportance(int uid, int importance) { + if (uid == mExpectedUid) { + mCurrentImportance = importance; + + /** + * 1) If the process status changes to foreground, send a notification + * for user consent. + * 2) If the process status remains background, we decline logd access request. + **/ + if (importance <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) { + String clientInfo = getClientInfo(uid, mExpectedGid, mExpectedPid, mExpectedFd); + sendNotification(0, clientInfo, uid, mExpectedGid, mExpectedPid, + mExpectedFd); + mActivityManager.removeOnUidImportanceListener(this); + + synchronized (LogcatManagerService.this) { + sUidImportanceListenerCount--; + } + } else { + try { + getLogdService().decline(uid, mExpectedGid, mExpectedPid, mExpectedFd); + } catch (RemoteException ex) { + Slog.e(TAG, "Fails to call remote functions ", ex); + } + } + } + } + } + + private static String getClientInfo(int uid, int gid, int pid, int fd) { + return "UID=" + Integer.toString(uid) + " GID=" + Integer.toString(gid) + " PID=" + + Integer.toString(pid) + " FD=" + Integer.toString(fd); } private class LogdMonitor implements Runnable { @@ -74,9 +318,7 @@ public final class LogcatManagerService extends SystemService { } /** - * The current version grant the permission by default. - * And track the logd access. - * The next version will generate a prompt for users. + * LogdMonitor generates a prompt for users. * The users decide whether the logd access is allowed. */ @Override @@ -86,11 +328,14 @@ public final class LogcatManagerService extends SystemService { } if (mStart) { + + // TODO Temporarily approve all the requests to unblock testing failures. try { - mLogdService.approve(mUid, mGid, mPid, mFd); - } catch (RemoteException ex) { - Slog.e(TAG, "Fails to call remote functions ", ex); + getLogdService().approve(mUid, mGid, mPid, mFd); + } catch (RemoteException e) { + e.printStackTrace(); } + return; } } } @@ -100,6 +345,8 @@ public final class LogcatManagerService extends SystemService { mContext = context; mBinderService = new BinderService(); mThreadExecutor = Executors.newCachedThreadPool(); + mActivityManager = context.getSystemService(ActivityManager.class); + mNotificationManager = mContext.getSystemService(NotificationManager.class); } @Override @@ -114,5 +361,4 @@ public final class LogcatManagerService extends SystemService { private void addLogdService() { mLogdService = ILogd.Stub.asInterface(ServiceManager.getService("logd")); } - } diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java index fd141bd78371..1ae7ac07594b 100644 --- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java +++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java @@ -26,6 +26,7 @@ import android.media.metrics.PlaybackMetrics; import android.media.metrics.PlaybackStateEvent; import android.media.metrics.TrackChangeEvent; import android.os.Binder; +import android.os.PersistableBundle; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; import android.util.Base64; @@ -180,6 +181,20 @@ public final class MediaMetricsManagerService extends SystemService { StatsLog.write(statsEvent); } + public void reportBundleMetrics(String sessionId, PersistableBundle metrics, int userId) { + int level = loggingLevel(); + if (level == LOGGING_LEVEL_BLOCKED) { + return; + } + + int atomid = metrics.getInt("KEY_STATSD_ATOM_NUMBER"); + switch (atomid) { + default: + return; + // to be extended as we define statsd atoms + } + } + @Override public void reportPlaybackStateEvent( String sessionId, PlaybackStateEvent event, int userId) { @@ -222,6 +237,21 @@ public final class MediaMetricsManagerService extends SystemService { } @Override + public String getTranscodingSessionId(int userId) { + return getSessionIdInternal(userId); + } + + @Override + public String getEditingSessionId(int userId) { + return getSessionIdInternal(userId); + } + + @Override + public String getBundleSessionId(int userId) { + return getSessionIdInternal(userId); + } + + @Override public void reportPlaybackErrorEvent( String sessionId, PlaybackErrorEvent event, int userId) { int level = loggingLevel(); diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java index b66c4668f2a0..33ac6cdb269e 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java +++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java @@ -16,14 +16,16 @@ package com.android.server.net; import static android.net.ConnectivityManager.BLOCKED_REASON_NONE; -import static android.net.INetd.FIREWALL_CHAIN_DOZABLE; -import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE; -import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED; -import static android.net.INetd.FIREWALL_CHAIN_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; import static android.net.INetd.FIREWALL_RULE_ALLOW; import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; @@ -328,6 +330,8 @@ public class NetworkPolicyLogger { return FIREWALL_CHAIN_NAME_POWERSAVE; case FIREWALL_CHAIN_RESTRICTED: return FIREWALL_CHAIN_NAME_RESTRICTED; + case FIREWALL_CHAIN_LOW_POWER_STANDBY: + return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; default: return String.valueOf(chain); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java index 8ef42ff97aad..3cb587812c2a 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java @@ -91,4 +91,10 @@ public abstract class NetworkPolicyManagerInternal { */ public abstract void setMeteredRestrictedPackagesAsync( Set<String> packageNames, int userId); + + /** Informs that Low Power Standby has become active */ + public abstract void setLowPowerStandbyActive(boolean active); + + /** Informs that the Low Power Standby allowlist has changed */ + public abstract void setLowPowerStandbyAllowlist(int[] uids); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index bb229022ac0b..8d05415a4714 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -46,22 +46,23 @@ import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRI import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY; import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER; import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE; +import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY; import static android.net.ConnectivityManager.BLOCKED_REASON_NONE; import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED; import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED; import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED; import static android.net.ConnectivityManager.TYPE_MOBILE; -import static android.net.INetd.FIREWALL_CHAIN_DOZABLE; -import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE; -import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED; -import static android.net.INetd.FIREWALL_CHAIN_STANDBY; import static android.net.INetd.FIREWALL_RULE_ALLOW; import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; -import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.SNOOZE_NEVER; import static android.net.NetworkPolicy.WARNING_DISABLED; @@ -70,11 +71,13 @@ import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_MASK; import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_SYSTEM; import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_USER_EXEMPTED; import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND; +import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST; import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE; import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLIST; import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST; import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS; import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM; +import static android.net.NetworkPolicyManager.ALLOWED_REASON_TOP; import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND; @@ -88,6 +91,7 @@ import static android.net.NetworkPolicyManager.RULE_REJECT_RESTRICTED_MODE; import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED; import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode; +import static android.net.NetworkPolicyManager.isProcStateAllowedWhileInLowPowerStandby; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground; import static android.net.NetworkPolicyManager.resolveNetworkId; import static android.net.NetworkPolicyManager.uidPoliciesToString; @@ -171,11 +175,9 @@ import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; import android.net.NetworkPolicyManager.UidState; import android.net.NetworkRequest; -import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkStateSnapshot; import android.net.NetworkTemplate; -import android.net.TelephonyNetworkSpecifier; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.BestClock; @@ -478,6 +480,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { volatile boolean mRestrictBackgroundChangedInBsm; @GuardedBy("mUidRulesFirstLock") volatile boolean mRestrictedNetworkingMode; + @GuardedBy("mUidRulesFirstLock") + volatile boolean mLowPowerStandbyActive; private final boolean mSuppressDefaultPolicy; @@ -517,6 +521,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray(); @GuardedBy("mUidRulesFirstLock") final SparseIntArray mUidFirewallRestrictedModeRules = new SparseIntArray(); + @GuardedBy("mUidRulesFirstLock") + final SparseIntArray mUidFirewallLowPowerStandbyModeRules = new SparseIntArray(); /** Set of states for the child firewall chains. True if the chain is active. */ @GuardedBy("mUidRulesFirstLock") @@ -545,6 +551,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @GuardedBy("mUidRulesFirstLock") private final SparseBooleanArray mPowerSaveTempWhitelistAppIds = new SparseBooleanArray(); + @GuardedBy("mUidRulesFirstLock") + private final SparseBooleanArray mLowPowerStandbyAllowlistUids = new SparseBooleanArray(); + /** * UIDs that have been allowlisted temporarily to be able to have network access despite being * idle. Other power saving restrictions still apply. @@ -1508,7 +1517,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { .setType(TYPE_MOBILE) .setSubscriberId(subscriberId) .setMetered(true) - .setDefaultNetwork(true).build(); + .setDefaultNetwork(true) + .setSubId(subId).build(); if (template.matches(probeIdent)) { return subId; } @@ -1745,7 +1755,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { .setType(TYPE_MOBILE) .setSubscriberId(subscriberId) .setMetered(true) - .setDefaultNetwork(true).build(); + .setDefaultNetwork(true) + .setSubId(subId).build(); for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) { final NetworkTemplate template = mNetworkPolicy.keyAt(i); if (template.matches(probeIdent)) { @@ -1977,7 +1988,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { .setType(TYPE_MOBILE) .setSubscriberId(subscriberId) .setMetered(true) - .setDefaultNetwork(true).build(); + .setDefaultNetwork(true) + .setSubId(subId).build(); // Template is matched when subscriber id matches. if (template.matches(probeIdent)) { matchingSubIds.add(subId); @@ -2079,7 +2091,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mNetIdToSubId.clear(); final ArrayMap<NetworkStateSnapshot, NetworkIdentity> identified = new ArrayMap<>(); for (final NetworkStateSnapshot snapshot : snapshots) { - mNetIdToSubId.put(snapshot.getNetwork().getNetId(), parseSubId(snapshot)); + final int subId = snapshot.getSubId(); + mNetIdToSubId.put(snapshot.getNetwork().getNetId(), subId); // Policies matched by NPMS only match by subscriber ID or by network ID. final NetworkIdentity ident = new NetworkIdentity.Builder() @@ -2284,7 +2297,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { .setType(TYPE_MOBILE) .setSubscriberId(subscriberId) .setMetered(true) - .setDefaultNetwork(true).build(); + .setDefaultNetwork(true) + .setSubId(subId).build(); for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) { final NetworkTemplate template = mNetworkPolicy.keyAt(i); if (template.matches(probeIdent)) { @@ -3763,6 +3777,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { fout.print("Restrict power: "); fout.println(mRestrictPower); fout.print("Device idle: "); fout.println(mDeviceIdleMode); fout.print("Restricted networking mode: "); fout.println(mRestrictedNetworkingMode); + fout.print("Low Power Standby mode: "); fout.println(mLowPowerStandbyActive); synchronized (mMeteredIfacesLock) { fout.print("Metered ifaces: "); fout.println(mMeteredIfaces); @@ -3898,6 +3913,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { fout.decreaseIndent(); } + size = mLowPowerStandbyAllowlistUids.size(); + if (size > 0) { + fout.println("Low Power Standby allowlist uids:"); + fout.increaseIndent(); + for (int i = 0; i < size; i++) { + fout.print("UID="); + fout.print(mLowPowerStandbyAllowlistUids.keyAt(i)); + fout.println(); + } + fout.decreaseIndent(); + } + final SparseBooleanArray knownUids = new SparseBooleanArray(); collectKeys(mUidState, knownUids); collectKeys(mUidBlockedState, knownUids); @@ -3979,6 +4006,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return isProcStateAllowedWhileIdleOrPowerSaveMode(uidState); } + @GuardedBy("mUidRulesFirstLock") + private boolean isUidTop(int uid) { + final UidState uidState = mUidState.get(uid); + return isProcStateAllowedWhileInLowPowerStandby(uidState); + } + /** * Process state of UID changed; if needed, will trigger * {@link #updateRulesForDataUsageRestrictionsUL(int)} and @@ -3995,8 +4028,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // state changed, push updated rules mUidState.put(uid, newUidState); updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, newUidState); - if (isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState) - != isProcStateAllowedWhileIdleOrPowerSaveMode(newUidState)) { + boolean allowedWhileIdleOrPowerSaveModeChanged = + isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState) + != isProcStateAllowedWhileIdleOrPowerSaveMode(newUidState); + if (allowedWhileIdleOrPowerSaveModeChanged) { updateRuleForAppIdleUL(uid); if (mDeviceIdleMode) { updateRuleForDeviceIdleUL(uid); @@ -4006,6 +4041,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } updateRulesForPowerRestrictionsUL(uid); } + if (mLowPowerStandbyActive) { + boolean allowedInLpsChanged = + isProcStateAllowedWhileInLowPowerStandby(oldUidState) + != isProcStateAllowedWhileInLowPowerStandby(newUidState); + if (allowedInLpsChanged) { + if (!allowedWhileIdleOrPowerSaveModeChanged) { + updateRulesForPowerRestrictionsUL(uid); + } + updateRuleForLowPowerStandbyUL(uid); + } + } return true; } } finally { @@ -4029,6 +4075,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { updateRuleForRestrictPowerUL(uid); } updateRulesForPowerRestrictionsUL(uid); + if (mLowPowerStandbyActive) { + updateRuleForLowPowerStandbyUL(uid); + } return true; } } @@ -4232,6 +4281,50 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } + @GuardedBy("mUidRulesFirstLock") + void updateRulesForLowPowerStandbyUL() { + Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForLowPowerStandbyUL"); + try { + if (mLowPowerStandbyActive) { + mUidFirewallLowPowerStandbyModeRules.clear(); + for (int i = mUidState.size() - 1; i >= 0; i--) { + final int uid = mUidState.keyAt(i); + UidBlockedState uidBlockedState = mUidBlockedState.get(uid); + if (hasInternetPermissionUL(uid) && uidBlockedState != null + && (uidBlockedState.effectiveBlockedReasons + & BLOCKED_REASON_LOW_POWER_STANDBY) == 0) { + mUidFirewallLowPowerStandbyModeRules.put(mUidBlockedState.keyAt(i), + FIREWALL_RULE_ALLOW); + } + } + setUidFirewallRulesUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, + mUidFirewallLowPowerStandbyModeRules, CHAIN_TOGGLE_ENABLE); + } else { + setUidFirewallRulesUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, null, CHAIN_TOGGLE_DISABLE); + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_NETWORK); + } + } + + @GuardedBy("mUidRulesFirstLock") + void updateRuleForLowPowerStandbyUL(int uid) { + if (!hasInternetPermissionUL(uid)) { + return; + } + + final UidBlockedState uidBlockedState = mUidBlockedState.get(uid); + if (mUidState.contains(uid) && uidBlockedState != null + && (uidBlockedState.effectiveBlockedReasons & BLOCKED_REASON_LOW_POWER_STANDBY) + == 0) { + mUidFirewallLowPowerStandbyModeRules.put(uid, FIREWALL_RULE_ALLOW); + setUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_ALLOW); + } else { + mUidFirewallLowPowerStandbyModeRules.delete(uid); + setUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_DEFAULT); + } + } + /** * Returns whether a uid is allowlisted from power saving restrictions (eg: Battery Saver, Doze * mode, and app idle). @@ -4261,6 +4354,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return mPowerSaveWhitelistExceptIdleAppIds.get(appId); } + /** + * Returns whether a uid is allowlisted from low power standby restrictions. + */ + @GuardedBy("mUidRulesFirstLock") + private boolean isAllowlistedFromLowPowerStandbyUL(int uid) { + return mLowPowerStandbyAllowlistUids.get(uid); + } + // NOTE: since both fw_dozable and fw_powersave uses the same map // (mPowerSaveTempWhitelistAppIds) for allowlisting, we can reuse their logic in this method. @GuardedBy("mUidRulesFirstLock") @@ -4598,6 +4699,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mPowerSaveTempWhitelistAppIds.delete(uid); mAppIdleTempWhitelistAppIds.delete(uid); mUidFirewallRestrictedModeRules.delete(uid); + mUidFirewallLowPowerStandbyModeRules.delete(uid); synchronized (mUidStateCallbackInfos) { mUidStateCallbackInfos.remove(uid); } @@ -4823,6 +4925,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid); + final boolean isTop = isUidTop(uid); final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid, mDeviceIdleMode); @@ -4836,17 +4939,21 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { int newAllowedReasons = ALLOWED_REASON_NONE; newBlockedReasons |= (mRestrictPower ? BLOCKED_REASON_BATTERY_SAVER : 0); newBlockedReasons |= (mDeviceIdleMode ? BLOCKED_REASON_DOZE : 0); + newBlockedReasons |= (mLowPowerStandbyActive ? BLOCKED_REASON_LOW_POWER_STANDBY : 0); newBlockedReasons |= (isUidIdle ? BLOCKED_REASON_APP_STANDBY : 0); newBlockedReasons |= (uidBlockedState.blockedReasons & BLOCKED_REASON_RESTRICTED_MODE); newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0); newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0); + newAllowedReasons |= (isTop ? ALLOWED_REASON_TOP : 0); newAllowedReasons |= (isWhitelistedFromPowerSaveUL(uid, true) ? ALLOWED_REASON_POWER_SAVE_ALLOWLIST : 0); newAllowedReasons |= (isWhitelistedFromPowerSaveExceptIdleUL(uid) ? ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST : 0); newAllowedReasons |= (uidBlockedState.allowedReasons & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS); + newAllowedReasons |= (isAllowlistedFromLowPowerStandbyUL(uid)) + ? ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST : 0; uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons & BLOCKED_METERED_REASON_MASK) | newBlockedReasons; @@ -4868,6 +4975,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { + ", mRestrictPower: " + mRestrictPower + ", mDeviceIdleMode: " + mDeviceIdleMode + ", isForeground=" + isForeground + + ", isTop=" + isTop + ", isWhitelisted=" + isWhitelisted + ", oldUidBlockedState=" + previousUidBlockedState.toString() + ", newUidBlockedState=" + uidBlockedState.toString()); @@ -5398,6 +5506,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mUidFirewallPowerSaveRules.put(uid, rule); } else if (chain == FIREWALL_CHAIN_RESTRICTED) { mUidFirewallRestrictedModeRules.put(uid, rule); + } else if (chain == FIREWALL_CHAIN_LOW_POWER_STANDBY) { + mUidFirewallLowPowerStandbyModeRules.put(uid, rule); } try { @@ -5445,6 +5555,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { .setFirewallUidRule(FIREWALL_CHAIN_POWERSAVE, uid, FIREWALL_RULE_DEFAULT); mNetworkManager .setFirewallUidRule(FIREWALL_CHAIN_RESTRICTED, uid, FIREWALL_RULE_DEFAULT); + mNetworkManager + .setFirewallUidRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, + FIREWALL_RULE_DEFAULT); mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false); mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false); } catch (IllegalStateException e) { @@ -5720,6 +5833,67 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mHandler.obtainMessage(MSG_METERED_RESTRICTED_PACKAGES_CHANGED, userId, 0, packageNames).sendToTarget(); } + + @Override + public void setLowPowerStandbyActive(boolean active) { + Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setLowPowerStandbyActive"); + try { + synchronized (mUidRulesFirstLock) { + if (mLowPowerStandbyActive == active) { + return; + } + mLowPowerStandbyActive = active; + synchronized (mNetworkPoliciesSecondLock) { + if (!mSystemReady) return; + } + + forEachUid("updateRulesForRestrictPower", + uid -> updateRulesForPowerRestrictionsUL(uid)); + updateRulesForLowPowerStandbyUL(); + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_NETWORK); + } + } + + @Override + public void setLowPowerStandbyAllowlist(int[] uids) { + synchronized (mUidRulesFirstLock) { + final SparseBooleanArray changedUids = new SparseBooleanArray(); + for (int i = 0; i < mLowPowerStandbyAllowlistUids.size(); i++) { + final int oldUid = mLowPowerStandbyAllowlistUids.keyAt(i); + if (!ArrayUtils.contains(uids, oldUid)) { + changedUids.put(oldUid, true); + } + } + + for (int i = 0; i < changedUids.size(); i++) { + final int deletedUid = changedUids.keyAt(i); + mLowPowerStandbyAllowlistUids.delete(deletedUid); + } + + for (int newUid : uids) { + if (mLowPowerStandbyAllowlistUids.indexOfKey(newUid) < 0) { + changedUids.append(newUid, true); + mLowPowerStandbyAllowlistUids.append(newUid, true); + } + } + + if (!mLowPowerStandbyActive) { + return; + } + + synchronized (mNetworkPoliciesSecondLock) { + if (!mSystemReady) return; + } + + for (int i = 0; i < changedUids.size(); i++) { + final int changedUid = changedUids.keyAt(i); + updateRulesForPowerRestrictionsUL(changedUid); + updateRuleForLowPowerStandbyUL(changedUid); + } + } + } } private void setMeteredRestrictedPackagesInternal(Set<String> packageNames, int userId) { @@ -5747,17 +5921,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - private int parseSubId(@NonNull NetworkStateSnapshot snapshot) { - int subId = INVALID_SUBSCRIPTION_ID; - if (snapshot.getNetworkCapabilities().hasTransport(TRANSPORT_CELLULAR)) { - NetworkSpecifier spec = snapshot.getNetworkCapabilities().getNetworkSpecifier(); - if (spec instanceof TelephonyNetworkSpecifier) { - subId = ((TelephonyNetworkSpecifier) spec).getSubscriptionId(); - } - } - return subId; - } - @GuardedBy("mNetworkPoliciesSecondLock") private int getSubIdLocked(Network network) { return mNetIdToSubId.get(network.getNetId(), INVALID_SUBSCRIPTION_ID); @@ -5888,6 +6051,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_DATA_SAVER; effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_USER_RESTRICTED; } + if ((allowedReasons & ALLOWED_REASON_TOP) != 0) { + effectiveBlockedReasons &= ~BLOCKED_REASON_LOW_POWER_STANDBY; + } if ((allowedReasons & ALLOWED_REASON_POWER_SAVE_ALLOWLIST) != 0) { effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER; effectiveBlockedReasons &= ~BLOCKED_REASON_DOZE; @@ -5903,6 +6069,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if ((allowedReasons & ALLOWED_METERED_REASON_USER_EXEMPTED) != 0) { effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_DATA_SAVER; } + if ((allowedReasons & ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST) != 0) { + effectiveBlockedReasons &= ~BLOCKED_REASON_LOW_POWER_STANDBY; + } + return effectiveBlockedReasons; } @@ -5927,6 +6097,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { BLOCKED_REASON_DOZE, BLOCKED_REASON_APP_STANDBY, BLOCKED_REASON_RESTRICTED_MODE, + BLOCKED_REASON_LOW_POWER_STANDBY, BLOCKED_METERED_REASON_DATA_SAVER, BLOCKED_METERED_REASON_USER_RESTRICTED, BLOCKED_METERED_REASON_ADMIN_DISABLED, @@ -5935,9 +6106,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int[] ALLOWED_REASONS = { ALLOWED_REASON_SYSTEM, ALLOWED_REASON_FOREGROUND, + ALLOWED_REASON_TOP, ALLOWED_REASON_POWER_SAVE_ALLOWLIST, ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST, ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS, + ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST, ALLOWED_METERED_REASON_USER_EXEMPTED, ALLOWED_METERED_REASON_SYSTEM, ALLOWED_METERED_REASON_FOREGROUND, @@ -5955,6 +6128,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return "APP_STANDBY"; case BLOCKED_REASON_RESTRICTED_MODE: return "RESTRICTED_MODE"; + case BLOCKED_REASON_LOW_POWER_STANDBY: + return "LOW_POWER_STANDBY"; case BLOCKED_METERED_REASON_DATA_SAVER: return "DATA_SAVER"; case BLOCKED_METERED_REASON_USER_RESTRICTED: @@ -5975,12 +6150,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return "SYSTEM"; case ALLOWED_REASON_FOREGROUND: return "FOREGROUND"; + case ALLOWED_REASON_TOP: + return "TOP"; case ALLOWED_REASON_POWER_SAVE_ALLOWLIST: return "POWER_SAVE_ALLOWLIST"; case ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST: return "POWER_SAVE_EXCEPT_IDLE_ALLOWLIST"; case ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS: return "RESTRICTED_MODE_PERMISSIONS"; + case ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST: + return "LOW_POWER_STANDBY_ALLOWLIST"; case ALLOWED_METERED_REASON_USER_EXEMPTED: return "METERED_USER_EXEMPTED"; case ALLOWED_METERED_REASON_SYSTEM: @@ -6047,7 +6226,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { int powerBlockedReasons = BLOCKED_REASON_APP_STANDBY | BLOCKED_REASON_DOZE - | BLOCKED_REASON_BATTERY_SAVER; + | BLOCKED_REASON_BATTERY_SAVER + | BLOCKED_REASON_LOW_POWER_STANDBY; if ((effectiveBlockedReasons & powerBlockedReasons) != 0) { uidRule |= RULE_REJECT_ALL; } else if ((blockedReasons & powerBlockedReasons) != 0) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 2717f0c4c8a4..3d34976adc27 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5805,6 +5805,7 @@ public class NotificationManagerService extends SystemService { || channel.isImportanceLockedByCriticalDeviceFunction()); final StatusBarNotification adjustedSbn = notificationRecord.getSbn(); userId = adjustedSbn.getUser().getIdentifier(); + int uid = adjustedSbn.getUid(); ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId); if (summaries == null) { summaries = new ArrayMap<>(); @@ -5851,7 +5852,7 @@ public class NotificationManagerService extends SystemService { notificationRecord.getIsAppImportanceLocked()); summaries.put(pkg, summarySbn.getKey()); } - if (summaryRecord != null && checkDisqualifyingFeatures(userId, MY_UID, + if (summaryRecord != null && checkDisqualifyingFeatures(userId, uid, summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord, true)) { return summaryRecord; @@ -6527,7 +6528,7 @@ public class NotificationManagerService extends SystemService { @VisibleForTesting protected void fixNotification(Notification notification, String pkg, String tag, int id, - int userId) throws NameNotFoundException { + int userId) throws NameNotFoundException, RemoteException { final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser( pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId); @@ -6561,6 +6562,21 @@ public class NotificationManagerService extends SystemService { actions.toArray(notification.actions); } + // Ensure MediaStyle has correct permissions for remote device extras + if (notification.isStyle(Notification.MediaStyle.class)) { + int hasMediaContentControlPermission = mPackageManager.checkPermission( + android.Manifest.permission.MEDIA_CONTENT_CONTROL, pkg, userId); + if (hasMediaContentControlPermission != PERMISSION_GRANTED) { + notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_DEVICE); + notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_ICON); + notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_INTENT); + if (DBG) { + Slog.w(TAG, "Package " + pkg + ": Use of setRemotePlayback requires the " + + "MEDIA_CONTENT_CONTROL permission"); + } + } + } + // Remote views? Are they too big? checkRemoteViews(pkg, tag, id, notification); } @@ -6822,7 +6838,6 @@ public class NotificationManagerService extends SystemService { return false; } - // blocked apps boolean isBlocked = !areNotificationsEnabledForPackageInt(pkg, uid); synchronized (mNotificationLock) { @@ -7215,10 +7230,12 @@ public class NotificationManagerService extends SystemService { if (mAssistants.isEnabled()) { mAssistants.onNotificationEnqueuedLocked(r); mHandler.postDelayed( - new PostNotificationRunnable(r.getKey(), enqueueElapsedTimeMs), + new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), enqueueElapsedTimeMs), DELAY_FOR_ASSISTANT_TIME); } else { - mHandler.post(new PostNotificationRunnable(r.getKey(), enqueueElapsedTimeMs)); + mHandler.post(new PostNotificationRunnable(r.getKey(), + r.getSbn().getPackageName(), r.getUid(), enqueueElapsedTimeMs)); } } } @@ -7242,16 +7259,19 @@ public class NotificationManagerService extends SystemService { protected class PostNotificationRunnable implements Runnable { private final String key; private final long postElapsedTimeMs; + private final String pkg; + private final int uid; - PostNotificationRunnable(String key, @ElapsedRealtimeLong long postElapsedTimeMs) { + PostNotificationRunnable(String key, String pkg, int uid, + @ElapsedRealtimeLong long postElapsedTimeMs) { this.key = key; + this.pkg = pkg; + this.uid = uid; this.postElapsedTimeMs = postElapsedTimeMs; } @Override public void run() { - String pkg = StatusBarNotification.getPkgFromKey(key); - int uid = StatusBarNotification.getUidFromKey(key); boolean appBanned = !areNotificationsEnabledForPackageInt(pkg, uid); synchronized (mNotificationLock) { try { diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java index 5d18069ea205..d80d9f3ab15a 100644 --- a/services/core/java/com/android/server/notification/PermissionHelper.java +++ b/services/core/java/com/android/server/notification/PermissionHelper.java @@ -178,8 +178,14 @@ public final class PermissionHelper { boolean userSet, boolean reviewRequired) { assertFlag(); final long callingId = Binder.clearCallingIdentity(); + // Do not change fixed permissions, and do not change non-user set permissions that are + // granted by default, or granted by role. + if (isPermissionFixed(packageName, userId) + || (isPermissionGrantedByDefaultOrRole(packageName, userId) && !userSet)) { + return; + } try { - if (grant) { + if (grant && !reviewRequired) { mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId); } else { mPermManager.revokeRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId, @@ -210,8 +216,10 @@ public final class PermissionHelper { if (pkgPerm == null || pkgPerm.packageName == null) { return; } - setNotificationPermission(pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted, - pkgPerm.userSet, !pkgPerm.userSet); + if (!isPermissionFixed(pkgPerm.packageName, pkgPerm.userId)) { + setNotificationPermission(pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted, + pkgPerm.userSet, !pkgPerm.userSet); + } } public boolean isPermissionFixed(String packageName, @UserIdInt int userId) { @@ -239,7 +247,26 @@ public final class PermissionHelper { try { int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION, userId); - return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0; + return (flags & (PackageManager.FLAG_PERMISSION_USER_SET + | PackageManager.FLAG_PERMISSION_USER_FIXED)) != 0; + } catch (RemoteException e) { + Slog.e(TAG, "Could not reach system server", e); + } + return false; + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + + boolean isPermissionGrantedByDefaultOrRole(String packageName, @UserIdInt int userId) { + assertFlag(); + final long callingId = Binder.clearCallingIdentity(); + try { + try { + int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION, + userId); + return (flags & (PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT + | PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE)) != 0; } catch (RemoteException e) { Slog.e(TAG, "Could not reach system server", e); } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index e8ad2bbf0dfc..11858904a69a 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -391,6 +391,7 @@ public class PreferencesHelper implements RankingConfig { if (migrateToPermission) { r.importance = appImportance; + r.migrateToPm = true; if (r.uid != UNKNOWN_UID) { // Don't call into permission system until we have a valid uid PackagePermission pkgPerm = new PackagePermission( @@ -2575,7 +2576,7 @@ public class PreferencesHelper implements RankingConfig { synchronized (mPackagePreferences) { mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r); } - if (mPermissionHelper.isMigrationEnabled()) { + if (mPermissionHelper.isMigrationEnabled() && r.migrateToPm) { try { PackagePermission p = new PackagePermission( r.pkg, UserHandle.getUserId(r.uid), @@ -2841,6 +2842,8 @@ public class PreferencesHelper implements RankingConfig { boolean userDemotedMsgApp = false; boolean hasSentValidBubble = false; + boolean migrateToPm = false; + Delegate delegate = null; ArrayMap<String, NotificationChannel> channels = new ArrayMap<>(); Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>(); diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 5b2e097d531e..4f1a9e726864 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -33,11 +33,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.SigningDetails; import android.content.pm.UserInfo; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProvider; import android.os.Binder; import android.os.Process; import android.os.Trace; @@ -62,6 +57,11 @@ import com.android.server.om.OverlayReferenceMapper; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedProvider; import com.android.server.utils.Snappable; import com.android.server.utils.SnapshotCache; import com.android.server.utils.Snapshots; @@ -158,6 +158,7 @@ public class AppsFilter implements Watchable, Snappable { private final FeatureConfig mFeatureConfig; private final OverlayReferenceMapper mOverlayReferenceMapper; private final StateProvider mStateProvider; + private final PackageManagerInternal mPmInternal; private SigningDetails mSystemSigningDetails; private Set<String> mProtectedBroadcasts = new ArraySet<>(); @@ -250,13 +251,15 @@ public class AppsFilter implements Watchable, Snappable { String[] forceQueryableList, boolean systemAppsQueryable, @Nullable OverlayReferenceMapper.Provider overlayProvider, - Executor backgroundExecutor) { + Executor backgroundExecutor, + PackageManagerInternal pmInternal) { mFeatureConfig = featureConfig; mForceQueryableByDevicePackageNames = forceQueryableList; mSystemAppsQueryable = systemAppsQueryable; mOverlayReferenceMapper = new OverlayReferenceMapper(true /*deferRebuild*/, overlayProvider); mStateProvider = stateProvider; + mPmInternal = pmInternal; mBackgroundExecutor = backgroundExecutor; mSnapshot = makeCache(); } @@ -289,6 +292,7 @@ public class AppsFilter implements Watchable, Snappable { } mBackgroundExecutor = null; + mPmInternal = null; mSnapshot = new SnapshotCache.Sealed<>(); } @@ -508,7 +512,7 @@ public class AppsFilter implements Watchable, Snappable { }; AppsFilter appsFilter = new AppsFilter(stateProvider, featureConfig, forcedQueryablePackageNames, forceSystemAppsQueryable, null, - injector.getBackgroundExecutor()); + injector.getBackgroundExecutor(), pms); featureConfig.setAppsFilter(appsFilter); return appsFilter; } @@ -1236,9 +1240,9 @@ public class AppsFilter implements Watchable, Snappable { // shared user members to re-establish visibility between them and other packages. // NOTE: this must come after all removals from data structures but before we update the // cache - if (setting.getSharedUser() != null) { - final ArraySet<? extends PackageStateInternal> sharedUserPackages = - setting.getSharedUser().getPackageStates(); + if (setting.hasSharedUser()) { + final ArraySet<PackageStateInternal> sharedUserPackages = + mPmInternal.getSharedUserPackages(setting.getSharedUserAppId()); for (int i = sharedUserPackages.size() - 1; i >= 0; i--) { if (sharedUserPackages.valueAt(i) == setting) { continue; @@ -1250,9 +1254,9 @@ public class AppsFilter implements Watchable, Snappable { synchronized (mCacheLock) { removeAppIdFromVisibilityCache(setting.getAppId()); - if (mShouldFilterCache != null && setting.getSharedUser() != null) { - final ArraySet<? extends PackageStateInternal> sharedUserPackages = - setting.getSharedUser().getPackageStates(); + if (mShouldFilterCache != null && setting.hasSharedUser()) { + final ArraySet<PackageStateInternal> sharedUserPackages = + mPmInternal.getSharedUserPackages(setting.getSharedUserAppId()); for (int i = sharedUserPackages.size() - 1; i >= 0; i--) { PackageStateInternal siblingSetting = sharedUserPackages.valueAt(i); @@ -1367,13 +1371,14 @@ public class AppsFilter implements Watchable, Snappable { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "callingSetting instanceof"); } if (callingSetting instanceof PackageStateInternal) { - if (((PackageStateInternal) callingSetting).getSharedUser() == null) { - callingPkgSetting = (PackageStateInternal) callingSetting; - callingSharedPkgSettings = null; - } else { + final PackageStateInternal packageState = (PackageStateInternal) callingSetting; + if (packageState.hasSharedUser()) { callingPkgSetting = null; - callingSharedPkgSettings = ((PackageStateInternal) callingSetting) - .getSharedUser().getPackageStates(); + callingSharedPkgSettings = mPmInternal.getSharedUserPackages( + packageState.getSharedUserAppId()); + } else { + callingPkgSetting = packageState; + callingSharedPkgSettings = null; } } else { callingPkgSetting = null; diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java index 0ae341833e34..0d9ccd2f5aa4 100644 --- a/services/core/java/com/android/server/pm/Computer.java +++ b/services/core/java/com/android/server/pm/Computer.java @@ -640,4 +640,12 @@ public interface Computer { @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) @Nullable Pair<PackageStateInternal, SharedUserApi> getPackageOrSharedUser(int appId); + + @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) + @Nullable + SharedUserApi getSharedUser(int sharedUserAppIde); + + @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) + @NonNull + ArraySet<PackageStateInternal> getSharedUserPackages(int sharedUserAppId); } diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index c942a4357900..04f0aab52e49 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -264,7 +264,7 @@ public class ComputerEngine implements Computer { } @Nullable - public SharedUserSetting getSharedUser(String name) { + public SharedUserSetting getSharedUserFromId(String name) { try { return mSettings.getSharedUserLPw(name, 0, 0, false /*create*/); } catch (PackageManagerException ignored) { @@ -298,6 +298,31 @@ public class ComputerEngine implements Computer { public Collection<SharedUserSetting> getAllSharedUsers() { return mSettings.getAllSharedUsersLPw(); } + + @Nullable + public SharedUserApi getSharedUserFromPackageName(String packageName) { + return mSettings.getSharedUserSettingLPr(packageName); + } + + @Nullable + public SharedUserApi getSharedUserFromAppId(int sharedUserAppId) { + return (SharedUserSetting) mSettings.getSettingLPr(sharedUserAppId); + } + + @NonNull + public ArraySet<PackageStateInternal> getSharedUserPackages(int sharedUserAppId) { + final ArraySet<PackageStateInternal> res = new ArraySet<>(); + final SharedUserSetting sharedUserSetting = + (SharedUserSetting) mSettings.getSettingLPr(sharedUserAppId); + if (sharedUserSetting != null) { + final ArraySet<? extends PackageStateInternal> sharedUserPackages = + sharedUserSetting.getPackageStates(); + for (PackageStateInternal ps : sharedUserPackages) { + res.add(ps); + } + } + return res; + } } private static final Comparator<ProviderInfo> sProviderInitOrderSorter = (p1, p2) -> { @@ -1570,7 +1595,8 @@ public class ComputerEngine implements Computer { PackageInfo pi = new PackageInfo(); pi.packageName = ps.getPackageName(); pi.setLongVersionCode(ps.getVersionCode()); - pi.sharedUserId = (ps.getSharedUser() != null) ? ps.getSharedUser().getName() : null; + SharedUserApi sharedUser = mSettings.getSharedUserFromPackageName(pi.packageName); + pi.sharedUserId = (sharedUser != null) ? sharedUser.getName() : null; pi.firstInstallTime = state.getFirstInstallTime(); pi.lastUpdateTime = ps.getLastUpdateTime(); @@ -2134,6 +2160,9 @@ public class ComputerEngine implements Computer { private String[] getPackagesForUidInternal(int uid, int callingUid) { final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null; final int userId = UserHandle.getUserId(uid); + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } final int appId = UserHandle.getAppId(uid); return getPackagesForUidInternalBody(callingUid, userId, appId, isCallerInstantApp); } @@ -2369,6 +2398,10 @@ public class ComputerEngine implements Computer { } public final boolean isCallerSameApp(String packageName, int uid) { + if (Process.isSupplemental(uid)) { + return (packageName != null + && packageName.equals(mService.getSupplementalProcessPackageName())); + } AndroidPackage pkg = mPackages.get(packageName); return pkg != null && UserHandle.getAppId(uid) == pkg.getUid(); @@ -4288,6 +4321,9 @@ public class ComputerEngine implements Computer { if (getInstantAppPackageName(callingUid) != null) { return null; } + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } final int callingUserId = UserHandle.getUserId(callingUid); final int appId = UserHandle.getAppId(uid); final Object obj = mSettings.getSettingBase(appId); @@ -4296,7 +4332,7 @@ public class ComputerEngine implements Computer { if (shouldFilterApplication(sus, callingUid, callingUserId)) { return null; } - return sus.name + ":" + sus.userId; + return sus.name + ":" + sus.mAppId; } else if (obj instanceof PackageSetting) { final PackageSetting ps = (PackageSetting) obj; if (shouldFilterApplication(ps, callingUid, callingUserId)) { @@ -4320,7 +4356,11 @@ public class ComputerEngine implements Computer { final int callingUserId = UserHandle.getUserId(callingUid); final String[] names = new String[uids.length]; for (int i = uids.length - 1; i >= 0; i--) { - final int appId = UserHandle.getAppId(uids[i]); + int uid = uids[i]; + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } + final int appId = UserHandle.getAppId(uid); final Object obj = mSettings.getSettingBase(appId); if (obj instanceof SharedUserSetting) { final SharedUserSetting sus = (SharedUserSetting) obj; @@ -4352,10 +4392,10 @@ public class ComputerEngine implements Computer { if (getInstantAppPackageName(callingUid) != null) { return Process.INVALID_UID; } - final SharedUserSetting suid = mSettings.getSharedUser(sharedUserName); + final SharedUserSetting suid = mSettings.getSharedUserFromId(sharedUserName); if (suid != null && !shouldFilterApplication(suid, callingUid, UserHandle.getUserId(callingUid))) { - return suid.userId; + return suid.mAppId; } return Process.INVALID_UID; } @@ -4366,6 +4406,9 @@ public class ComputerEngine implements Computer { if (getInstantAppPackageName(callingUid) != null) { return 0; } + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } final int callingUserId = UserHandle.getUserId(callingUid); final int appId = UserHandle.getAppId(uid); final Object obj = mSettings.getSettingBase(appId); @@ -4391,6 +4434,9 @@ public class ComputerEngine implements Computer { if (getInstantAppPackageName(callingUid) != null) { return 0; } + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } final int callingUserId = UserHandle.getUserId(callingUid); final int appId = UserHandle.getAppId(uid); final Object obj = mSettings.getSettingBase(appId); @@ -4415,6 +4461,9 @@ public class ComputerEngine implements Computer { if (getInstantAppPackageName(Binder.getCallingUid()) != null) { return false; } + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } final int appId = UserHandle.getAppId(uid); final Object obj = mSettings.getSettingBase(appId); if (obj instanceof SharedUserSetting) { @@ -5407,7 +5456,7 @@ public class ComputerEngine implements Computer { public SparseArray<String> getAppsWithSharedUserIds() { final SparseArray<String> sharedUserIds = new SparseArray<>(); for (SharedUserSetting setting : mSettings.getAllSharedUsers()) { - sharedUserIds.put(UserHandle.getAppId(setting.userId), setting.name); + sharedUserIds.put(UserHandle.getAppId(setting.mAppId), setting.name); } return sharedUserIds; } @@ -5417,12 +5466,12 @@ public class ComputerEngine implements Computer { public String[] getSharedUserPackagesForPackage(@NonNull String packageName, @UserIdInt int userId) { final PackageStateInternal packageSetting = mSettings.getPackage(packageName); - if (packageSetting == null || packageSetting.getSharedUser() == null) { + if (packageSetting == null || mSettings.getSharedUserFromPackageName(packageName) == null) { return EmptyArray.STRING; } ArraySet<? extends PackageStateInternal> packages = - packageSetting.getSharedUser().getPackageStates(); + mSettings.getSharedUserFromPackageName(packageName).getPackageStates(); final int numPackages = packages.size(); String[] res = new String[numPackages]; int i = 0; @@ -5539,6 +5588,9 @@ public class ComputerEngine implements Computer { @Override public int getUidTargetSdkVersion(int uid) { + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } final int appId = UserHandle.getAppId(uid); final SettingBase settingBase = mSettings.getSettingBase(appId); if (settingBase instanceof SharedUserSetting) { @@ -5565,6 +5617,9 @@ public class ComputerEngine implements Computer { @Nullable @Override public ArrayMap<String, ProcessInfo> getProcessesForUid(int uid) { + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } final int appId = UserHandle.getAppId(uid); final SettingBase settingBase = mSettings.getSettingBase(appId); if (settingBase instanceof SharedUserSetting) { @@ -5594,4 +5649,20 @@ public class ComputerEngine implements Computer { return null; } } + + private int getSupplementalProcessUid() { + return getPackage(mService.getSupplementalProcessPackageName()).getUid(); + } + + @Nullable + @Override + public SharedUserApi getSharedUser(int sharedUserAppId) { + return mSettings.getSharedUserFromAppId(sharedUserAppId); + } + + @NonNull + @Override + public ArraySet<PackageStateInternal> getSharedUserPackages(int sharedUserAppId) { + return mSettings.getSharedUserPackages(sharedUserAppId); + } } diff --git a/services/core/java/com/android/server/pm/ComputerLocked.java b/services/core/java/com/android/server/pm/ComputerLocked.java index 40d4c036c964..583348b3e337 100644 --- a/services/core/java/com/android/server/pm/ComputerLocked.java +++ b/services/core/java/com/android/server/pm/ComputerLocked.java @@ -852,4 +852,20 @@ public final class ComputerLocked extends ComputerEngine { return super.getPackageOrSharedUser(appId); } } + + @Nullable + @Override + public SharedUserApi getSharedUser(int sharedUserAppId) { + synchronized (mLock) { + return super.getSharedUser(sharedUserAppId); + } + } + + @NonNull + @Override + public ArraySet<PackageStateInternal> getSharedUserPackages(int sharedUserAppId) { + synchronized (mLock) { + return super.getSharedUserPackages(sharedUserAppId); + } + } } diff --git a/services/core/java/com/android/server/pm/ComputerTracker.java b/services/core/java/com/android/server/pm/ComputerTracker.java index 24c08d1c8d18..72e67da9e184 100644 --- a/services/core/java/com/android/server/pm/ComputerTracker.java +++ b/services/core/java/com/android/server/pm/ComputerTracker.java @@ -1283,4 +1283,20 @@ public final class ComputerTracker implements Computer { return current.mComputer.getPackageOrSharedUser(appId); } } + + @Nullable + @Override + public SharedUserApi getSharedUser(int sharedUserAppId) { + try (ThreadComputer current = snapshot()) { + return current.mComputer.getSharedUser(sharedUserAppId); + } + } + + @NonNull + @Override + public ArraySet<PackageStateInternal> getSharedUserPackages(int sharedUserAppId) { + try (ThreadComputer current = snapshot()) { + return current.mComputer.getSharedUserPackages(sharedUserAppId); + } + } } diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 3fb4ab1fefc8..3220b3171178 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -467,13 +467,14 @@ final class DeletePackageHelper { private void clearPackageStateForUserLIF(PackageSetting ps, int userId, PackageRemovedInfo outInfo, int flags) { final AndroidPackage pkg; + final SharedUserSetting sus; synchronized (mPm.mLock) { pkg = mPm.mPackages.get(ps.getPackageName()); + sus = mPm.mSettings.getSharedUserSettingLPr(ps); } mAppDataHelper.destroyAppProfilesLIF(pkg); - final SharedUserSetting sus = ps.getSharedUser(); final List<AndroidPackage> sharedUserPkgs = sus != null ? sus.getPackages() : Collections.emptyList(); final PreferredActivityHelper preferredActivityHelper = new PreferredActivityHelper(mPm); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index c418a106d93b..684e6ba5cf31 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -86,7 +86,6 @@ import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists; import static com.android.server.pm.PackageManagerServiceUtils.deriveAbiOverride; import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; -import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures; import android.annotation.NonNull; import android.annotation.Nullable; @@ -254,15 +253,21 @@ final class InstallPackageHelper { final String realPkgName = request.mRealPkgName; final List<String> changedAbiCodePath = result.mChangedAbiCodePath; final PackageSetting pkgSetting; - if (request.mPkgSetting != null && request.mPkgSetting.getSharedUser() != null - && request.mPkgSetting.getSharedUser() != result.mPkgSetting.getSharedUser()) { - // shared user changed, remove from old shared user - final SharedUserSetting sus = request.mPkgSetting.getSharedUser(); - sus.removePackage(request.mPkgSetting); - // Prune unused SharedUserSetting - if (mPm.mSettings.checkAndPruneSharedUserLPw(sus, false)) { - // Set the app ID in removed info for UID_REMOVED broadcasts - reconciledPkg.mInstallResult.mRemovedInfo.mRemovedAppId = sus.userId; + if (request.mPkgSetting != null) { + SharedUserSetting requestSharedUserSetting = mPm.mSettings.getSharedUserSettingLPr( + request.mPkgSetting); + SharedUserSetting resultSharedUserSetting = mPm.mSettings.getSharedUserSettingLPr( + result.mPkgSetting); + if (requestSharedUserSetting != null + && requestSharedUserSetting != resultSharedUserSetting) { + // shared user changed, remove from old shared user + requestSharedUserSetting.removePackage(request.mPkgSetting); + // Prune unused SharedUserSetting + if (mPm.mSettings.checkAndPruneSharedUserLPw(requestSharedUserSetting, false)) { + // Set the app ID in removed info for UID_REMOVED broadcasts + reconciledPkg.mInstallResult.mRemovedInfo.mRemovedAppId = + requestSharedUserSetting.mAppId; + } } } if (result.mExistingSettingCopied) { @@ -279,8 +284,9 @@ final class InstallPackageHelper { mPm.mSettings.removeRenamedPackageLPw(parsedPackage.getPackageName()); } } - if (pkgSetting.getSharedUser() != null) { - pkgSetting.getSharedUser().addPackage(pkgSetting); + SharedUserSetting sharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(pkgSetting); + if (sharedUserSetting != null) { + sharedUserSetting.addPackage(pkgSetting); } if (reconciledPkg.mInstallArgs != null && reconciledPkg.mInstallArgs.mForceQueryableOverride) { @@ -327,8 +333,8 @@ final class InstallPackageHelper { ksms.removeAppKeySetDataLPw(pkg.getPackageName()); } if (reconciledPkg.mSharedUserSignaturesChanged) { - pkgSetting.getSharedUser().signaturesChanged = Boolean.TRUE; - pkgSetting.getSharedUser().signatures.mSigningDetails = reconciledPkg.mSigningDetails; + sharedUserSetting.signaturesChanged = Boolean.TRUE; + sharedUserSetting.signatures.mSigningDetails = reconciledPkg.mSigningDetails; } pkgSetting.setSigningDetails(reconciledPkg.mSigningDetails); @@ -963,7 +969,7 @@ final class InstallPackageHelper { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages"); reconciledPackages = ReconcilePackageUtils.reconcilePackages( reconcileRequest, mSharedLibraries, - mPm.mSettings.getKeySetManagerService()); + mPm.mSettings.getKeySetManagerService(), mPm.mSettings); } catch (ReconcileFailure e) { for (InstallRequest request : requests) { request.mInstallResult.setError("Reconciliation failed...", e); @@ -1242,7 +1248,10 @@ final class InstallPackageHelper { // we'll check this again later when scanning, but we want to // bail early here before tripping over redefined permissions. final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService(); - if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) { + final SharedUserSetting signatureCheckSus = mPm.mSettings.getSharedUserSettingLPr( + signatureCheckPs); + if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, signatureCheckSus, + scanFlags)) { if (!ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) { throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package " + parsedPackage.getPackageName() + " upgrade keys do not match the " @@ -1257,7 +1266,9 @@ final class InstallPackageHelper { ReconcilePackageUtils.isRecoverSignatureUpdateNeeded( mPm.getSettingsVersionForPackage(parsedPackage)); // We don't care about disabledPkgSetting on install for now. - final boolean compatMatch = verifySignatures(signatureCheckPs, null, + final boolean compatMatch = + PackageManagerServiceUtils.verifySignatures(signatureCheckPs, + signatureCheckSus, null, parsedPackage.getSigningDetails(), compareCompat, compareRecover, isRollback); // The new KeySets will be re-added later in the scanning process. @@ -1496,6 +1507,7 @@ final class InstallPackageHelper { int targetParseFlags = parseFlags; final PackageSetting ps; final PackageSetting disabledPs; + final SharedUserSetting sharedUserSetting; if (replace) { final String pkgName11 = parsedPackage.getPackageName(); synchronized (mPm.mLock) { @@ -1527,10 +1539,11 @@ final class InstallPackageHelper { ps = mPm.mSettings.getPackageLPr(pkgName11); disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(ps); + sharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(ps); // verify signatures are valid final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService(); - if (ksms.shouldCheckUpgradeKeySetLocked(ps, scanFlags)) { + if (ksms.shouldCheckUpgradeKeySetLocked(ps, sharedUserSetting, scanFlags)) { if (!ksms.checkUpgradeKeySetLocked(ps, parsedPackage)) { throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "New package not signed by keys specified by upgrade-keysets: " @@ -1743,16 +1756,18 @@ final class InstallPackageHelper { final PackageSetting sourcePackageSetting; final KeySetManagerService ksms; + final SharedUserSetting sharedUserSetting; synchronized (mPm.mLock) { sourcePackageSetting = mPm.mSettings.getPackageLPr(sourcePackageName); ksms = mPm.mSettings.getKeySetManagerService(); + sharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(sourcePackageSetting); } final SigningDetails sourceSigningDetails = (sourcePackageSetting == null ? SigningDetails.UNKNOWN : sourcePackageSetting.getSigningDetails()); if (sourcePackageName.equals(parsedPackage.getPackageName()) && (ksms.shouldCheckUpgradeKeySetLocked( - sourcePackageSetting, scanFlags))) { + sourcePackageSetting, sharedUserSetting, scanFlags))) { return ksms.checkUpgradeKeySetLocked(sourcePackageSetting, parsedPackage); } else { @@ -3574,7 +3589,8 @@ final class InstallPackageHelper { mPm.getSettingsVersionForPackage(parsedPackage))); final Map<String, ReconciledPackage> reconcileResult = ReconcilePackageUtils.reconcilePackages(reconcileRequest, - mSharedLibraries, mPm.mSettings.getKeySetManagerService()); + mSharedLibraries, mPm.mSettings.getKeySetManagerService(), + mPm.mSettings); appIdCreated = optimisticallyRegisterAppId(scanResult); commitReconciledScanResultLocked(reconcileResult.get(pkgName), mPm.mUserManager.getUserIds()); @@ -3658,6 +3674,7 @@ final class InstallPackageHelper { final PackageSetting installedPkgSetting; final PackageSetting originalPkgSetting; final SharedUserSetting sharedUserSetting; + SharedUserSetting oldSharedUserSetting = null; synchronized (mPm.mLock) { platformPackage = mPm.getPlatformPackage(); @@ -3683,17 +3700,21 @@ final class InstallPackageHelper { && (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 && sharedUserSetting != null) { Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId() - + " (uid=" + sharedUserSetting.userId + "):" + + " (uid=" + sharedUserSetting.mAppId + "):" + " packages=" + sharedUserSetting.packages); } + if (installedPkgSetting != null) { + oldSharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(installedPkgSetting); + } } final boolean isPlatformPackage = platformPackage != null && platformPackage.getPackageName().equals(parsedPackage.getPackageName()); - return new ScanRequest(parsedPackage, sharedUserSetting, + return new ScanRequest(parsedPackage, oldSharedUserSetting, installedPkgSetting == null ? null : installedPkgSetting.getPkg() /* oldPkg */, installedPkgSetting /* packageSetting */, + sharedUserSetting, disabledPkgSetting /* disabledPackageSetting */, originalPkgSetting /* originalPkgSetting */, realPkgName, parseFlags, scanFlags, isPlatformPackage, user, cpuAbiOverride); @@ -3703,8 +3724,8 @@ final class InstallPackageHelper { private ScanResult scanPackageNewLI(@NonNull ParsedPackage parsedPackage, final @ParsingPackageUtils.ParseFlags int parseFlags, @PackageManagerService.ScanFlags int scanFlags, long currentTime, - @Nullable UserHandle user, String cpuAbiOverride) throws PackageManagerException { - + @Nullable UserHandle user, String cpuAbiOverride) + throws PackageManagerException { final ScanRequest initialScanRequest = prepareInitialScanRequest(parsedPackage, parseFlags, scanFlags, user, cpuAbiOverride); final PackageSetting installedPkgSetting = initialScanRequest.mPkgSetting; @@ -3725,8 +3746,9 @@ final class InstallPackageHelper { synchronized (mPm.mLock) { assertPackageIsValid(parsedPackage, parseFlags, newScanFlags); final ScanRequest request = new ScanRequest(parsedPackage, - initialScanRequest.mSharedUserSetting, - initialScanRequest.mOldPkg, installedPkgSetting, disabledPkgSetting, + initialScanRequest.mOldSharedUserSetting, + initialScanRequest.mOldPkg, installedPkgSetting, + initialScanRequest.mSharedUserSetting, disabledPkgSetting, initialScanRequest.mOriginalPkgSetting, initialScanRequest.mRealPkgName, parseFlags, scanFlags, initialScanRequest.mIsPlatformPackage, user, cpuAbiOverride); @@ -3775,8 +3797,9 @@ final class InstallPackageHelper { if (scanSystemPartition && isSystemPkgUpdated) { // we're updating the disabled package, so, scan it as the package setting final ScanRequest request = new ScanRequest(parsedPackage, - initialScanRequest.mSharedUserSetting, + mPm.mSettings.getSharedUserSettingLPr(disabledPkgSetting), null, disabledPkgSetting /* pkgSetting */, + initialScanRequest.mSharedUserSetting, null /* disabledPkgSetting */, null /* originalPkgSetting */, null, parseFlags, scanFlags, initialScanRequest.mIsPlatformPackage, user, null); @@ -3994,12 +4017,14 @@ final class InstallPackageHelper { if (!verifyPackageUpdateLPr(originalPs, pkg)) { // the new package is incompatible with the original continue; - } else if (originalPs.getSharedUser() != null) { - if (!originalPs.getSharedUser().name.equals(pkg.getSharedUserId())) { + } else if (mPm.mSettings.getSharedUserSettingLPr(originalPs) != null) { + final String sharedUserSettingsName = + mPm.mSettings.getSharedUserSettingLPr(originalPs).name; + if (!sharedUserSettingsName.equals(pkg.getSharedUserId())) { // the shared user id is incompatible with the original Slog.w(TAG, "Unable to migrate data from " + originalPs.getPackageName() - + " to " + pkg.getPackageName() + ": old uid " - + originalPs.getSharedUser().name + + " to " + pkg.getPackageName() + ": old shared user settings name " + + sharedUserSettingsName + " differs from " + pkg.getSharedUserId()); continue; } diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java index afca35081659..5c298330cd5c 100644 --- a/services/core/java/com/android/server/pm/KeySetManagerService.java +++ b/services/core/java/com/android/server/pm/KeySetManagerService.java @@ -32,6 +32,7 @@ import android.util.TypedXmlSerializer; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.SharedUserApi; import com.android.server.utils.WatchedArrayMap; import org.xmlpull.v1.XmlPullParser; @@ -354,9 +355,10 @@ public class KeySetManagerService { return mKeySets.get(id) != null; } - public boolean shouldCheckUpgradeKeySetLocked(PackageStateInternal oldPs, int scanFlags) { + public boolean shouldCheckUpgradeKeySetLocked(PackageStateInternal oldPs, + SharedUserApi sharedUserSetting, int scanFlags) { // Can't rotate keys during boot or if sharedUser. - if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || (oldPs.getSharedUser() != null) + if (oldPs == null || (scanFlags & SCAN_INITIAL) != 0 || (sharedUserSetting != null) || !oldPs.getKeySetData().isUsingUpgradeKeySets()) { return false; } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index e65783865659..6b3ce773fb63 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME; import static android.app.PendingIntent.FLAG_IMMUTABLE; +import static android.app.PendingIntent.FLAG_MUTABLE; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; @@ -1218,8 +1219,9 @@ public class LauncherAppsService extends SystemService { } @Override - public PendingIntent getActivityLaunchIntent(ComponentName component, Bundle opts, + public PendingIntent getActivityLaunchIntent(String callingPackage, ComponentName component, UserHandle user) { + ensureShortcutPermission(callingPackage); if (!canAccessProfile(user.getIdentifier(), "Cannot start activity")) { throw new ActivityNotFoundException("Activity could not be found"); } @@ -1237,7 +1239,7 @@ public class LauncherAppsService extends SystemService { // calling identity to mirror the startActivityAsUser() call which does not validate // the calling user return PendingIntent.getActivityAsUser(mContext, 0 /* requestCode */, launchIntent, - FLAG_IMMUTABLE, null /* options */, user); + FLAG_MUTABLE, null /* opts */, user); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ee5c6385429a..5c68fac7715e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1515,7 +1515,8 @@ public class PackageManagerService extends IPackageManager.Stub return; } AndroidPackage pkg = packageState.getPkg(); - SharedUserApi sharedUser = packageState.getSharedUser(); + SharedUserApi sharedUser = m.mComputer.getSharedUser( + packageState.getSharedUserAppId()); String oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, packageState); if (pkg == null) { @@ -7557,7 +7558,7 @@ public class PackageManagerService extends IPackageManager.Stub if (packageState == null) { return new ArraySet<>(); } - return packageState.getUserStateOrDefault(userId).getEnabledComponentsNoCopy(); + return packageState.getUserStateOrDefault(userId).getEnabledComponents(); } @Override @@ -7566,7 +7567,7 @@ public class PackageManagerService extends IPackageManager.Stub if (packageState == null) { return new ArraySet<>(); } - return packageState.getUserStateOrDefault(userId).getDisabledComponentsNoCopy(); + return packageState.getUserStateOrDefault(userId).getDisabledComponents(); } @Override @@ -7867,6 +7868,18 @@ public class PackageManagerService extends IPackageManager.Stub migrateAppsData); } + @Override + @NonNull + public ArraySet<PackageStateInternal> getSharedUserPackages(int sharedUserAppId) { + return PackageManagerService.this.mComputer.getSharedUserPackages(sharedUserAppId); + } + + @Override + @Nullable + public SharedUserApi getSharedUserApi(int sharedUserAppId) { + return mComputer.getSharedUser(sharedUserAppId); + } + @NonNull @Override public PackageStateMutator.InitialState recordInitialState() { diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index d6340b5bc811..0dae12f43236 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -505,6 +505,7 @@ public class PackageManagerServiceUtils { * @throws PackageManagerException if the signatures did not match. */ public static boolean verifySignatures(PackageSetting pkgSetting, + @Nullable SharedUserSetting sharedUserSetting, PackageSetting disabledPkgSetting, SigningDetails parsedSignatures, boolean compareCompat, boolean compareRecover, boolean isRollback) throws PackageManagerException { @@ -555,10 +556,8 @@ public class PackageManagerServiceUtils { } } // Check for shared user signatures - if (pkgSetting.getSharedUser() != null - && pkgSetting.getSharedUser().signatures.mSigningDetails - != SigningDetails.UNKNOWN) { - + if (sharedUserSetting != null + && sharedUserSetting.getSigningDetails() != SigningDetails.UNKNOWN) { // Already existing package. Make sure signatures match. In case of signing certificate // rotation, the packages with newer certs need to be ok with being sharedUserId with // the older ones. We check to see if either the new package is signed by an older cert @@ -566,32 +565,32 @@ public class PackageManagerServiceUtils { // with being sharedUser with the existing signing cert. boolean match = parsedSignatures.checkCapability( - pkgSetting.getSharedUser().signatures.mSigningDetails, + sharedUserSetting.getSigningDetails(), SigningDetails.CertCapabilities.SHARED_USER_ID) - || pkgSetting.getSharedUser().signatures.mSigningDetails.checkCapability( + || sharedUserSetting.getSigningDetails().checkCapability( parsedSignatures, SigningDetails.CertCapabilities.SHARED_USER_ID); // Special case: if the sharedUserId capability check failed it could be due to this // being the only package in the sharedUserId so far and the lineage being updated to // deny the sharedUserId capability of the previous key in the lineage. - if (!match && pkgSetting.getSharedUser().packages.size() == 1 - && pkgSetting.getSharedUser().packages + if (!match && sharedUserSetting.packages.size() == 1 + && sharedUserSetting.packages .valueAt(0).getPackageName().equals(packageName)) { match = true; } if (!match && compareCompat) { match = matchSignaturesCompat( - packageName, pkgSetting.getSharedUser().signatures, parsedSignatures); + packageName, sharedUserSetting.signatures, parsedSignatures); } if (!match && compareRecover) { match = matchSignaturesRecover(packageName, - pkgSetting.getSharedUser().signatures.mSigningDetails, + sharedUserSetting.signatures.mSigningDetails, parsedSignatures, SigningDetails.CertCapabilities.SHARED_USER_ID) || matchSignaturesRecover(packageName, parsedSignatures, - pkgSetting.getSharedUser().signatures.mSigningDetails, + sharedUserSetting.signatures.mSigningDetails, SigningDetails.CertCapabilities.SHARED_USER_ID); compatMatch |= match; } @@ -599,14 +598,14 @@ public class PackageManagerServiceUtils { throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE, "Package " + packageName + " has no signatures that match those in shared user " - + pkgSetting.getSharedUser().name + "; ignoring!"); + + sharedUserSetting.name + "; ignoring!"); } // It is possible that this package contains a lineage that blocks sharedUserId access // to an already installed package in the sharedUserId signed with a previous key. // Iterate over all of the packages in the sharedUserId and ensure any that are signed // with a key in this package's lineage have the SHARED_USER_ID capability granted. if (parsedSignatures.hasPastSigningCertificates()) { - for (PackageSetting shUidPkgSetting : pkgSetting.getSharedUser().packages) { + for (PackageSetting shUidPkgSetting : sharedUserSetting.packages) { // if the current package in the sharedUserId is the package being updated then // skip this check as the update may revoke the sharedUserId capability from // the key with which this app was previously signed. @@ -633,7 +632,7 @@ public class PackageManagerServiceUtils { // If the lineage of this package diverges from the lineage of the sharedUserId then // do not allow the installation to proceed. if (!parsedSignatures.hasCommonAncestor( - pkgSetting.getSharedUser().signatures.mSigningDetails)) { + sharedUserSetting.signatures.mSigningDetails)) { throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE, "Package " + packageName + " has a signing lineage " + "that diverges from the lineage of the sharedUserId"); diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index bd32d03f8b2c..f06ae1e06187 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -52,6 +52,7 @@ import com.android.server.pm.pkg.PackageUserStateImpl; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.SuspendParams; import com.android.server.utils.SnapshotCache; +import com.android.server.utils.WatchedArraySet; import libcore.util.EmptyArray; @@ -76,12 +77,9 @@ import java.util.UUID; public class PackageSetting extends SettingBase implements PackageStateInternal { /** - * Temporary holding space for the shared user ID. While parsing package settings, the - * shared users tag may come after the packages. In this case, we must delay linking the - * shared user setting with the package setting. The shared user ID lets us link the - * two objects. + * The shared user ID lets us link this object to {@link SharedUserSetting}. */ - private int sharedUserId; + private int mSharedUserAppId; @Nullable private Map<String, Set<String>> mimeGroups; @@ -130,13 +128,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal @Nullable private AndroidPackage pkg; - /** - * WARNING. The object reference is important. We perform integer equality and NOT - * object equality to check whether shared user settings are the same. - */ - @Nullable - private SharedUserSetting sharedUser; - /** @see AndroidPackage#getPath() */ @NonNull private File mPath; @@ -210,7 +201,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal String legacyNativeLibraryPath, String primaryCpuAbi, String secondaryCpuAbi, String cpuAbiOverride, long longVersionCode, int pkgFlags, int pkgPrivateFlags, - int sharedUserId, + int sharedUserAppId, String[] usesSdkLibraries, long[] usesSdkLibrariesVersionsMajor, String[] usesStaticLibraries, long[] usesStaticLibrariesVersions, Map<String, Set<String>> mimeGroups, @@ -231,7 +222,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal this.versionCode = longVersionCode; this.signatures = new PackageSignatures(); this.installSource = InstallSource.EMPTY; - this.sharedUserId = sharedUserId; + this.mSharedUserAppId = sharedUserAppId; mDomainSetId = domainSetId; copyMimeGroups(mimeGroups); mSnapshot = makeCache(); @@ -259,9 +250,8 @@ public class PackageSetting extends SettingBase implements PackageStateInternal PackageSetting(@NonNull PackageSetting original, boolean sealedSnapshot) { super(original); - copyPackageSetting(original); + copyPackageSetting(original, sealedSnapshot); if (sealedSnapshot) { - sharedUser = sharedUser == null ? null : sharedUser.snapshot(); mSnapshot = new SnapshotCache.Sealed(); } else { mSnapshot = makeCache(); @@ -313,10 +303,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal proto.end(packageToken); } - public boolean isSharedUser() { - return sharedUser != null; - } - public PackageSetting setAppId(int appId) { this.mAppId = appId; onChanged(); @@ -489,11 +475,18 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return this; } - public int getSharedUserIdInt() { - if (sharedUser != null) { - return sharedUser.userId; - } - return sharedUserId; + public void setSharedUserAppId(int sharedUserAppId) { + mSharedUserAppId = sharedUserAppId; + } + + @Override + public int getSharedUserAppId() { + return mSharedUserAppId; + } + + @Override + public boolean hasSharedUser() { + return mSharedUserAppId > 0; } @Override @@ -523,7 +516,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal /** Updates all fields in the current setting from another. */ public void updateFrom(PackageSetting other) { - copyPackageSetting(other); + copyPackageSetting(other, false /* sealedSnapshot */); Set<String> mimeGroupNames = other.mimeGroups != null ? other.mimeGroups.keySet() : null; updateMimeGroups(mimeGroupNames); @@ -564,9 +557,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal @Deprecated @Override public LegacyPermissionState getLegacyPermissionState() { - return (sharedUser != null) - ? sharedUser.getLegacyPermissionState() - : super.getLegacyPermissionState(); + return super.getLegacyPermissionState(); } public PackageSetting setInstallPermissionsFixed(boolean installPermissionsFixed) { @@ -618,16 +609,15 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return this; } - public void copyPackageSetting(PackageSetting other) { + public void copyPackageSetting(PackageSetting other, boolean sealedSnapshot) { super.copySettingBase(other); - sharedUserId = other.sharedUserId; + mSharedUserAppId = other.mSharedUserAppId; mLoadingProgress = other.mLoadingProgress; legacyNativeLibraryPath = other.legacyNativeLibraryPath; mName = other.mName; mRealName = other.mRealName; mAppId = other.mAppId; pkg = other.pkg; - sharedUser = other.sharedUser; mPath = other.mPath; mPathString = other.mPathString; mPrimaryCpuAbi = other.mPrimaryCpuAbi; @@ -661,8 +651,12 @@ public class PackageSetting extends SettingBase implements PackageStateInternal other.usesStaticLibrariesVersions.length) : null; mUserStates.clear(); for (int i = 0; i < other.mUserStates.size(); i++) { - mUserStates.put(other.mUserStates.keyAt(i), - new PackageUserStateImpl(this, other.mUserStates.valueAt(i))); + if (sealedSnapshot) { + mUserStates.put(other.mUserStates.keyAt(i), + other.mUserStates.valueAt(i).snapshot()); + } else { + mUserStates.put(other.mUserStates.keyAt(i), other.mUserStates.valueAt(i)); + } } if (mOldCodePaths != null) { @@ -882,42 +876,48 @@ public class PackageSetting extends SettingBase implements PackageStateInternal setUserState(userId, otherState.getCeDataInode(), otherState.getEnabledState(), otherState.isInstalled(), otherState.isStopped(), otherState.isNotLaunched(), otherState.isHidden(), otherState.getDistractionFlags(), - otherState.getSuspendParams(), otherState.isInstantApp(), + otherState.getSuspendParams() == null + ? null : otherState.getSuspendParams().untrackedStorage(), + otherState.isInstantApp(), otherState.isVirtualPreload(), otherState.getLastDisableAppCaller(), - new ArraySet<>(otherState.getEnabledComponentsNoCopy()), - new ArraySet<>(otherState.getDisabledComponentsNoCopy()), + otherState.getEnabledComponentsNoCopy() == null + ? null : otherState.getEnabledComponentsNoCopy().untrackedStorage(), + otherState.getDisabledComponentsNoCopy() == null + ? null : otherState.getDisabledComponentsNoCopy().untrackedStorage(), otherState.getInstallReason(), otherState.getUninstallReason(), otherState.getHarmfulAppWarning(), otherState.getSplashScreenTheme(), otherState.getFirstInstallTime()); } - ArraySet<String> getEnabledComponents(int userId) { + WatchedArraySet<String> getEnabledComponents(int userId) { return readUserState(userId).getEnabledComponentsNoCopy(); } - ArraySet<String> getDisabledComponents(int userId) { + WatchedArraySet<String> getDisabledComponents(int userId) { return readUserState(userId).getDisabledComponentsNoCopy(); } - void setEnabledComponents(ArraySet<String> components, int userId) { + /** Test only */ + void setEnabledComponents(WatchedArraySet<String> components, int userId) { modifyUserState(userId).setEnabledComponents(components); onChanged(); } - void setDisabledComponents(ArraySet<String> components, int userId) { + /** Test only */ + void setDisabledComponents(WatchedArraySet<String> components, int userId) { modifyUserState(userId).setDisabledComponents(components); onChanged(); } - void setEnabledComponentsCopy(ArraySet<String> components, int userId) { + void setEnabledComponentsCopy(WatchedArraySet<String> components, int userId) { modifyUserState(userId).setEnabledComponents(components != null - ? new ArraySet<String>(components) : null); + ? components.untrackedStorage() : null); onChanged(); } - void setDisabledComponentsCopy(ArraySet<String> components, int userId) { + void setDisabledComponentsCopy(WatchedArraySet<String> components, int userId) { modifyUserState(userId).setDisabledComponents(components != null - ? new ArraySet<String>(components) : null); + ? components.untrackedStorage() : null); onChanged(); } @@ -1168,12 +1168,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return getPkg(); } - @Nullable - @Override - public int getSharedUserId() { - return sharedUser == null ? -1 : sharedUser.userId; - } - @NonNull public SigningInfo getSigningInfo() { return new SigningInfo(signatures.mSigningDetails); @@ -1238,12 +1232,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return this; } - public PackageSetting setSharedUser(SharedUserSetting sharedUser) { - this.sharedUser = sharedUser; - onChanged(); - return this; - } - public PackageSetting setCategoryOverride(int categoryHint) { this.categoryOverride = categoryHint; onChanged(); @@ -1377,15 +1365,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } /** - * WARNING. The object reference is important. We perform integer equality and NOT - * object equality to check whether shared user settings are the same. - */ - @DataClass.Generated.Member - public @Nullable SharedUserSetting getSharedUser() { - return sharedUser; - } - - /** * @see AndroidPackage#getPath() */ @DataClass.Generated.Member @@ -1488,10 +1467,10 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } @DataClass.Generated( - time = 1643648635766L, + time = 1644270960923L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java", - inputSignatures = "private int sharedUserId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackage pkg\nprivate @android.annotation.Nullable com.android.server.pm.SharedUserSetting sharedUser\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic boolean isSharedUser()\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.parsing.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic int getSharedUserIdInt()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n android.util.ArraySet<java.lang.String> getEnabledComponents(int)\n android.util.ArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(android.util.ArraySet<java.lang.String>,int)\n void setDisabledComponents(android.util.ArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackageApi getAndroidPackage()\npublic @android.annotation.Nullable @java.lang.Override int getSharedUserId()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<android.content.pm.SharedLibraryInfo> getUsesLibraryInfos()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setSharedUser(com.android.server.pm.SharedUserSetting)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") + inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackage pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.parsing.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n android.util.ArraySet<java.lang.String> getEnabledComponents(int)\n android.util.ArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(android.util.ArraySet<java.lang.String>,int)\n void setDisabledComponents(android.util.ArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackageApi getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<android.content.pm.SharedLibraryInfo> getUsesLibraryInfos()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index f3d88edf40dd..0b69cd376bd1 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -22,19 +22,18 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTEN import static com.android.server.pm.PackageManagerService.SCAN_BOOTING; import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP; import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures; -import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.Signature; import android.content.pm.SigningDetails; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Log; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.utils.WatchedLongSparseArray; import java.util.List; @@ -43,7 +42,7 @@ import java.util.Map; final class ReconcilePackageUtils { public static Map<String, ReconciledPackage> reconcilePackages( final ReconcileRequest request, SharedLibrariesImpl sharedLibraries, - KeySetManagerService ksms) + KeySetManagerService ksms, Settings settings) throws ReconcileFailure { final Map<String, ScanResult> scannedPackages = request.mScannedPackages; @@ -121,7 +120,10 @@ final class ReconcilePackageUtils { boolean removeAppKeySetData = false; boolean sharedUserSignaturesChanged = false; SigningDetails signingDetails = null; - if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) { + SharedUserSetting sharedUserSetting = settings.getSharedUserSettingLPr( + signatureCheckPs); + if (ksms.shouldCheckUpgradeKeySetLocked( + signatureCheckPs, sharedUserSetting, scanFlags)) { if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) { // We just determined the app is signed correctly, so bring // over the latest parsed certs. @@ -139,6 +141,7 @@ final class ReconcilePackageUtils { } signingDetails = parsedPackage.getSigningDetails(); } else { + try { final Settings.VersionInfo versionInfo = request.mVersionInfos.get(installPackageName); @@ -146,9 +149,11 @@ final class ReconcilePackageUtils { final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo); final boolean isRollback = installArgs != null && installArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK; - final boolean compatMatch = verifySignatures(signatureCheckPs, - disabledPkgSetting, parsedPackage.getSigningDetails(), compareCompat, - compareRecover, isRollback); + final boolean compatMatch = + PackageManagerServiceUtils.verifySignatures(signatureCheckPs, + sharedUserSetting, disabledPkgSetting, + parsedPackage.getSigningDetails(), compareCompat, + compareRecover, isRollback); // The new KeySets will be re-added later in the scanning process. if (compatMatch) { removeAppKeySetData = true; @@ -161,21 +166,21 @@ final class ReconcilePackageUtils { // newer // signing certificate than the existing one, and if so, copy over the new // details - if (signatureCheckPs.getSharedUser() != null) { + if (sharedUserSetting != null) { // Attempt to merge the existing lineage for the shared SigningDetails with // the lineage of the new package; if the shared SigningDetails are not // returned this indicates the new package added new signers to the lineage // and/or changed the capabilities of existing signers in the lineage. SigningDetails sharedSigningDetails = - signatureCheckPs.getSharedUser().signatures.mSigningDetails; + sharedUserSetting.signatures.mSigningDetails; SigningDetails mergedDetails = sharedSigningDetails.mergeLineageWith( signingDetails); if (mergedDetails != sharedSigningDetails) { - signatureCheckPs.getSharedUser().signatures.mSigningDetails = + sharedUserSetting.signatures.mSigningDetails = mergedDetails; } - if (signatureCheckPs.getSharedUser().signaturesChanged == null) { - signatureCheckPs.getSharedUser().signaturesChanged = Boolean.FALSE; + if (sharedUserSetting.signaturesChanged == null) { + sharedUserSetting.signaturesChanged = Boolean.FALSE; } } } catch (PackageManagerException e) { @@ -192,10 +197,10 @@ final class ReconcilePackageUtils { // updating // the signatures on the first package scanned for the shared user (i.e. if the // signaturesChanged state hasn't been initialized yet in SharedUserSetting). - if (signatureCheckPs.getSharedUser() != null) { - final Signature[] sharedUserSignatures = signatureCheckPs.getSharedUser() + if (sharedUserSetting != null) { + final Signature[] sharedUserSignatures = sharedUserSetting .signatures.mSigningDetails.getSignatures(); - if (signatureCheckPs.getSharedUser().signaturesChanged != null + if (sharedUserSetting.signaturesChanged != null && compareSignatures(sharedUserSignatures, parsedPackage.getSigningDetails().getSignatures()) != PackageManager.SIGNATURE_MATCH) { @@ -209,7 +214,7 @@ final class ReconcilePackageUtils { throw new ReconcileFailure( INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Signature mismatch for shared user: " - + scanResult.mPkgSetting.getSharedUser()); + + sharedUserSetting); } else { // Treat mismatched signatures on system packages using a shared // UID as @@ -219,14 +224,14 @@ final class ReconcilePackageUtils { "Signature mismatch on system package " + parsedPackage.getPackageName() + " for shared user " - + scanResult.mPkgSetting.getSharedUser()); + + sharedUserSetting); } } sharedUserSignaturesChanged = true; - signatureCheckPs.getSharedUser().signatures.mSigningDetails = + sharedUserSetting.signatures.mSigningDetails = parsedPackage.getSigningDetails(); - signatureCheckPs.getSharedUser().signaturesChanged = Boolean.TRUE; + sharedUserSetting.signaturesChanged = Boolean.TRUE; } // File a report about this. String msg = "System package " + parsedPackage.getPackageName() diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 7e898cbe86b0..079903e09ba8 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -279,7 +279,7 @@ final class RemovePackageHelper { if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) { // If we don't have a disabled system package to reinstall, the package is // really gone and its permission state should be removed. - final SharedUserSetting sus = deletedPs.getSharedUser(); + SharedUserSetting sus = mPm.mSettings.getSharedUserSettingLPr(deletedPs); List<AndroidPackage> sharedUserPkgs = sus != null ? sus.getPackages() : Collections.emptyList(); mPermissionManager.onPackageUninstalled(packageName, deletedPs.getAppId(), diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 79ab563e4e6e..99d9d58fc4b6 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -125,6 +125,7 @@ final class ScanPackageUtils { final @ParsingPackageUtils.ParseFlags int parseFlags = request.mParseFlags; final @PackageManagerService.ScanFlags int scanFlags = request.mScanFlags; final String realPkgName = request.mRealPkgName; + final SharedUserSetting oldSharedUserSetting = request.mOldSharedUserSetting; final SharedUserSetting sharedUserSetting = request.mSharedUserSetting; final UserHandle user = request.mUser; final boolean isPlatformPackage = request.mIsPlatformPackage; @@ -165,18 +166,18 @@ final class ScanPackageUtils { int previousAppId = Process.INVALID_UID; - if (pkgSetting != null && pkgSetting.getSharedUser() != sharedUserSetting) { - if (pkgSetting.getSharedUser() != null && sharedUserSetting == null) { + if (pkgSetting != null && oldSharedUserSetting != sharedUserSetting) { + if (oldSharedUserSetting != null && sharedUserSetting == null) { previousAppId = pkgSetting.getAppId(); // Log that something is leaving shareduid and keep going Slog.i(TAG, "Package " + parsedPackage.getPackageName() + " shared user changed from " - + pkgSetting.getSharedUser().name + " to " + "<nothing>."); + + oldSharedUserSetting.name + " to " + "<nothing>."); } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Package " + parsedPackage.getPackageName() + " shared user changed from " - + (pkgSetting.getSharedUser() != null - ? pkgSetting.getSharedUser().name : "<nothing>") + + (oldSharedUserSetting != null + ? oldSharedUserSetting.name : "<nothing>") + " to " + (sharedUserSetting != null ? sharedUserSetting.name : "<nothing>") + "; replacing with new"); @@ -234,8 +235,8 @@ final class ScanPackageUtils { // TODO(narayan): This update is bogus. nativeLibraryDir & primaryCpuAbi, // secondaryCpuAbi are not known at this point so we always update them // to null here, only to reset them at a later point. - Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, sharedUserSetting, - destCodeFile, parsedPackage.getNativeLibraryDir(), + Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, oldSharedUserSetting, + sharedUserSetting, destCodeFile, parsedPackage.getNativeLibraryDir(), AndroidPackageUtils.getPrimaryCpuAbi(parsedPackage, pkgSetting), AndroidPackageUtils.getSecondaryCpuAbi(parsedPackage, pkgSetting), PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting), @@ -390,16 +391,16 @@ final class ScanPackageUtils { + " abiOverride=" + pkgSetting.getCpuAbiOverride()); } - if ((scanFlags & SCAN_BOOTING) == 0 && pkgSetting.getSharedUser() != null) { + if ((scanFlags & SCAN_BOOTING) == 0 && oldSharedUserSetting != null) { // We don't do this here during boot because we can do it all // at once after scanning all existing packages. // // We also do this *before* we perform dexopt on this package, so that // we can avoid redundant dexopts, and also to make sure we've got the // code and package path correct. - changedAbiCodePath = applyAdjustedAbiToSharedUser(pkgSetting.getSharedUser(), + changedAbiCodePath = applyAdjustedAbiToSharedUser(oldSharedUserSetting, parsedPackage, packageAbiHelper.getAdjustedAbiForSharedUser( - pkgSetting.getSharedUser().packages, parsedPackage)); + oldSharedUserSetting.packages, parsedPackage)); } parsedPackage.setFactoryTest(isUnderFactoryTest && parsedPackage.getRequestedPermissions() diff --git a/services/core/java/com/android/server/pm/ScanRequest.java b/services/core/java/com/android/server/pm/ScanRequest.java index 34abdb108068..98d11bd42495 100644 --- a/services/core/java/com/android/server/pm/ScanRequest.java +++ b/services/core/java/com/android/server/pm/ScanRequest.java @@ -32,14 +32,16 @@ final class ScanRequest { @NonNull public final ParsedPackage mParsedPackage; /** The package this package replaces */ @Nullable public final AndroidPackage mOldPkg; - /** Shared user settings, if the package has a shared user */ - @Nullable public final SharedUserSetting mSharedUserSetting; + /** Shared user settings, if the old package has a shared user */ + @Nullable public final SharedUserSetting mOldSharedUserSetting; /** * Package settings of the currently installed version. * <p><em>IMPORTANT:</em> The contents of this object may be modified * during scan. */ @Nullable public final PackageSetting mPkgSetting; + /** Shared user settings of the currently installed package */ + @Nullable public final SharedUserSetting mSharedUserSetting; /** A copy of the settings for the currently installed version */ @Nullable public final PackageSetting mOldPkgSetting; /** Package settings for the disabled version on the /system partition */ @@ -59,9 +61,10 @@ final class ScanRequest { ScanRequest( @NonNull ParsedPackage parsedPackage, - @Nullable SharedUserSetting sharedUserSetting, + @Nullable SharedUserSetting oldSharedUserSetting, @Nullable AndroidPackage oldPkg, @Nullable PackageSetting pkgSetting, + @Nullable SharedUserSetting sharedUserSetting, @Nullable PackageSetting disabledPkgSetting, @Nullable PackageSetting originalPkgSetting, @Nullable String realPkgName, @@ -73,6 +76,7 @@ final class ScanRequest { mParsedPackage = parsedPackage; mOldPkg = oldPkg; mPkgSetting = pkgSetting; + mOldSharedUserSetting = oldSharedUserSetting; mSharedUserSetting = sharedUserSetting; mOldPkgSetting = pkgSetting == null ? null : new PackageSetting(pkgSetting); mDisabledPkgSetting = disabledPkgSetting; diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 279de830476a..5966917c680a 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -51,13 +51,6 @@ import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; import android.content.pm.VerifierDeviceIdentity; import android.content.pm.overlay.OverlayPaths; -import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.PackageUserStateUtils; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -118,10 +111,18 @@ import com.android.server.pm.permission.LegacyPermissionState.PermissionState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; +import com.android.server.pm.pkg.PackageUserStateUtils; import com.android.server.pm.pkg.SuspendParams; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedProcess; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; import com.android.server.pm.verify.domain.DomainVerificationLegacySettings; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import com.android.server.pm.verify.domain.DomainVerificationPersistence; +import com.android.server.utils.Slogf; import com.android.server.utils.Snappable; import com.android.server.utils.SnapshotCache; import com.android.server.utils.TimingsTraceAndSlog; @@ -779,13 +780,13 @@ public final class Settings implements Watchable, Snappable { SharedUserSetting s = mSharedUsers.get(name); if (s == null && create) { s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags); - s.userId = acquireAndRegisterNewAppIdLPw(s); - if (s.userId < 0) { + s.mAppId = acquireAndRegisterNewAppIdLPw(s); + if (s.mAppId < 0) { // < 0 means we couldn't assign a userid; throw exception throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Creating shared user " + name + " failed"); } - Log.i(PackageManagerService.TAG, "New shared user " + name + ": id=" + s.userId); + Log.i(PackageManagerService.TAG, "New shared user " + name + ": id=" + s.mAppId); mSharedUsers.put(name, s); } return s; @@ -817,8 +818,9 @@ public final class Settings implements Watchable, Snappable { disabled = p; } mDisabledSysPackages.put(name, disabled); - if (disabled.getSharedUser() != null) { - disabled.getSharedUser().mDisabledPackages.add(disabled); + SharedUserSetting sharedUserSetting = getSharedUserSettingLPr(disabled); + if (sharedUserSetting != null) { + sharedUserSetting.mDisabledPackages.add(disabled); } return true; } @@ -831,8 +833,9 @@ public final class Settings implements Watchable, Snappable { Log.w(PackageManagerService.TAG, "Package " + name + " is not disabled"); return null; } - if (p.getSharedUser() != null) { - p.getSharedUser().mDisabledPackages.remove(p); + SharedUserSetting sharedUserSetting = getSharedUserSettingLPr(p); + if (sharedUserSetting != null) { + sharedUserSetting.mDisabledPackages.remove(p); } p.getPkgState().setUpdatedSystemApp(false); PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(), @@ -855,9 +858,12 @@ public final class Settings implements Watchable, Snappable { void removeDisabledSystemPackageLPw(String name) { final PackageSetting p = mDisabledSysPackages.remove(name); - if (p != null && p.getSharedUser() != null) { - p.getSharedUser().mDisabledPackages.remove(p); - checkAndPruneSharedUserLPw(p.getSharedUser(), false); + if (p != null) { + SharedUserSetting sharedUserSetting = getSharedUserSettingLPr(p); + if (sharedUserSetting != null) { + sharedUserSetting.mDisabledPackages.remove(p); + checkAndPruneSharedUserLPw(sharedUserSetting, false); + } } } @@ -892,7 +898,7 @@ public final class Settings implements Watchable, Snappable { SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) { SharedUserSetting s = mSharedUsers.get(name); if (s != null) { - if (s.userId == uid) { + if (s.mAppId == uid) { return s; } PackageManagerService.reportSettingsProblem(Log.ERROR, @@ -900,7 +906,7 @@ public final class Settings implements Watchable, Snappable { return null; } s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags); - s.userId = uid; + s.mAppId = uid; if (registerExistingAppIdLPw(uid, s, name)) { mSharedUsers.put(name, s); return s; @@ -973,7 +979,9 @@ public final class Settings implements Watchable, Snappable { usesStaticLibraries, usesStaticLibrariesVersions, createMimeGroups(mimeGroupNames), domainSetId); pkgSetting.setLastModifiedTime(codePath.lastModified()); - pkgSetting.setSharedUser(sharedUser); + if (sharedUser != null) { + pkgSetting.setSharedUserAppId(sharedUser.mAppId); + } // If this is not a system app, it starts out stopped. if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { if (DEBUG_STOPPED) { @@ -982,7 +990,8 @@ public final class Settings implements Watchable, Snappable { Slog.i(PackageManagerService.TAG, "Stopping package " + pkgName, e); } List<UserInfo> users = getAllUsers(userManager); - final int installUserId = installUser != null ? installUser.getIdentifier() : 0; + int installUserId = installUser != null ? installUser.getIdentifier() + : UserHandle.USER_SYSTEM; if (users != null && allowInstall) { for (UserInfo user : users) { // By default we consider this app to be installed @@ -993,8 +1002,14 @@ public final class Settings implements Watchable, Snappable { // user we are installing for. final boolean installed = installUser == null || (installUserId == UserHandle.USER_ALL - && !isAdbInstallDisallowed(userManager, user.id)) + && !isAdbInstallDisallowed(userManager, user.id) + && !user.preCreated) || installUserId == user.id; + if (DEBUG_MU) { + Slogf.d(TAG, "createNewSetting(pkg=%s, installUserId=%s, user=%s, " + + "installed=%b)", + pkgName, installUserId, user.toFullString(), installed); + } pkgSetting.setUserState(user.id, 0, COMPONENT_ENABLED_STATE_DEFAULT, installed, true /*stopped*/, @@ -1017,7 +1032,7 @@ public final class Settings implements Watchable, Snappable { } } if (sharedUser != null) { - pkgSetting.setAppId(sharedUser.userId); + pkgSetting.setAppId(sharedUser.mAppId); } else { // Clone the setting here for disabled system packages if (disabledPkg != null) { @@ -1061,7 +1076,9 @@ public final class Settings implements Watchable, Snappable { * WARNING: The provided PackageSetting object may be mutated. */ static void updatePackageSetting(@NonNull PackageSetting pkgSetting, - @Nullable PackageSetting disabledPkg, @Nullable SharedUserSetting sharedUser, + @Nullable PackageSetting disabledPkg, + @Nullable SharedUserSetting existingSharedUserSetting, + @Nullable SharedUserSetting sharedUser, @NonNull File codePath, @Nullable String legacyNativeLibraryPath, @Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi, int pkgFlags, int pkgPrivateFlags, @NonNull UserManagerService userManager, @@ -1070,16 +1087,18 @@ public final class Settings implements Watchable, Snappable { @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId) throws PackageManagerException { final String pkgName = pkgSetting.getPackageName(); - if (!Objects.equals(pkgSetting.getSharedUser(), sharedUser) && sharedUser != null) { - PackageManagerService.reportSettingsProblem(Log.WARN, - "Package " + pkgName + " shared user changed from " - + (pkgSetting.getSharedUser() != null - ? pkgSetting.getSharedUser().name : "<nothing>") - + " to " + sharedUser.name); - throw new PackageManagerException(INSTALL_FAILED_UID_CHANGED, - "Updating application package " + pkgName + " failed"); + if (sharedUser != null) { + if (!Objects.equals(existingSharedUserSetting, sharedUser)) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Package " + pkgName + " shared user changed from " + + (existingSharedUserSetting != null + ? existingSharedUserSetting.name : "<nothing>") + + " to " + sharedUser.name); + throw new PackageManagerException(INSTALL_FAILED_UID_CHANGED, + "Updating application package " + pkgName + " failed"); + } + pkgSetting.setSharedUserAppId(sharedUser.mAppId); } - pkgSetting.setSharedUser(sharedUser); if (!pkgSetting.getPath().equals(codePath)) { final boolean isSystem = pkgSetting.isSystem(); @@ -1220,11 +1239,13 @@ public final class Settings implements Watchable, Snappable { } // If this app defines a shared user id initialize // the shared user signatures as well. - if (p.getSharedUser() != null - && p.getSharedUser().signatures.mSigningDetails.getSignatures() == null) { - p.getSharedUser().signatures.mSigningDetails = pkg.getSigningDetails(); + SharedUserSetting sharedUserSetting = getSharedUserSettingLPr(p); + if (sharedUserSetting != null) { + if (sharedUserSetting.signatures.mSigningDetails.getSignatures() == null) { + sharedUserSetting.signatures.mSigningDetails = pkg.getSigningDetails(); + } } - addPackageSettingLPw(p, p.getSharedUser()); + addPackageSettingLPw(p, sharedUserSetting); } // Utility method that adds a PackageSetting to mPackages and @@ -1233,23 +1254,24 @@ public final class Settings implements Watchable, Snappable { private void addPackageSettingLPw(PackageSetting p, SharedUserSetting sharedUser) { mPackages.put(p.getPackageName(), p); if (sharedUser != null) { - if (p.getSharedUser() != null && p.getSharedUser() != sharedUser) { + SharedUserSetting existingSharedUserSetting = getSharedUserSettingLPr(p); + if (existingSharedUserSetting != null && existingSharedUserSetting != sharedUser) { PackageManagerService.reportSettingsProblem(Log.ERROR, "Package " + p.getPackageName() + " was user " - + p.getSharedUser() + " but is now " + sharedUser + + existingSharedUserSetting + " but is now " + sharedUser + "; I am not changing its files so it will probably fail!"); - p.getSharedUser().removePackage(p); - } else if (p.getAppId() != sharedUser.userId) { + sharedUser.removePackage(p); + } else if (p.getAppId() != sharedUser.mAppId) { PackageManagerService.reportSettingsProblem(Log.ERROR, - "Package " + p.getPackageName() + " was user id " + p.getAppId() - + " but is now user " + sharedUser - + " with id " + sharedUser.userId - + "; I am not changing its files so it will probably fail!"); + "Package " + p.getPackageName() + " was user id " + p.getAppId() + + " but is now user " + sharedUser + + " with id " + sharedUser.mAppId + + "; I am not changing its files so it will probably fail!"); } sharedUser.addPackage(p); - p.setSharedUser(sharedUser); - p.setAppId(sharedUser.userId); + p.setSharedUserAppId(sharedUser.mAppId); + p.setAppId(sharedUser.mAppId); } // If the we know about this user id, we have to update it as it @@ -1269,7 +1291,7 @@ public final class Settings implements Watchable, Snappable { boolean checkAndPruneSharedUserLPw(SharedUserSetting s, boolean skipCheck) { if (skipCheck || (s.packages.isEmpty() && s.mDisabledPackages.isEmpty())) { if (mSharedUsers.remove(s.name) != null) { - removeAppIdLPw(s.userId); + removeAppIdLPw(s.mAppId); return true; } } @@ -1280,10 +1302,11 @@ public final class Settings implements Watchable, Snappable { final PackageSetting p = mPackages.remove(name); if (p != null) { removeInstallerPackageStatus(name); - if (p.getSharedUser() != null) { - p.getSharedUser().removePackage(p); - if (checkAndPruneSharedUserLPw(p.getSharedUser(), false)) { - return p.getSharedUser().userId; + SharedUserSetting sharedUserSetting = getSharedUserSettingLPr(p); + if (sharedUserSetting != null) { + sharedUserSetting.removePackage(p); + if (checkAndPruneSharedUserLPw(sharedUserSetting, false)) { + return sharedUserSetting.mAppId; } } else { removeAppIdLPw(p.getAppId()); @@ -2039,11 +2062,14 @@ public final class Settings implements Watchable, Snappable { serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS); - if (DEBUG_MU) Log.i(TAG, "Writing " + userPackagesStateFile); + if (DEBUG_MU) { + Slogf.i(TAG, "Writing %s (%d packages)", userPackagesStateFile, + mPackages.values().size()); + } for (final PackageSetting pkg : mPackages.values()) { final PackageUserStateInternal ustate = pkg.readUserState(userId); if (DEBUG_MU) { - Log.i(TAG, " pkg=" + pkg.getPackageName() + Log.v(TAG, " pkg=" + pkg.getPackageName() + ", installed=" + ustate.isInstalled() + ", state=" + ustate.getEnabledState()); } @@ -2116,20 +2142,24 @@ public final class Settings implements Watchable, Snappable { serializer.endTag(null, TAG_SUSPEND_PARAMS); } } - if (!ArrayUtils.isEmpty(ustate.getEnabledComponentsNoCopy())) { + final ArraySet<String> enabledComponents = ustate.getEnabledComponents(); + if (enabledComponents != null && enabledComponents.size() > 0) { serializer.startTag(null, TAG_ENABLED_COMPONENTS); - for (final String name : ustate.getEnabledComponentsNoCopy()) { + for (int i = 0; i < enabledComponents.size(); i++) { serializer.startTag(null, TAG_ITEM); - serializer.attribute(null, ATTR_NAME, name); + serializer.attribute(null, ATTR_NAME, + enabledComponents.valueAt(i)); serializer.endTag(null, TAG_ITEM); } serializer.endTag(null, TAG_ENABLED_COMPONENTS); } - if (!ArrayUtils.isEmpty(ustate.getDisabledComponentsNoCopy())) { + final ArraySet<String> disabledComponents = ustate.getDisabledComponents(); + if (disabledComponents != null && disabledComponents.size() > 0) { serializer.startTag(null, TAG_DISABLED_COMPONENTS); - for (final String name : ustate.getDisabledComponentsNoCopy()) { + for (int i = 0; i < disabledComponents.size(); i++) { serializer.startTag(null, TAG_ITEM); - serializer.attribute(null, ATTR_NAME, name); + serializer.attribute(null, ATTR_NAME, + disabledComponents.valueAt(i)); serializer.endTag(null, TAG_ITEM); } serializer.endTag(null, TAG_DISABLED_COMPONENTS); @@ -2454,7 +2484,7 @@ public final class Settings implements Watchable, Snappable { for (final SharedUserSetting usr : mSharedUsers.values()) { serializer.startTag(null, "shared-user"); serializer.attribute(null, ATTR_NAME, usr.name); - serializer.attributeInt(null, "userId", usr.userId); + serializer.attributeInt(null, "userId", usr.mAppId); usr.signatures.writeXml(serializer, "sigs", mPastSignatures.untrackedStorage()); serializer.endTag(null, "shared-user"); } @@ -2773,7 +2803,7 @@ public final class Settings implements Watchable, Snappable { serializer.attribute(null, "cpuAbiOverride", pkg.getCpuAbiOverride()); } - if (pkg.getSharedUser() == null) { + if (!pkg.hasSharedUser()) { serializer.attributeInt(null, "userId", pkg.getAppId()); } else { serializer.attributeInt(null, "sharedUserId", pkg.getAppId()); @@ -2816,7 +2846,7 @@ public final class Settings implements Watchable, Snappable { serializer.attributeLongHex(null, "ft", pkg.getLastModifiedTime()); serializer.attributeLongHex(null, "ut", pkg.getLastUpdateTime()); serializer.attributeLong(null, "version", pkg.getVersionCode()); - if (pkg.getSharedUser() == null) { + if (!pkg.hasSharedUser()) { serializer.attributeInt(null, "userId", pkg.getAppId()); } else { serializer.attributeInt(null, "sharedUserId", pkg.getAppId()); @@ -3088,21 +3118,22 @@ public final class Settings implements Watchable, Snappable { for (int i = 0; i < N; i++) { final PackageSetting p = mPendingPackages.get(i); - final int sharedUserId = p.getSharedUserIdInt(); - final Object idObj = getSettingLPr(sharedUserId); + final int sharedUserAppId = p.getSharedUserAppId(); + if (sharedUserAppId <= 0) { + continue; + } + final Object idObj = getSettingLPr(sharedUserAppId); if (idObj instanceof SharedUserSetting) { final SharedUserSetting sharedUser = (SharedUserSetting) idObj; - p.setSharedUser(sharedUser); - p.setAppId(sharedUser.userId); addPackageSettingLPw(p, sharedUser); } else if (idObj != null) { String msg = "Bad package setting: package " + p.getPackageName() - + " has shared uid " + sharedUserId + " that is not a shared uid\n"; + + " has shared uid " + sharedUserAppId + " that is not a shared uid\n"; mReadMessages.append(msg); PackageManagerService.reportSettingsProblem(Log.ERROR, msg); } else { String msg = "Bad package setting: package " + p.getPackageName() - + " has shared uid " + sharedUserId + " that is not defined\n"; + + " has shared uid " + sharedUserAppId + " that is not defined\n"; mReadMessages.append(msg); PackageManagerService.reportSettingsProblem(Log.ERROR, msg); } @@ -3135,8 +3166,9 @@ public final class Settings implements Watchable, Snappable { for (PackageSetting disabledPs : mDisabledSysPackages.values()) { final Object id = getSettingLPr(disabledPs.getAppId()); if (id instanceof SharedUserSetting) { - disabledPs.setSharedUser((SharedUserSetting) id); - disabledPs.getSharedUser().mDisabledPackages.add(disabledPs); + SharedUserSetting sharedUserSetting = (SharedUserSetting) id; + sharedUserSetting.mDisabledPackages.add(disabledPs); + disabledPs.setSharedUserAppId(sharedUserSetting.mAppId); } } @@ -3552,7 +3584,9 @@ public final class Settings implements Watchable, Snappable { ps.setLastUpdateTime(parser.getAttributeLongHex(null, "ut", 0)); ps.setAppId(parser.getAttributeInt(null, "userId", 0)); if (ps.getAppId() <= 0) { - ps.setAppId(parser.getAttributeInt(null, "sharedUserId", 0)); + final int sharedUserAppId = parser.getAttributeInt(null, "sharedUserId", 0); + ps.setAppId(sharedUserAppId); + ps.setSharedUserAppId(sharedUserAppId); } final float loadingProgress = parser.getAttributeFloat(null, "loadingProgress", 0); @@ -3567,7 +3601,13 @@ public final class Settings implements Watchable, Snappable { } if (parser.getName().equals(TAG_PERMISSIONS)) { - readInstallPermissionsLPr(parser, ps.getLegacyPermissionState(), users); + final LegacyPermissionState legacyState; + if (ps.hasSharedUser()) { + legacyState = getSettingLPr(ps.getSharedUserAppId()).getLegacyPermissionState(); + } else { + legacyState = ps.getLegacyPermissionState(); + } + readInstallPermissionsLPr(parser, legacyState, users); } else if (parser.getName().equals(TAG_USES_STATIC_LIB)) { readUsesStaticLibLPw(parser, ps); } else if (parser.getName().equals(TAG_USES_SDK_LIB)) { @@ -3592,7 +3632,7 @@ public final class Settings implements Watchable, Snappable { String name = null; String realName = null; int userId = 0; - int sharedUserId = 0; + int sharedUserAppId = 0; String codePathStr = null; String legacyCpuAbiString = null; String legacyNativeLibraryPathStr = null; @@ -3624,7 +3664,7 @@ public final class Settings implements Watchable, Snappable { name = parser.getAttributeValue(null, ATTR_NAME); realName = parser.getAttributeValue(null, "realName"); userId = parser.getAttributeInt(null, "userId", 0); - sharedUserId = parser.getAttributeInt(null, "sharedUserId", 0); + sharedUserAppId = parser.getAttributeInt(null, "sharedUserId", 0); codePathStr = parser.getAttributeValue(null, "codePath"); legacyCpuAbiString = parser.getAttributeValue(null, "requiredCpuAbi"); @@ -3718,7 +3758,7 @@ public final class Settings implements Watchable, Snappable { lastUpdateTime = parser.getAttributeLongHex(null, "ut", 0); if (PackageManagerService.DEBUG_SETTINGS) Log.v(PackageManagerService.TAG, "Reading package: " + name + " userId=" + userId - + " sharedUserId=" + sharedUserId); + + " sharedUserId=" + sharedUserAppId); if (realName != null) { realName = realName.intern(); } @@ -3748,12 +3788,12 @@ public final class Settings implements Watchable, Snappable { packageSetting.setLastModifiedTime(timeStamp); packageSetting.setLastUpdateTime(lastUpdateTime); } - } else if (sharedUserId != 0) { - if (sharedUserId > 0) { + } else if (sharedUserAppId != 0) { + if (sharedUserAppId > 0) { packageSetting = new PackageSetting(name.intern(), realName, new File(codePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, - versionCode, pkgFlags, pkgPrivateFlags, sharedUserId, + versionCode, pkgFlags, pkgPrivateFlags, sharedUserAppId, null /* usesSdkLibraries */, null /* usesSdkLibrariesVersions */, null /* usesStaticLibraries */, @@ -3764,11 +3804,11 @@ public final class Settings implements Watchable, Snappable { mPendingPackages.add(packageSetting); if (PackageManagerService.DEBUG_SETTINGS) Log.i(PackageManagerService.TAG, "Reading package " + name - + ": sharedUserId=" + sharedUserId + " pkg=" + packageSetting); + + ": sharedUserId=" + sharedUserAppId + " pkg=" + packageSetting); } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Error in package manager settings: package " + name - + " has bad sharedId " + sharedUserId + " at " + + " has bad sharedId " + sharedUserAppId + " at " + parser.getPositionDescription()); } } else { @@ -3838,8 +3878,14 @@ public final class Settings implements Watchable, Snappable { packageSetting.getSignatures() .readXml(parser,mPastSignatures.untrackedStorage()); } else if (tagName.equals(TAG_PERMISSIONS)) { - readInstallPermissionsLPr(parser, - packageSetting.getLegacyPermissionState(), users); + final LegacyPermissionState legacyState; + if (packageSetting.hasSharedUser()) { + legacyState = getSettingLPr( + packageSetting.getSharedUserAppId()).getLegacyPermissionState(); + } else { + legacyState = packageSetting.getLegacyPermissionState(); + } + readInstallPermissionsLPr(parser, legacyState, users); packageSetting.setInstallPermissionsFixed(true); } else if (tagName.equals("proper-signing-keyset")) { long id = parser.getAttributeLong(null, "identifier"); @@ -4294,6 +4340,19 @@ public final class Settings implements Watchable, Snappable { return pkg.getCurrentEnabledStateLPr(classNameStr, userId); } + SharedUserSetting getSharedUserSettingLPr(String packageName) { + final PackageSetting ps = mPackages.get(packageName); + return getSharedUserSettingLPr(ps); + } + + @Nullable + SharedUserSetting getSharedUserSettingLPr(PackageSetting ps) { + if (ps == null || !ps.hasSharedUser()) { + return null; + } + return (SharedUserSetting) getSettingLPr(ps.getSharedUserAppId()); + } + /** * Returns all users on the device, including pre-created and dying users. * @@ -4512,8 +4571,9 @@ public final class Settings implements Watchable, Snappable { pw.print(prefix); pw.print(" userId="); pw.println(ps.getAppId()); - if (ps.getSharedUser() != null) { - pw.print(prefix); pw.print(" sharedUser="); pw.println(ps.getSharedUser()); + SharedUserSetting sharedUserSetting = getSharedUserSettingLPr(ps); + if (sharedUserSetting != null) { + pw.print(prefix); pw.print(" sharedUser="); pw.println(sharedUserSetting); } pw.print(prefix); pw.print(" pkg="); pw.println(pkg); pw.print(prefix); pw.print(" codePath="); pw.println(ps.getPathString()); @@ -4797,7 +4857,7 @@ public final class Settings implements Watchable, Snappable { } } - if (ps.getSharedUser() == null || permissionNames != null || dumpAll) { + if (!ps.hasSharedUser() || permissionNames != null || dumpAll) { dumpInstallPermissionsLPr(pw, prefix + " ", permissionNames, permissionsState, users); } @@ -4917,7 +4977,7 @@ public final class Settings implements Watchable, Snappable { pw.println(lastDisabledAppCaller); } - if (ps.getSharedUser() == null) { + if (!ps.hasSharedUser()) { dumpGidsLPr(pw, prefix + " ", mPermissionDataProvider.getGidsForUid( UserHandle.getUid(user.id, ps.getAppId()))); dumpRuntimePermissionsLPr(pw, prefix + " ", permissionNames, permissionsState @@ -4931,18 +4991,18 @@ public final class Settings implements Watchable, Snappable { } if (permissionNames == null) { - Set<String> cmp = userState.getDisabledComponents(); + WatchedArraySet<String> cmp = userState.getDisabledComponentsNoCopy(); if (cmp != null && cmp.size() > 0) { pw.print(prefix); pw.println(" disabledComponents:"); - for (String s : cmp) { - pw.print(prefix); pw.print(" "); pw.println(s); + for (int i = 0; i < cmp.size(); i++) { + pw.print(prefix); pw.print(" "); pw.println(cmp.valueAt(i)); } } - cmp = userState.getEnabledComponents(); + cmp = userState.getEnabledComponentsNoCopy(); if (cmp != null && cmp.size() > 0) { pw.print(prefix); pw.println(" enabledComponents:"); - for (String s : cmp) { - pw.print(prefix); pw.print(" "); pw.println(s); + for (int i = 0; i < cmp.size(); i++) { + pw.print(prefix); pw.print(" "); pw.println(cmp.valueAt(i)); } } } @@ -4970,7 +5030,7 @@ public final class Settings implements Watchable, Snappable { } if (!checkin && packageName != null) { - dumpState.setSharedUser(ps.getSharedUser()); + dumpState.setSharedUser(getSharedUserSettingLPr(ps)); } if (!checkin && !printedSomething) { @@ -5053,7 +5113,7 @@ public final class Settings implements Watchable, Snappable { continue; } final LegacyPermissionState permissionsState = - mPermissionDataProvider.getLegacyPermissionState(su.userId); + mPermissionDataProvider.getLegacyPermissionState(su.mAppId); if (permissionNames != null && !permissionsState.hasPermissionState(permissionNames)) { continue; @@ -5073,7 +5133,7 @@ public final class Settings implements Watchable, Snappable { pw.println("):"); String prefix = " "; - pw.print(prefix); pw.print("userId="); pw.println(su.userId); + pw.print(prefix); pw.print("userId="); pw.println(su.mAppId); pw.print(prefix); pw.println("Packages"); final int numPackages = su.packages.size(); @@ -5097,7 +5157,7 @@ public final class Settings implements Watchable, Snappable { for (UserInfo user : users) { final int userId = user.id; final int[] gids = mPermissionDataProvider.getGidsForUid(UserHandle.getUid( - userId, su.userId)); + userId, su.mAppId)); final Collection<PermissionState> permissions = permissionsState.getPermissionStates(userId); if (!ArrayUtils.isEmpty(gids) || !permissions.isEmpty()) { @@ -5108,7 +5168,7 @@ public final class Settings implements Watchable, Snappable { } } } else { - pw.print("suid,"); pw.print(su.userId); pw.print(","); pw.println(su.name); + pw.print("suid,"); pw.print(su.mAppId); pw.print(","); pw.println(su.name); } } } @@ -5510,7 +5570,7 @@ public final class Settings implements Watchable, Snappable { for (int i = 0; i < packagesSize; i++) { String packageName = packageStates.keyAt(i); PackageStateInternal packageState = packageStates.valueAt(i); - if (packageState.getSharedUser() == null) { + if (!packageState.hasSharedUser()) { List<RuntimePermissionsState.PermissionState> permissions = getPermissionsFromPermissionsState( packageState.getLegacyPermissionState(), userId); @@ -5616,8 +5676,9 @@ public final class Settings implements Watchable, Snappable { packageSetting.getLegacyPermissionState(), userId); packageSetting.setInstallPermissionsFixed(true); - } else if (packageSetting.getSharedUser() == null && !isUpgradeToR) { - Slog.w(TAG, "Missing permission state for package: " + packageName); + } else if (!packageSetting.hasSharedUser() && !isUpgradeToR) { + Slogf.w(TAG, "Missing permission state for package %s on user %d", + packageName, userId); packageSetting.getLegacyPermissionState().setMissing(true, userId); } } diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java index 5ef14715e4d1..d0e1e6c76596 100644 --- a/services/core/java/com/android/server/pm/SharedUserSetting.java +++ b/services/core/java/com/android/server/pm/SharedUserSetting.java @@ -19,9 +19,6 @@ package com.android.server.pm; import android.annotation.NonNull; import android.content.pm.ApplicationInfo; import android.content.pm.SigningDetails; -import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProcessImpl; import android.service.pm.PackageServiceDumpProto; import android.util.ArrayMap; import android.util.ArraySet; @@ -29,8 +26,12 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.util.ArrayUtils; import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.permission.LegacyPermissionState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedUserApi; +import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedProcess; +import com.android.server.pm.pkg.component.ParsedProcessImpl; import com.android.server.utils.SnapshotCache; import libcore.util.EmptyArray; @@ -46,7 +47,7 @@ import java.util.Map; public final class SharedUserSetting extends SettingBase implements SharedUserApi { final String name; - int userId; + int mAppId; /** @see SharedUserApi#getUidFlags() **/ int uidFlags; @@ -98,7 +99,7 @@ public final class SharedUserSetting extends SettingBase implements SharedUserAp private SharedUserSetting(SharedUserSetting orig) { super(orig); name = orig.name; - userId = orig.userId; + mAppId = orig.mAppId; uidFlags = orig.uidFlags; uidPrivateFlags = orig.uidPrivateFlags; packages = new ArraySet<>(orig.packages); @@ -133,12 +134,12 @@ public final class SharedUserSetting extends SettingBase implements SharedUserAp @Override public String toString() { return "SharedUserSetting{" + Integer.toHexString(System.identityHashCode(this)) + " " - + name + "/" + userId + "}"; + + name + "/" + mAppId + "}"; } public void dumpDebug(ProtoOutputStream proto, long fieldId) { long token = proto.start(fieldId); - proto.write(PackageServiceDumpProto.SharedUserProto.UID, userId); + proto.write(PackageServiceDumpProto.SharedUserProto.UID, mAppId); proto.write(PackageServiceDumpProto.SharedUserProto.NAME, name); proto.end(token); } @@ -286,7 +287,7 @@ public final class SharedUserSetting extends SettingBase implements SharedUserAp /** Updates all fields in this shared user setting from another. */ public SharedUserSetting updateFrom(SharedUserSetting sharedUser) { super.copySettingBase(sharedUser); - this.userId = sharedUser.userId; + this.mAppId = sharedUser.mAppId; this.uidFlags = sharedUser.uidFlags; this.uidPrivateFlags = sharedUser.uidPrivateFlags; this.seInfoTargetSdkVersion = sharedUser.seInfoTargetSdkVersion; @@ -315,8 +316,8 @@ public final class SharedUserSetting extends SettingBase implements SharedUserAp } @Override - public int getUserId() { - return userId; + public int getAppId() { + return mAppId; } @Override @@ -363,4 +364,10 @@ public final class SharedUserSetting extends SettingBase implements SharedUserAp public ArrayMap<String, ParsedProcess> getProcesses() { return processes; } + + @NonNull + @Override + public LegacyPermissionState getSharedUserLegacyPermissionState() { + return super.getLegacyPermissionState(); + } } diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index 1ea8b2478a90..85d1367d340b 100644 --- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -52,6 +52,7 @@ import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.SuspendParams; import com.android.server.pm.pkg.mutate.PackageUserStateWrite; +import com.android.server.utils.WatchedArrayMap; import java.util.ArrayList; import java.util.Arrays; @@ -145,7 +146,7 @@ public final class SuspendPackageHelper { continue; } - final ArrayMap<String, SuspendParams> suspendParamsMap = + final WatchedArrayMap<String, SuspendParams> suspendParamsMap = packageState.getUserStateOrDefault(userId).getSuspendParams(); final SuspendParams suspendParams = suspendParamsMap == null ? null : suspendParamsMap.get(packageName); @@ -297,7 +298,8 @@ public final class SuspendPackageHelper { continue; } - ArrayMap<String, SuspendParams> suspendParamsMap = packageUserState.getSuspendParams(); + WatchedArrayMap<String, SuspendParams> suspendParamsMap = + packageUserState.getSuspendParams(); int countRemoved = 0; for (int index = 0; index < suspendParamsMap.size(); index++) { String suspendingPackage = suspendParamsMap.keyAt(index); @@ -440,7 +442,8 @@ public final class SuspendPackageHelper { return null; } - final ArrayMap<String, SuspendParams> suspendParamsMap = userState.getSuspendParams(); + final WatchedArrayMap<String, SuspendParams> suspendParamsMap = + userState.getSuspendParams(); if (suspendParamsMap == null) { return null; } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index e63d72116bb5..5c4d0116e1a0 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1517,7 +1517,6 @@ public class UserManagerService extends IUserManager.Stub { return userTypeDetails.getBadgeNoBackground(); } - @Override public boolean isProfile(@UserIdInt int userId) { checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isProfile"); synchronized (mUsersLock) { @@ -1526,21 +1525,19 @@ public class UserManagerService extends IUserManager.Stub { } } + /** + * Returns the user type (if it is a profile), empty string (if it isn't a profile), + * or null (if the user doesn't exist). + */ @Override - public boolean isManagedProfile(@UserIdInt int userId) { - checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isManagedProfile"); - synchronized (mUsersLock) { - UserInfo userInfo = getUserInfoLU(userId); - return userInfo != null && userInfo.isManagedProfile(); - } - } - - @Override - public boolean isCloneProfile(@UserIdInt int userId) { - checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isCloneProfile"); + public @Nullable String getProfileType(@UserIdInt int userId) { + checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getProfileType"); synchronized (mUsersLock) { UserInfo userInfo = getUserInfoLU(userId); - return userInfo != null && userInfo.isCloneProfile(); + if (userInfo != null) { + return userInfo.isProfile() ? userInfo.userType : ""; + } + return null; } } @@ -5163,6 +5160,8 @@ public class UserManagerService extends IUserManager.Stub { nextId = scanNextAvailableIdLocked(); } } + // If we got here, we probably recycled user ids, so invalidate any caches. + UserManager.invalidateStaticUserProperties(); if (nextId < 0) { throw new IllegalStateException("No user id available!"); } diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 6e6585ebcf14..1e3b67c394cb 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -332,13 +332,13 @@ public final class UserTypeFactory { } String typeName = parser.getAttributeValue(null, "name"); - if (typeName == null) { + if (typeName == null || typeName.equals("")) { Slog.w(LOG_TAG, "Skipping user type with no name in " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } - typeName.intern(); + typeName = typeName.intern(); UserTypeDetails.Builder builder; if (typeName.startsWith("android.")) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index ed351fd4aef9..ed47bfb7e180 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -17,6 +17,7 @@ package com.android.server.pm.permission; import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY; +import static android.Manifest.permission.POST_NOTIFICATIONS; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT; @@ -41,6 +42,7 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE; import static android.content.pm.PackageManager.MASK_PERMISSION_FLAGS_ALL; import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; +import static android.os.Process.INVALID_UID; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.permission.PermissionManager.KILL_APP_REASON_GIDS_CHANGED; import static android.permission.PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED; @@ -129,6 +131,7 @@ import com.android.server.pm.UserManagerService; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; +import com.android.server.pm.pkg.AndroidPackageApi; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.component.ComponentMutateUtils; import com.android.server.pm.pkg.component.ParsedPermission; @@ -752,11 +755,14 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; - flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; flagValues &= ~FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; flagValues &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; flagValues &= ~PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; + // REVIEW_REQUIRED can only be set by non-system apps for for POST_NOTIFICATIONS + if (!POST_NOTIFICATIONS.equals(permName)) { + flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; + } } final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); @@ -2538,16 +2544,18 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt if (uidState.isMissing()) { Collection<String> uidRequestedPermissions; int targetSdkVersion; - if (ps.getSharedUser() == null) { + if (!ps.hasSharedUser()) { uidRequestedPermissions = pkg.getRequestedPermissions(); targetSdkVersion = pkg.getTargetSdkVersion(); } else { uidRequestedPermissions = new ArraySet<>(); targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - List<AndroidPackage> packages = ps.getSharedUser().getPackages(); + final ArraySet<PackageStateInternal> packages = + mPackageManagerInt.getSharedUserPackages(ps.getSharedUserAppId()); int packagesSize = packages.size(); for (int i = 0; i < packagesSize; i++) { - AndroidPackage sharedUserPackage = packages.get(i); + AndroidPackageApi sharedUserPackage = + packages.valueAt(i).getAndroidPackage(); uidRequestedPermissions.addAll( sharedUserPackage.getRequestedPermissions()); targetSdkVersion = Math.min(targetSdkVersion, @@ -2589,7 +2597,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt if (replace) { userState.setInstallPermissionsFixed(ps.getPackageName(), false); - if (ps.getSharedUser() == null) { + if (!ps.hasSharedUser()) { origState = new UidPermissionState(uidState); uidState.reset(); } else { @@ -2599,7 +2607,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt // changed runtime permissions here are promotion of an install to // runtime and revocation of a runtime from a shared user. if (revokeUnusedSharedUserPermissionsLocked( - ps.getSharedUser().getPackages(), uidState)) { + mPackageManagerInt.getSharedUserPackages(ps.getSharedUserAppId()), + uidState)) { updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); runtimePermissionsRevoked = true; } @@ -3166,44 +3175,12 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms, newPerm, ps, pkg); } - } else if (NOTIFICATION_PERMISSIONS.contains(newPerm)) { - //&& (origPs.getPermissionState(newPerm) == null) { - // TODO(b/205888750): add back line about origPs once all TODO sections below are - // propagated through droidfood - Permission bp = mRegistry.getPermission(newPerm); - if (bp == null) { - throw new IllegalStateException("Unknown new permission " + newPerm); - } - if (!isUserSetOrPregrantedOrFixed(ps.getPermissionFlags(newPerm))) { - updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); - int setFlag = ps.isPermissionGranted(newPerm) - ? 0 : FLAG_PERMISSION_REVIEW_REQUIRED; - ps.updatePermissionFlags(bp, FLAG_PERMISSION_REVIEW_REQUIRED, setFlag); - // TODO(b/205888750): remove if/else block once propagated through droidfood - if (ps.isPermissionGranted(newPerm) - && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M) { - ps.revokePermission(bp); - } else if (!ps.isPermissionGranted(newPerm) - && pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) { - ps.grantPermission(bp); - } - } else { - // TODO(b/205888750): remove once propagated through droidfood - ps.updatePermissionFlags(bp, FLAG_PERMISSION_REVOKE_WHEN_REQUESTED - | FLAG_PERMISSION_REVIEW_REQUIRED, 0); - } } } return updatedUserIds; } - private boolean isUserSetOrPregrantedOrFixed(int flags) { - return (flags & (FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED - | FLAG_PERMISSION_POLICY_FIXED | FLAG_PERMISSION_SYSTEM_FIXED - | FLAG_PERMISSION_GRANTED_BY_DEFAULT | FLAG_PERMISSION_GRANTED_BY_ROLE)) != 0; - } - @NonNull @Override public List<SplitPermissionInfoParcelable> getSplitPermissions() { @@ -3845,13 +3822,14 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt @GuardedBy("mLock") private boolean revokeUnusedSharedUserPermissionsLocked( - List<AndroidPackage> pkgList, UidPermissionState uidState) { + ArraySet<PackageStateInternal> pkgList, UidPermissionState uidState) { // Collect all used permissions in the UID final ArraySet<String> usedPermissions = new ArraySet<>(); if (pkgList == null || pkgList.size() == 0) { return false; } - for (AndroidPackage pkg : pkgList) { + for (PackageStateInternal pkgState : pkgList) { + final AndroidPackageApi pkg = pkgState.getAndroidPackage(); if (pkg.getRequestedPermissions().isEmpty()) { continue; } @@ -4485,8 +4463,13 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt final int[] userIds = getAllUserIds(); mPackageManagerInt.forEachPackageState(ps -> { final int appId = ps.getAppId(); - final LegacyPermissionState legacyState = ps.getLegacyPermissionState(); - + final LegacyPermissionState legacyState; + if (ps.hasSharedUser()) { + legacyState = mPackageManagerInt.getSharedUserApi( + ps.getSharedUserAppId()).getSharedUserLegacyPermissionState(); + } else { + legacyState = ps.getLegacyPermissionState(); + } synchronized (mLock) { for (final int userId : userIds) { final UserPermissionState userState = mState.getOrCreateUserState(userId); @@ -4526,7 +4509,13 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } mPackageManagerInt.forEachPackageSetting(ps -> { ps.setInstallPermissionsFixed(false); - final LegacyPermissionState legacyState = ps.getLegacyPermissionState(); + final LegacyPermissionState legacyState; + if (ps.hasSharedUser()) { + legacyState = mPackageManagerInt.getSharedUserApi( + ps.getSharedUserAppId()).getSharedUserLegacyPermissionState(); + } else { + legacyState = ps.getLegacyPermissionState(); + } legacyState.reset(); final int appId = ps.getAppId(); @@ -4872,7 +4861,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt final PackageStateInternal ps = mPackageManagerInt.getPackageStateInternal(pkg.getPackageName()); - if (ps.getSharedUser() != null) { + if (ps.hasSharedUser()) { // The package is joining a shared user group. This can only happen when a system // app left shared UID with an update, and then the update is uninstalled. // If no apps remain in its original shared UID group, clone the current @@ -4963,7 +4952,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt private void onPackageInstalledInternal(@NonNull AndroidPackage pkg, int previousAppId, @NonNull PermissionManagerServiceInternal.PackageInstalledParams params, @UserIdInt int[] userIds) { - if (previousAppId != Process.INVALID_UID) { + if (previousAppId != INVALID_UID) { handleAppIdMigration(pkg, previousAppId); } updatePermissions(pkg.getPackageName(), pkg); diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java index 9fa964479fb4..7726d7fc226b 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageState.java @@ -159,12 +159,18 @@ public interface PackageState { String getSecondaryCpuAbi(); /** - * Retrieves the shared user ID. Note that the actual shared user data is not available here and - * must be queried separately. + * Whether the package shares the same user ID as other packages + */ + boolean hasSharedUser(); + + /** + * Retrieves the shared user app ID. Note that the actual shared user data is not available here + * and must be queried separately. * - * @return the shared user this package is a part of, or -1 if it's not part of a shared user. + * @return the app ID of the shared user that this package is a part of, or -1 if it's not part + * of a shared user. */ - int getSharedUserId(); + int getSharedUserAppId(); @NonNull SigningInfo getSigningInfo(); diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java index 9395ca54061a..3170304985b5 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java @@ -24,6 +24,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningInfo; import android.content.pm.overlay.OverlayPaths; +import android.util.ArraySet; import android.util.SparseArray; import com.android.internal.util.DataClass; @@ -126,8 +127,8 @@ public class PackageStateImpl implements PackageState { private final String mPrimaryCpuAbi; @Nullable private final String mSecondaryCpuAbi; - @Nullable - private final int mSharedUserId; + private final boolean mHasSharedUser; + private final int mSharedUserAppId; @NonNull private final String[] mUsesSdkLibraries; @NonNull @@ -172,7 +173,8 @@ public class PackageStateImpl implements PackageState { mPath = pkgState.getPath(); mPrimaryCpuAbi = pkgState.getPrimaryCpuAbi(); mSecondaryCpuAbi = pkgState.getSecondaryCpuAbi(); - mSharedUserId = pkgState.getSharedUserId(); + mHasSharedUser = pkgState.hasSharedUser(); + mSharedUserAppId = pkgState.getSharedUserAppId(); mUsesSdkLibraries = pkgState.getUsesSdkLibraries(); mUsesSdkLibrariesVersionsMajor = pkgState.getUsesSdkLibrariesVersionsMajor(); mUsesStaticLibraries = pkgState.getUsesStaticLibraries(); @@ -271,6 +273,15 @@ public class PackageStateImpl implements PackageState { return mLongVersionCode; } + @Override + public boolean hasSharedUser() { + return mHasSharedUser; + } + + @Override + public int getSharedUserAppId() { + return mSharedUserAppId; + } /** * @hide */ @@ -319,11 +330,11 @@ public class PackageStateImpl implements PackageState { private final long mCeDataInode; @NonNull - private final Set<String> mDisabledComponents; + private final ArraySet<String> mDisabledComponents; @PackageManager.DistractionRestriction private final int mDistractionFlags; @NonNull - private final Set<String> mEnabledComponents; + private final ArraySet<String> mEnabledComponents; private final int mEnabledState; @Nullable private final String mHarmfulAppWarning; @@ -450,7 +461,8 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated.Member - public @NonNull Set<String> getDisabledComponents() { + public @NonNull + ArraySet<String> getDisabledComponents() { return mDisabledComponents; } @@ -460,7 +472,7 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated.Member - public @NonNull Set<String> getEnabledComponents() { + public @NonNull ArraySet<String> getEnabledComponents() { return mEnabledComponents; } @@ -516,7 +528,7 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated( - time = 1640209608883L, + time = 1644270981508L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java", inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final long mFirstInstallTime\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") @@ -615,8 +627,8 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated.Member - public @Nullable int getSharedUserId() { - return mSharedUserId; + public boolean isHasSharedUser() { + return mHasSharedUser; } @DataClass.Generated.Member @@ -671,10 +683,10 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated( - time = 1640209608912L, + time = 1644270981543L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java", - inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackageApi mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.Integer mSharedUserId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mUsesLibraryInfos\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") + inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackageApi mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mUsesLibraryInfos\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java index fb2fe1f442b7..68a00a9c8eae 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java @@ -50,9 +50,6 @@ public interface PackageStateInternal extends PackageState { @NonNull InstallSource getInstallSource(); - @Nullable - SharedUserApi getSharedUser(); - // TODO: Remove this in favor of boolean APIs int getFlags(); int getPrivateFlags(); diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java index bed140192fda..e19e555b5fcf 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java @@ -18,14 +18,12 @@ package com.android.server.pm.pkg; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; import android.content.pm.PackageManager; import android.content.pm.overlay.OverlayPaths; import android.os.UserHandle; +import android.util.ArraySet; import java.util.Map; -import java.util.Set; /** * The API surface for {@link PackageUserStateInternal}, for use by in-process mainline consumers. @@ -59,7 +57,7 @@ public interface PackageUserState { * Fully qualified class names of components explicitly disabled. */ @NonNull - Set<String> getDisabledComponents(); + ArraySet<String> getDisabledComponents(); @PackageManager.DistractionRestriction int getDistractionFlags(); @@ -68,7 +66,7 @@ public interface PackageUserState { * Fully qualified class names of components explicitly enabled. */ @NonNull - Set<String> getEnabledComponents(); + ArraySet<String> getEnabledComponents(); /** * Retrieve the effective enabled state of the package itself. diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java index 73c86c7f163d..daa4545b4a4a 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java @@ -21,13 +21,14 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.overlay.OverlayPaths; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; +import com.android.server.utils.WatchedArrayMap; +import com.android.server.utils.WatchedArraySet; + import java.util.Collections; import java.util.Map; -import java.util.Set; class PackageUserStateDefault implements PackageUserStateInternal { @@ -59,14 +60,14 @@ class PackageUserStateDefault implements PackageUserStateInternal { @NonNull @Override - public Set<String> getDisabledComponents() { - return Collections.emptySet(); + public ArraySet<String> getDisabledComponents() { + return new ArraySet<>(); } @NonNull @Override - public Set<String> getEnabledComponents() { - return Collections.emptySet(); + public ArraySet<String> getEnabledComponents() { + return new ArraySet<>(); } @Override @@ -156,19 +157,19 @@ class PackageUserStateDefault implements PackageUserStateInternal { @Nullable @Override - public ArrayMap<String, SuspendParams> getSuspendParams() { + public WatchedArrayMap<String, SuspendParams> getSuspendParams() { return null; } @Nullable @Override - public ArraySet<String> getDisabledComponentsNoCopy() { + public WatchedArraySet<String> getDisabledComponentsNoCopy() { return null; } @Nullable @Override - public ArraySet<String> getEnabledComponentsNoCopy() { + public WatchedArraySet<String> getEnabledComponentsNoCopy() { return null; } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java index efb6144af9a8..410fa975cb8a 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java @@ -30,19 +30,25 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; +import com.android.server.utils.Snappable; +import com.android.server.utils.SnapshotCache; import com.android.server.utils.Watchable; +import com.android.server.utils.WatchableImpl; +import com.android.server.utils.WatchedArrayMap; +import com.android.server.utils.WatchedArraySet; import java.util.Objects; @DataClass(genConstructor = false, genBuilder = false, genEqualsHashCode = true) @DataClass.Suppress({"mOverlayPathsLock", "mOverlayPaths", "mSharedLibraryOverlayPathsLock", "mSharedLibraryOverlayPaths", "setOverlayPaths", "setCachedOverlayPaths"}) -public class PackageUserStateImpl implements PackageUserStateInternal { +public class PackageUserStateImpl extends WatchableImpl implements PackageUserStateInternal, + Snappable { @Nullable - protected ArraySet<String> mDisabledComponents; + protected WatchedArraySet<String> mDisabledComponentsWatched; @Nullable - protected ArraySet<String> mEnabledComponents; + protected WatchedArraySet<String> mEnabledComponentsWatched; private long mCeDataInode; private boolean mInstalled = true; @@ -63,11 +69,11 @@ public class PackageUserStateImpl implements PackageUserStateInternal { private String mLastDisableAppCaller; @Nullable - protected OverlayPaths mOverlayPaths; + private OverlayPaths mOverlayPaths; // Lib name to overlay paths @Nullable - protected ArrayMap<String, OverlayPaths> mSharedLibraryOverlayPaths; + protected WatchedArrayMap<String, OverlayPaths> mSharedLibraryOverlayPaths; @Nullable private String mSplashScreenTheme; @@ -76,33 +82,50 @@ public class PackageUserStateImpl implements PackageUserStateInternal { * Suspending package to suspend params */ @Nullable - private ArrayMap<String, SuspendParams> mSuspendParams; + private WatchedArrayMap<String, SuspendParams> mSuspendParams; @Nullable - private ArrayMap<ComponentName, Pair<String, Integer>> mComponentLabelIconOverrideMap; + private WatchedArrayMap<ComponentName, Pair<String, Integer>> mComponentLabelIconOverrideMap; private long mFirstInstallTime; @Nullable private final Watchable mWatchable; + @NonNull + final SnapshotCache<PackageUserStateImpl> mSnapshot; + + private SnapshotCache<PackageUserStateImpl> makeCache() { + return new SnapshotCache<PackageUserStateImpl>(this, this) { + @Override + public PackageUserStateImpl createSnapshot() { + return new PackageUserStateImpl(mWatchable, mSource); + }}; + } + + /** + * Only used for tests + */ public PackageUserStateImpl() { super(); mWatchable = null; + mSnapshot = makeCache(); } public PackageUserStateImpl(@NonNull Watchable watchable) { mWatchable = watchable; + mSnapshot = makeCache(); } public PackageUserStateImpl(@NonNull Watchable watchable, PackageUserStateImpl other) { mWatchable = watchable; - mDisabledComponents = ArrayUtils.cloneOrNull(other.mDisabledComponents); - mEnabledComponents = ArrayUtils.cloneOrNull(other.mEnabledComponents); + mDisabledComponentsWatched = other.mDisabledComponentsWatched == null + ? null : other.mDisabledComponentsWatched.snapshot(); + mEnabledComponentsWatched = other.mEnabledComponentsWatched == null + ? null : other.mEnabledComponentsWatched.snapshot(); mOverlayPaths = other.mOverlayPaths; - if (other.mSharedLibraryOverlayPaths != null) { - mSharedLibraryOverlayPaths = new ArrayMap<>(other.mSharedLibraryOverlayPaths); - } + mSharedLibraryOverlayPaths = other.mSharedLibraryOverlayPaths == null + ? null : other.mSharedLibraryOverlayPaths.snapshot(); mCeDataInode = other.mCeDataInode; mInstalled = other.mInstalled; mStopped = other.mStopped; @@ -117,16 +140,24 @@ public class PackageUserStateImpl implements PackageUserStateInternal { mHarmfulAppWarning = other.mHarmfulAppWarning; mLastDisableAppCaller = other.mLastDisableAppCaller; mSplashScreenTheme = other.mSplashScreenTheme; - mSuspendParams = other.mSuspendParams == null ? null : new ArrayMap<>(other.mSuspendParams); - mComponentLabelIconOverrideMap = other.mComponentLabelIconOverrideMap == null ? null - : new ArrayMap<>(other.mComponentLabelIconOverrideMap); + mSuspendParams = other.mSuspendParams == null ? null : other.mSuspendParams.snapshot(); + mComponentLabelIconOverrideMap = other.mComponentLabelIconOverrideMap == null + ? null : other.mComponentLabelIconOverrideMap.snapshot(); mFirstInstallTime = other.mFirstInstallTime; + mSnapshot = new SnapshotCache.Sealed<>(); } private void onChanged() { if (mWatchable != null) { mWatchable.dispatchChange(mWatchable); } + dispatchChange(this); + } + + @NonNull + @Override + public PackageUserStateImpl snapshot() { + return mSnapshot.snapshot(); } /** @@ -156,7 +187,8 @@ public class PackageUserStateImpl implements PackageUserStateInternal { public boolean setSharedLibraryOverlayPaths(@NonNull String library, @Nullable OverlayPaths paths) { if (mSharedLibraryOverlayPaths == null) { - mSharedLibraryOverlayPaths = new ArrayMap<>(); + mSharedLibraryOverlayPaths = new WatchedArrayMap<>(); + mSharedLibraryOverlayPaths.registerObserver(mSnapshot); } final OverlayPaths currentPaths = mSharedLibraryOverlayPaths.get(library); if (Objects.equals(paths, currentPaths)) { @@ -175,26 +207,41 @@ public class PackageUserStateImpl implements PackageUserStateInternal { @Nullable @Override - public ArraySet<String> getDisabledComponentsNoCopy() { - return mDisabledComponents; + public WatchedArraySet<String> getDisabledComponentsNoCopy() { + return mDisabledComponentsWatched; } @Nullable @Override - public ArraySet<String> getEnabledComponentsNoCopy() { - return mEnabledComponents; + public WatchedArraySet<String> getEnabledComponentsNoCopy() { + return mEnabledComponentsWatched; + } + + @NonNull + @Override + public ArraySet<String> getDisabledComponents() { + return mDisabledComponentsWatched == null + ? new ArraySet<>() : mDisabledComponentsWatched.untrackedStorage(); } + @NonNull + @Override + public ArraySet<String> getEnabledComponents() { + return mEnabledComponentsWatched == null + ? new ArraySet<>() : mEnabledComponentsWatched.untrackedStorage(); + } + + @Override public boolean isComponentEnabled(String componentName) { - // TODO: Not locked - return ArrayUtils.contains(mEnabledComponents, componentName); + return mEnabledComponentsWatched != null + && mEnabledComponentsWatched.contains(componentName); } @Override public boolean isComponentDisabled(String componentName) { - // TODO: Not locked - return ArrayUtils.contains(mDisabledComponents, componentName); + return mDisabledComponentsWatched != null + && mDisabledComponentsWatched.contains(componentName); } @Override @@ -242,7 +289,8 @@ public class PackageUserStateImpl implements PackageUserStateInternal { } } else { if (mComponentLabelIconOverrideMap == null) { - mComponentLabelIconOverrideMap = new ArrayMap<>(1); + mComponentLabelIconOverrideMap = new WatchedArrayMap<>(1); + mComponentLabelIconOverrideMap.registerObserver(mSnapshot); } mComponentLabelIconOverrideMap.put(component, Pair.create(nonLocalizedLabel, icon)); @@ -281,7 +329,8 @@ public class PackageUserStateImpl implements PackageUserStateInternal { public PackageUserStateImpl putSuspendParams(@NonNull String suspendingPackage, @Nullable SuspendParams suspendParams) { if (mSuspendParams == null) { - mSuspendParams = new ArrayMap<>(); + mSuspendParams = new WatchedArrayMap<>(); + mSuspendParams.registerObserver(mSnapshot); } if (!mSuspendParams.containsKey(suspendingPackage) || !Objects.equals(mSuspendParams.get(suspendingPackage), suspendParams)) { @@ -300,14 +349,50 @@ public class PackageUserStateImpl implements PackageUserStateInternal { return this; } - public @NonNull PackageUserStateImpl setDisabledComponents(@NonNull ArraySet<String> value) { - mDisabledComponents = value; + public @NonNull PackageUserStateImpl setDisabledComponents(@Nullable ArraySet<String> value) { + if (value == null) { + return this; + } + if (mDisabledComponentsWatched == null) { + mDisabledComponentsWatched = new WatchedArraySet<>(); + mDisabledComponentsWatched.registerObserver(mSnapshot); + } + mDisabledComponentsWatched.clear(); + mDisabledComponentsWatched.addAll(value); onChanged(); return this; } - public @NonNull PackageUserStateImpl setEnabledComponents(@NonNull ArraySet<String> value) { - mEnabledComponents = value; + public @NonNull PackageUserStateImpl setEnabledComponents(@Nullable ArraySet<String> value) { + if (value == null) { + return this; + } + if (mEnabledComponentsWatched == null) { + mEnabledComponentsWatched = new WatchedArraySet<>(); + mEnabledComponentsWatched.registerObserver(mSnapshot); + } + mEnabledComponentsWatched.clear(); + mEnabledComponentsWatched.addAll(value); + onChanged(); + return this; + } + + public @NonNull PackageUserStateImpl setEnabledComponents( + @Nullable WatchedArraySet<String> value) { + mEnabledComponentsWatched = value; + if (mEnabledComponentsWatched != null) { + mEnabledComponentsWatched.registerObserver(mSnapshot); + } + onChanged(); + return this; + } + + public @NonNull PackageUserStateImpl setDisabledComponents( + @Nullable WatchedArraySet<String> value) { + mDisabledComponentsWatched = value; + if (mDisabledComponentsWatched != null) { + mDisabledComponentsWatched.registerObserver(mSnapshot); + } onChanged(); return this; } @@ -397,7 +482,15 @@ public class PackageUserStateImpl implements PackageUserStateInternal { public @NonNull PackageUserStateImpl setSharedLibraryOverlayPaths( @NonNull ArrayMap<String, OverlayPaths> value) { - mSharedLibraryOverlayPaths = value; + if (value == null) { + return this; + } + if (mSharedLibraryOverlayPaths == null) { + mSharedLibraryOverlayPaths = new WatchedArrayMap<>(); + registerObserver(mSnapshot); + } + mSharedLibraryOverlayPaths.clear(); + mSharedLibraryOverlayPaths.putAll(value); onChanged(); return this; } @@ -413,14 +506,30 @@ public class PackageUserStateImpl implements PackageUserStateInternal { */ public @NonNull PackageUserStateImpl setSuspendParams( @NonNull ArrayMap<String, SuspendParams> value) { - mSuspendParams = value; + if (value == null) { + return this; + } + if (mSuspendParams == null) { + mSuspendParams = new WatchedArrayMap<>(); + registerObserver(mSnapshot); + } + mSuspendParams.clear(); + mSuspendParams.putAll(value); onChanged(); return this; } public @NonNull PackageUserStateImpl setComponentLabelIconOverrideMap( @NonNull ArrayMap<ComponentName, Pair<String, Integer>> value) { - mComponentLabelIconOverrideMap = value; + if (value == null) { + return this; + } + if (mComponentLabelIconOverrideMap == null) { + mComponentLabelIconOverrideMap = new WatchedArrayMap<>(); + registerObserver(mSnapshot); + } + mComponentLabelIconOverrideMap.clear(); + mComponentLabelIconOverrideMap.putAll(value); onChanged(); return this; } @@ -431,6 +540,73 @@ public class PackageUserStateImpl implements PackageUserStateInternal { return this; } + @Override + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(PackageUserStateImpl other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + PackageUserStateImpl that = (PackageUserStateImpl) o; + //noinspection PointlessBooleanExpression + return Objects.equals(mDisabledComponentsWatched, that.mDisabledComponentsWatched) + && Objects.equals(mEnabledComponentsWatched, that.mEnabledComponentsWatched) + && mCeDataInode == that.mCeDataInode + && mInstalled == that.mInstalled + && mStopped == that.mStopped + && mNotLaunched == that.mNotLaunched + && mHidden == that.mHidden + && mDistractionFlags == that.mDistractionFlags + && mInstantApp == that.mInstantApp + && mVirtualPreload == that.mVirtualPreload + && mEnabledState == that.mEnabledState + && mInstallReason == that.mInstallReason + && mUninstallReason == that.mUninstallReason + && Objects.equals(mHarmfulAppWarning, that.mHarmfulAppWarning) + && Objects.equals(mLastDisableAppCaller, that.mLastDisableAppCaller) + && Objects.equals(mOverlayPaths, that.mOverlayPaths) + && Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths) + && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme) + && Objects.equals(mSuspendParams, that.mSuspendParams) + && Objects.equals(mComponentLabelIconOverrideMap, + that.mComponentLabelIconOverrideMap) + && mFirstInstallTime == that.mFirstInstallTime + && Objects.equals(mWatchable, that.mWatchable); + } + + @Override + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + Objects.hashCode(mDisabledComponentsWatched); + _hash = 31 * _hash + Objects.hashCode(mEnabledComponentsWatched); + _hash = 31 * _hash + Long.hashCode(mCeDataInode); + _hash = 31 * _hash + Boolean.hashCode(mInstalled); + _hash = 31 * _hash + Boolean.hashCode(mStopped); + _hash = 31 * _hash + Boolean.hashCode(mNotLaunched); + _hash = 31 * _hash + Boolean.hashCode(mHidden); + _hash = 31 * _hash + mDistractionFlags; + _hash = 31 * _hash + Boolean.hashCode(mInstantApp); + _hash = 31 * _hash + Boolean.hashCode(mVirtualPreload); + _hash = 31 * _hash + mEnabledState; + _hash = 31 * _hash + mInstallReason; + _hash = 31 * _hash + mUninstallReason; + _hash = 31 * _hash + Objects.hashCode(mHarmfulAppWarning); + _hash = 31 * _hash + Objects.hashCode(mLastDisableAppCaller); + _hash = 31 * _hash + Objects.hashCode(mOverlayPaths); + _hash = 31 * _hash + Objects.hashCode(mSharedLibraryOverlayPaths); + _hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme); + _hash = 31 * _hash + Objects.hashCode(mSuspendParams); + _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap); + _hash = 31 * _hash + Long.hashCode(mFirstInstallTime); + _hash = 31 * _hash + Objects.hashCode(mWatchable); + return _hash; + } + // Code below generated by codegen v1.0.23. @@ -447,13 +623,13 @@ public class PackageUserStateImpl implements PackageUserStateInternal { @DataClass.Generated.Member - public @Nullable ArraySet<String> getDisabledComponents() { - return mDisabledComponents; + public @Nullable WatchedArraySet<String> getDisabledComponentsWatched() { + return mDisabledComponentsWatched; } @DataClass.Generated.Member - public @Nullable ArraySet<String> getEnabledComponents() { - return mEnabledComponents; + public @Nullable WatchedArraySet<String> getEnabledComponentsWatched() { + return mEnabledComponentsWatched; } @DataClass.Generated.Member @@ -527,7 +703,7 @@ public class PackageUserStateImpl implements PackageUserStateInternal { } @DataClass.Generated.Member - public @Nullable ArrayMap<String,OverlayPaths> getSharedLibraryOverlayPaths() { + public @Nullable WatchedArrayMap<String,OverlayPaths> getSharedLibraryOverlayPaths() { return mSharedLibraryOverlayPaths; } @@ -540,12 +716,12 @@ public class PackageUserStateImpl implements PackageUserStateInternal { * Suspending package to suspend params */ @DataClass.Generated.Member - public @Nullable ArrayMap<String,SuspendParams> getSuspendParams() { + public @Nullable WatchedArrayMap<String,SuspendParams> getSuspendParams() { return mSuspendParams; } @DataClass.Generated.Member - public @Nullable ArrayMap<ComponentName,Pair<String,Integer>> getComponentLabelIconOverrideMap() { + public @Nullable WatchedArrayMap<ComponentName,Pair<String,Integer>> getComponentLabelIconOverrideMap() { return mComponentLabelIconOverrideMap; } @@ -559,80 +735,49 @@ public class PackageUserStateImpl implements PackageUserStateInternal { return mWatchable; } - @Override @DataClass.Generated.Member - public boolean equals(@Nullable Object o) { - // You can override field equality logic by defining either of the methods like: - // boolean fieldNameEquals(PackageUserStateImpl other) { ... } - // boolean fieldNameEquals(FieldType otherValue) { ... } + public @NonNull SnapshotCache<PackageUserStateImpl> getSnapshot() { + return mSnapshot; + } - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - @SuppressWarnings("unchecked") - PackageUserStateImpl that = (PackageUserStateImpl) o; - //noinspection PointlessBooleanExpression - return true - && Objects.equals(mDisabledComponents, that.mDisabledComponents) - && Objects.equals(mEnabledComponents, that.mEnabledComponents) - && mCeDataInode == that.mCeDataInode - && mInstalled == that.mInstalled - && mStopped == that.mStopped - && mNotLaunched == that.mNotLaunched - && mHidden == that.mHidden - && mDistractionFlags == that.mDistractionFlags - && mInstantApp == that.mInstantApp - && mVirtualPreload == that.mVirtualPreload - && mEnabledState == that.mEnabledState - && mInstallReason == that.mInstallReason - && mUninstallReason == that.mUninstallReason - && Objects.equals(mHarmfulAppWarning, that.mHarmfulAppWarning) - && Objects.equals(mLastDisableAppCaller, that.mLastDisableAppCaller) - && Objects.equals(mOverlayPaths, that.mOverlayPaths) - && Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths) - && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme) - && Objects.equals(mSuspendParams, that.mSuspendParams) - && Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap) - && mFirstInstallTime == that.mFirstInstallTime - && Objects.equals(mWatchable, that.mWatchable); + @DataClass.Generated.Member + public @NonNull PackageUserStateImpl setDisabledComponentsWatched(@NonNull WatchedArraySet<String> value) { + mDisabledComponentsWatched = value; + return this; } - @Override @DataClass.Generated.Member - public int hashCode() { - // You can override field hashCode logic by defining methods like: - // int fieldNameHashCode() { ... } + public @NonNull PackageUserStateImpl setEnabledComponentsWatched(@NonNull WatchedArraySet<String> value) { + mEnabledComponentsWatched = value; + return this; + } - int _hash = 1; - _hash = 31 * _hash + Objects.hashCode(mDisabledComponents); - _hash = 31 * _hash + Objects.hashCode(mEnabledComponents); - _hash = 31 * _hash + Long.hashCode(mCeDataInode); - _hash = 31 * _hash + Boolean.hashCode(mInstalled); - _hash = 31 * _hash + Boolean.hashCode(mStopped); - _hash = 31 * _hash + Boolean.hashCode(mNotLaunched); - _hash = 31 * _hash + Boolean.hashCode(mHidden); - _hash = 31 * _hash + mDistractionFlags; - _hash = 31 * _hash + Boolean.hashCode(mInstantApp); - _hash = 31 * _hash + Boolean.hashCode(mVirtualPreload); - _hash = 31 * _hash + mEnabledState; - _hash = 31 * _hash + mInstallReason; - _hash = 31 * _hash + mUninstallReason; - _hash = 31 * _hash + Objects.hashCode(mHarmfulAppWarning); - _hash = 31 * _hash + Objects.hashCode(mLastDisableAppCaller); - _hash = 31 * _hash + Objects.hashCode(mOverlayPaths); - _hash = 31 * _hash + Objects.hashCode(mSharedLibraryOverlayPaths); - _hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme); - _hash = 31 * _hash + Objects.hashCode(mSuspendParams); - _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap); - _hash = 31 * _hash + Long.hashCode(mFirstInstallTime); - _hash = 31 * _hash + Objects.hashCode(mWatchable); - return _hash; + @DataClass.Generated.Member + public @NonNull PackageUserStateImpl setSharedLibraryOverlayPaths(@NonNull WatchedArrayMap<String,OverlayPaths> value) { + mSharedLibraryOverlayPaths = value; + return this; + } + + /** + * Suspending package to suspend params + */ + @DataClass.Generated.Member + public @NonNull PackageUserStateImpl setSuspendParams(@NonNull WatchedArrayMap<String,SuspendParams> value) { + mSuspendParams = value; + return this; + } + + @DataClass.Generated.Member + public @NonNull PackageUserStateImpl setComponentLabelIconOverrideMap(@NonNull WatchedArrayMap<ComponentName,Pair<String,Integer>> value) { + mComponentLabelIconOverrideMap = value; + return this; } @DataClass.Generated( - time = 1643854846064L, + time = 1644638242940L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java", - inputSignatures = "protected @android.annotation.Nullable android.util.ArraySet<java.lang.String> mDisabledComponents\nprotected @android.annotation.Nullable android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprotected @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate long mFirstInstallTime\nprivate final @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate void onChanged()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTime(long)\nclass PackageUserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserStateInternal]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") + inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate long mFirstInstallTime\nprivate final @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTime(long)\npublic @java.lang.Override boolean equals(java.lang.Object)\npublic @java.lang.Override int hashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java index bc521ceab1bb..96225c012999 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java @@ -20,10 +20,11 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.pm.pkg.FrameworkPackageUserState; -import android.util.ArrayMap; -import android.util.ArraySet; import android.util.Pair; +import com.android.server.utils.WatchedArrayMap; +import com.android.server.utils.WatchedArraySet; + /** * Internal variant of {@link PackageUserState} that includes data not exposed as API. This is * still read-only and should be used inside system server code when possible over the @@ -35,13 +36,13 @@ public interface PackageUserStateInternal extends PackageUserState, FrameworkPac // TODO: Make non-null with emptyMap() @Nullable - ArrayMap<String, SuspendParams> getSuspendParams(); + WatchedArrayMap<String, SuspendParams> getSuspendParams(); @Nullable - ArraySet<String> getDisabledComponentsNoCopy(); + WatchedArraySet<String> getDisabledComponentsNoCopy(); @Nullable - ArraySet<String> getEnabledComponentsNoCopy(); + WatchedArraySet<String> getEnabledComponentsNoCopy(); @Nullable Pair<String, Integer> getOverrideLabelIconForComponent(@NonNull ComponentName componentName); diff --git a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java index 43eac536ee34..94a87f3c1289 100644 --- a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java +++ b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java @@ -23,6 +23,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.permission.LegacyPermissionState; import com.android.server.pm.pkg.component.ParsedProcess; import java.util.List; @@ -33,7 +34,7 @@ public interface SharedUserApi { String getName(); @UserIdInt - int getUserId(); + int getAppId(); // flags that are associated with this uid, regardless of any package flags int getUidFlags(); @@ -65,4 +66,7 @@ public interface SharedUserApi { ArrayMap<String, ParsedProcess> getProcesses(); boolean isPrivileged(); + + @NonNull + LegacyPermissionState getSharedUserLegacyPermissionState(); } diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java index c2b3cbc026c2..03208188b3ea 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java @@ -20,6 +20,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ActivityInfo; +import java.util.Set; + /** @hide **/ //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public interface ParsedActivity extends ParsedMainComponent { @@ -59,6 +61,12 @@ public interface ParsedActivity extends ParsedMainComponent { @Nullable String getPermission(); + /** + * Gets the trusted host certificates of apps that are allowed to embed this activity. + */ + @NonNull + Set<String> getKnownActivityEmbeddingCerts(); + int getPersistableMode(); int getPrivateFlags(); diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java index 91c0b07e4a1d..acd5a813e32f 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java @@ -23,6 +23,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString; +import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForStringSet; import android.annotation.NonNull; import android.annotation.Nullable; @@ -33,12 +34,17 @@ import android.content.pm.PackageManager; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; import com.android.server.pm.pkg.parsing.ParsingUtils; +import java.util.Collections; +import java.util.Locale; +import java.util.Set; + /** * @hide **/ @@ -63,6 +69,8 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse @Nullable @DataClass.ParcelWith(ForInternedString.class) private String permission; + @Nullable + private Set<String> mKnownActivityEmbeddingCerts; private int launchMode; private int documentLaunchMode; @@ -113,6 +121,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse this.rotationAnimation = other.rotationAnimation; this.colorMode = other.colorMode; this.windowLayout = other.windowLayout; + this.mKnownActivityEmbeddingCerts = other.mKnownActivityEmbeddingCerts; } /** @@ -239,6 +248,25 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse return this; } + @NonNull + @Override + public Set<String> getKnownActivityEmbeddingCerts() { + return mKnownActivityEmbeddingCerts == null ? Collections.emptySet() + : mKnownActivityEmbeddingCerts; + } + + /** + * Sets the trusted host certificates of apps that are allowed to embed this activity. + */ + public void setKnownActivityEmbeddingCerts(@NonNull Set<String> knownActivityEmbeddingCerts) { + // Convert the provided digest to upper case for consistent Set membership + // checks when verifying the signing certificate digests of requesting apps. + this.mKnownActivityEmbeddingCerts = new ArraySet<>(); + for (String knownCert : knownActivityEmbeddingCerts) { + this.mKnownActivityEmbeddingCerts.add(knownCert.toUpperCase(Locale.US)); + } + } + public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("Activity{"); @@ -287,6 +315,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse } else { dest.writeBoolean(false); } + sForStringSet.parcel(this.mKnownActivityEmbeddingCerts, dest, flags); } public ParsedActivityImpl() { @@ -320,6 +349,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse if (in.readBoolean()) { windowLayout = new ActivityInfo.WindowLayout(in); } + this.mKnownActivityEmbeddingCerts = sForStringSet.unparcel(in); } @NonNull @@ -344,7 +374,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -360,6 +390,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse @Nullable String taskAffinity, int privateFlags, @Nullable String permission, + @Nullable Set<String> knownActivityEmbeddingCerts, int launchMode, int documentLaunchMode, int maxRecents, @@ -383,6 +414,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse this.taskAffinity = taskAffinity; this.privateFlags = privateFlags; this.permission = permission; + this.mKnownActivityEmbeddingCerts = knownActivityEmbeddingCerts; this.launchMode = launchMode; this.documentLaunchMode = documentLaunchMode; this.maxRecents = maxRecents; @@ -645,10 +677,10 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse } @DataClass.Generated( - time = 1641431949361L, + time = 1644372875433L, codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java", - inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAlias(java.lang.String,android.content.pm.parsing.component.ParsedActivity)\npublic android.content.pm.parsing.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic android.content.pm.parsing.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic android.content.pm.parsing.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic android.content.pm.parsing.component.ParsedActivityImpl setPermission(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") + sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java", + inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.server.pm.pkg.component.ParsedActivity)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java index a6c22a18f9ca..bbbf5982acb4 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java @@ -21,6 +21,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static com.android.server.pm.pkg.component.ComponentParseUtils.flag; import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; +import static com.android.server.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts; import android.annotation.NonNull; import android.annotation.Nullable; @@ -151,7 +152,8 @@ public class ParsedActivityUtils { | flag(ActivityInfo.FLAG_SHOW_WHEN_LOCKED, R.styleable.AndroidManifestActivity_showWhenLocked, sa) | flag(ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE, R.styleable.AndroidManifestActivity_supportsPictureInPicture, sa) | flag(ActivityInfo.FLAG_TURN_SCREEN_ON, R.styleable.AndroidManifestActivity_turnScreenOn, sa) - | flag(ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING, R.styleable.AndroidManifestActivity_preferMinimalPostProcessing, sa))); + | flag(ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING, R.styleable.AndroidManifestActivity_preferMinimalPostProcessing, sa)) + | flag(ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING, R.styleable.AndroidManifestActivity_allowUntrustedActivityEmbedding, sa)); activity.setPrivateFlags(activity.getPrivateFlags() | (flag(ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED, R.styleable.AndroidManifestActivity_inheritShowWhenLocked, sa) @@ -338,6 +340,20 @@ public class ParsedActivityUtils { activity.setPermission(permission != null ? permission : pkg.getPermission()); } + final ParseResult<Set<String>> knownActivityEmbeddingCertsResult = + parseKnownActivityEmbeddingCerts(array, resources, isAlias + ? R.styleable.AndroidManifestActivityAlias_knownActivityEmbeddingCerts + : R.styleable.AndroidManifestActivity_knownActivityEmbeddingCerts, input); + if (knownActivityEmbeddingCertsResult.isError()) { + return input.error(knownActivityEmbeddingCertsResult); + } else { + final Set<String> knownActivityEmbeddingCerts = knownActivityEmbeddingCertsResult + .getResult(); + if (knownActivityEmbeddingCerts != null) { + activity.setKnownActivityEmbeddingCerts(knownActivityEmbeddingCerts); + } + } + final boolean setExported = array.hasValue(exportedAttr); if (setExported) { activity.setExported(array.getBoolean(exportedAttr, false)); diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java index b92b84518067..f199841db3e0 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java @@ -549,6 +549,7 @@ public class PackageInfoWithoutStateUtils { ai.metaData = a.getMetaData(); } ai.applicationInfo = applicationInfo; + ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts()); return ai; } diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java index 52d9b7a3abc1..1754877b5571 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java @@ -381,6 +381,12 @@ public interface ParsingPackage extends ParsingPackageRead { ParsingPackage setLocaleConfigRes(int localeConfigRes); + /** + * Sets the trusted host certificates of apps that are allowed to embed activities of this + * application. + */ + ParsingPackage setKnownActivityEmbeddingCerts(Set<String> knownActivityEmbeddingCerts); + // TODO(b/135203078): This class no longer has access to ParsedPackage, find a replacement // for moving to the next step @CallSuper diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java index 84422cd0751f..c4b1275ba769 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java @@ -563,6 +563,9 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, return (mBooleans & flag) != 0; } + @Nullable + private Set<String> mKnownActivityEmbeddingCerts; + // Derived fields @NonNull private UUID mStorageUuid; @@ -1150,6 +1153,9 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, appInfo.setVersionCode(mLongVersionCode); appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess()); appInfo.setLocaleConfigRes(mLocaleConfigRes); + if (mKnownActivityEmbeddingCerts != null) { + appInfo.setKnownActivityEmbeddingCerts(mKnownActivityEmbeddingCerts); + } return appInfo; } @@ -1329,6 +1335,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, dest.writeInt(this.nativeHeapZeroInitialized); sForBoolean.parcel(this.requestRawExternalStorageAccess, dest, flags); dest.writeInt(this.mLocaleConfigRes); + sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags); } public ParsingPackageImpl(Parcel in) { @@ -1477,6 +1484,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, this.nativeHeapZeroInitialized = in.readInt(); this.requestRawExternalStorageAccess = sForBoolean.unparcel(in); this.mLocaleConfigRes = in.readInt(); + this.mKnownActivityEmbeddingCerts = sForStringSet.unparcel(in); assignDerivedFields(); } @@ -2376,6 +2384,13 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, return getBoolean(Booleans.RESET_ENABLED_SETTINGS_ON_APP_DATA_CLEARED); } + @NonNull + @Override + public Set<String> getKnownActivityEmbeddingCerts() { + return mKnownActivityEmbeddingCerts == null ? Collections.emptySet() + : mKnownActivityEmbeddingCerts; + } + @Override public boolean shouldInheritKeyStoreKeys() { return getBoolean(Booleans.INHERIT_KEYSTORE_KEYS); @@ -2973,4 +2988,11 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, mLocaleConfigRes = value; return this; } + + @Override + public ParsingPackage setKnownActivityEmbeddingCerts( + @Nullable Set<String> knownEmbeddingCerts) { + mKnownActivityEmbeddingCerts = knownEmbeddingCerts; + return this; + } } diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index e8f03ff36516..a89198034748 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -30,6 +30,8 @@ import static android.os.Build.VERSION_CODES.DONUT; import static android.os.Build.VERSION_CODES.O; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; +import static com.android.server.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts; + import android.annotation.AnyRes; import android.annotation.CheckResult; import android.annotation.IntDef; @@ -2009,6 +2011,19 @@ public class ParsingPackageUtils { .AndroidManifestApplication_requestForegroundServiceExemption, false)); } + final ParseResult<Set<String>> knownActivityEmbeddingCertsResult = + parseKnownActivityEmbeddingCerts(sa, res, + R.styleable.AndroidManifestApplication_knownActivityEmbeddingCerts, + input); + if (knownActivityEmbeddingCertsResult.isError()) { + return input.error(knownActivityEmbeddingCertsResult); + } else { + final Set<String> knownActivityEmbeddingCerts = knownActivityEmbeddingCertsResult + .getResult(); + if (knownActivityEmbeddingCerts != null) { + pkg.setKnownActivityEmbeddingCerts(knownActivityEmbeddingCerts); + } + } } finally { sa.recycle(); } diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java index 9430e988db2b..cb474df8b469 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java @@ -22,6 +22,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; +import android.content.res.Resources; +import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.os.Parcel; import android.os.Parcelable; @@ -38,6 +40,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Set; /** @hide **/ public class ParsingUtils { @@ -168,4 +171,50 @@ public class ParsingUtils { return list; } } + + /** + * Parse the {@link android.R.attr#knownActivityEmbeddingCerts} attribute, if available. + */ + @NonNull + public static ParseResult<Set<String>> parseKnownActivityEmbeddingCerts(@NonNull TypedArray sa, + @NonNull Resources res, int resourceId, @NonNull ParseInput input) { + if (!sa.hasValue(resourceId)) { + return input.success(null); + } + + final int knownActivityEmbeddingCertsResource = sa.getResourceId(resourceId, 0); + if (knownActivityEmbeddingCertsResource != 0) { + // The knownCerts attribute supports both a string array resource as well as a + // string resource for the case where the permission should only be granted to a + // single known signer. + Set<String> knownEmbeddingCertificates = null; + final String resourceType = res.getResourceTypeName( + knownActivityEmbeddingCertsResource); + if (resourceType.equals("array")) { + final String[] knownCerts = res.getStringArray(knownActivityEmbeddingCertsResource); + if (knownCerts != null) { + knownEmbeddingCertificates = Set.of(knownCerts); + } + } else { + final String knownCert = res.getString(knownActivityEmbeddingCertsResource); + if (knownCert != null) { + knownEmbeddingCertificates = Set.of(knownCert); + } + } + if (knownEmbeddingCertificates == null || knownEmbeddingCertificates.isEmpty()) { + return input.error("Defined a knownActivityEmbeddingCerts attribute but the " + + "provided resource is null"); + } + return input.success(knownEmbeddingCertificates); + } + + // If the knownCerts resource ID is null - the app specified a string value for the + // attribute representing a single trusted signer. + final String knownCert = sa.getString(resourceId); + if (knownCert == null || knownCert.isEmpty()) { + return input.error("Defined a knownActivityEmbeddingCerts attribute but the provided " + + "string is empty"); + } + return input.success(Set.of(knownCert)); + } } diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java index 2ef90ac1652e..3205b76d6dad 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java @@ -21,6 +21,8 @@ import android.annotation.Nullable; import android.content.pm.ApplicationInfo; import android.util.SparseArray; +import java.util.Set; + /** * Container for fields that are eventually exposed through {@link ApplicationInfo}. * <p> @@ -285,6 +287,13 @@ public interface PkgWithoutStateAppInfo { String getPermission(); /** + * @see ApplicationInfo#knownActivityEmbeddingCerts + * @see R.styleable#AndroidManifestApplication_knownActivityEmbeddingCerts + */ + @NonNull + Set<String> getKnownActivityEmbeddingCerts(); + + /** * @see ApplicationInfo#processName * @see R.styleable#AndroidManifestApplication_process */ diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index c9a8701ec7af..c637c6764092 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -37,7 +37,7 @@ import android.app.AppOpsManagerInternal; import android.app.TaskInfo; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledSince; +import android.compat.annotation.EnabledAfter; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -58,6 +58,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.permission.PermissionControllerManager; +import android.permission.PermissionManager; import android.provider.Settings; import android.provider.Telephony; import android.telecom.TelecomManager; @@ -141,7 +142,7 @@ public final class PermissionPolicyService extends SystemService { * This change reflects the presence of the new Notification Permission */ @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2) private static final long NOTIFICATION_PERM_CHANGE_ID = 194833441L; private List<String> mAppOpPermissions; @@ -149,7 +150,6 @@ public final class PermissionPolicyService extends SystemService { private Context mContext; private PackageManagerInternal mPackageManagerInternal; private NotificationManagerInternal mNotificationManager; - private PermissionManagerServiceInternal mPermissionManagerService; private final PackageManager mPackageManager; public PermissionPolicyService(@NonNull Context context) { @@ -1001,13 +1001,48 @@ public final class PermissionPolicyService extends SystemService { private class Internal extends PermissionPolicyInternal { - private ActivityInterceptorCallback mActivityInterceptorCallback = + // UIDs that, if a grant dialog is shown for POST_NOTIFICATIONS before next reboot, + // should display a "continue allowing" message, rather than an "allow" message + private final ArraySet<Integer> mContinueNotifGrantMessageUids = new ArraySet<>(); + + private final ActivityInterceptorCallback mActivityInterceptorCallback = new ActivityInterceptorCallback() { @Nullable @Override public ActivityInterceptorCallback.ActivityInterceptResult intercept( ActivityInterceptorInfo info) { - return null; + String action = info.intent.getAction(); + ActivityInterceptResult result = null; + if (!PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(action) + && !PackageManager.ACTION_REQUEST_PERMISSIONS.equals(action)) { + return null; + } + // Only this interceptor can add LEGACY_ACCESS_PERMISSION_NAMES + if (info.intent.getStringArrayExtra(PackageManager + .EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES) + != null) { + result = new ActivityInterceptResult( + new Intent(info.intent), info.checkedOptions); + result.intent.removeExtra(PackageManager + .EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES); + } + if (PackageManager.ACTION_REQUEST_PERMISSIONS.equals(action) + && !mContinueNotifGrantMessageUids.contains(info.realCallingUid)) { + return result; + } + if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(action)) { + String otherPkg = info.intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); + if (otherPkg == null || (mPackageManager.getPermissionFlags( + POST_NOTIFICATIONS, otherPkg, UserHandle.of(info.userId)) + & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) { + return result; + } + } + + mContinueNotifGrantMessageUids.remove(info.realCallingUid); + return new ActivityInterceptResult(info.intent.putExtra(PackageManager + .EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES, + new String[] { POST_NOTIFICATIONS }), info.checkedOptions); } @Override @@ -1057,12 +1092,21 @@ public final class PermissionPolicyService extends SystemService { launchNotificationPermissionRequestDialog(packageName, user, taskId); } - private void clearNotificationReviewFlagsIfNeeded(String packageName, UserHandle userId) { - if (!CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, packageName, userId)) { + private void clearNotificationReviewFlagsIfNeeded(String packageName, UserHandle user) { + if (!CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, packageName, user) + || ((mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, packageName, user) + & FLAG_PERMISSION_REVIEW_REQUIRED) == 0)) { return; } - mPackageManager.updatePermissionFlags(POST_NOTIFICATIONS, packageName, - FLAG_PERMISSION_REVIEW_REQUIRED, 0, userId); + try { + int uid = mPackageManager.getPackageUidAsUser(packageName, 0, + user.getIdentifier()); + mContinueNotifGrantMessageUids.add(uid); + mPackageManager.updatePermissionFlags(POST_NOTIFICATIONS, packageName, + FLAG_PERMISSION_REVIEW_REQUIRED, 0, user); + } catch (PackageManager.NameNotFoundException e) { + // Do nothing + } } private void launchNotificationPermissionRequestDialog(String pkgName, UserHandle user, @@ -1142,8 +1186,10 @@ public final class PermissionPolicyService extends SystemService { if (pkg == null || pkg.getPackageName() == null || Objects.equals(pkgName, mPackageManager.getPermissionControllerPackageName()) || pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) { - Slog.w(LOG_TAG, "Cannot check for Notification prompt, no package for " - + pkgName + " or pkg is Permission Controller"); + if (pkg == null) { + Slog.w(LOG_TAG, "Cannot check for Notification prompt, no package for " + + pkgName); + } return false; } @@ -1175,9 +1221,10 @@ public final class PermissionPolicyService extends SystemService { } boolean hasCreatedNotificationChannels = mNotificationManager .getNumNotificationChannelsForPackage(pkg.getPackageName(), uid, true) > 0; - boolean needsReview = (mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, pkgName, - user) & FLAG_PERMISSION_REVIEW_REQUIRED) != 0; - return hasCreatedNotificationChannels && needsReview; + int flags = mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, pkgName, user); + boolean explicitlySet = (flags & PermissionManager.EXPLICIT_SET_FLAGS) != 0; + boolean needsReview = (flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0; + return hasCreatedNotificationChannels && (needsReview || !explicitlySet); } } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 6e4651c8ab42..c0abbf62e2a7 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -298,6 +298,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // must match: config_doubleTapOnHomeBehavior in config.xml static final int DOUBLE_TAP_HOME_NOTHING = 0; static final int DOUBLE_TAP_HOME_RECENT_SYSTEM_UI = 1; + static final int DOUBLE_TAP_HOME_PIP_MENU = 2; static final int SHORT_PRESS_WINDOW_NOTHING = 0; static final int SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE = 1; @@ -1743,11 +1744,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Delay handling home if a double-tap is possible. if (mDoubleTapOnHomeBehavior != DOUBLE_TAP_HOME_NOTHING) { - mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); // just in case - mHomeDoubleTapPending = true; - mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable, - ViewConfiguration.getDoubleTapTimeout()); - return -1; + // For the picture-in-picture menu, only add the delay if a pip is there. + if (mDoubleTapOnHomeBehavior != DOUBLE_TAP_HOME_PIP_MENU + || mPictureInPictureVisible) { + mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); // just in case + mHomeDoubleTapPending = true; + mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable, + ViewConfiguration.getDoubleTapTimeout()); + return -1; + } } // Post to main thread to avoid blocking input pipeline. @@ -1780,7 +1785,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mHomeDoubleTapPending) { mHomeDoubleTapPending = false; mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); - handleDoubleTapOnHome(); + mHandler.post(this::handleDoubleTapOnHome); // TODO(multi-display): Remove display id check once we support recents on // multi-display } else if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI @@ -1798,13 +1803,29 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private void handleDoubleTapOnHome() { - if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) { - mHomeConsumed = true; - toggleRecentApps(); + if (mHomeConsumed) { + return; + } + switch (mDoubleTapOnHomeBehavior) { + case DOUBLE_TAP_HOME_RECENT_SYSTEM_UI: + mHomeConsumed = true; + toggleRecentApps(); + break; + case DOUBLE_TAP_HOME_PIP_MENU: + mHomeConsumed = true; + showPictureInPictureMenuInternal(); + break; + default: + Log.w(TAG, "No action or undefined behavior for double tap home: " + + mDoubleTapOnHomeBehavior); + break; } } private void handleLongPressOnHome(int deviceId, long eventTime) { + if (mHomeConsumed) { + return; + } if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_NOTHING) { return; } @@ -2362,8 +2383,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mDoubleTapOnHomeBehavior = res.getInteger( com.android.internal.R.integer.config_doubleTapOnHomeBehavior); if (mDoubleTapOnHomeBehavior < DOUBLE_TAP_HOME_NOTHING || - mDoubleTapOnHomeBehavior > DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) { - mDoubleTapOnHomeBehavior = LONG_PRESS_HOME_NOTHING; + mDoubleTapOnHomeBehavior > DOUBLE_TAP_HOME_PIP_MENU) { + mDoubleTapOnHomeBehavior = DOUBLE_TAP_HOME_NOTHING; } mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_NOTHING; @@ -5741,6 +5762,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { return "DOUBLE_TAP_HOME_NOTHING"; case DOUBLE_TAP_HOME_RECENT_SYSTEM_UI: return "DOUBLE_TAP_HOME_RECENT_SYSTEM_UI"; + case DOUBLE_TAP_HOME_PIP_MENU: + return "DOUBLE_TAP_HOME_PIP_MENU"; default: return Integer.toString(behavior); } diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java index cea84b57377c..2d2bad27ecd3 100644 --- a/services/core/java/com/android/server/power/LowPowerStandbyController.java +++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java @@ -42,6 +42,7 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; +import com.android.server.net.NetworkPolicyManagerInternal; import java.io.PrintWriter; import java.util.Arrays; @@ -394,7 +395,11 @@ public final class LowPowerStandbyController { /** Notify other system components about the updated Low Power Standby active state */ private void notifyActiveChanged(boolean active) { final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); + final NetworkPolicyManagerInternal npmi = LocalServices.getService( + NetworkPolicyManagerInternal.class); + pmi.setLowPowerStandbyActive(active); + npmi.setLowPowerStandbyActive(active); } @VisibleForTesting @@ -580,7 +585,10 @@ public final class LowPowerStandbyController { private void notifyAllowlistChanged(int[] allowlistUids) { final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); + final NetworkPolicyManagerInternal npmi = LocalServices.getService( + NetworkPolicyManagerInternal.class); pmi.setLowPowerStandbyAllowlist(allowlistUids); + npmi.setLowPowerStandbyAllowlist(allowlistUids); } private final class LocalService extends LowPowerStandbyControllerInternal { diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java index 040fffa885f1..19c8cd2050e4 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java @@ -160,7 +160,7 @@ public final class SensorPrivacyService extends SystemService { private SensorPrivacyManagerInternalImpl mSensorPrivacyManagerInternal; - private EmergencyCallHelper mEmergencyCallHelper; + private CallStateHelper mCallStateHelper; private KeyguardManager mKeyguardManager; private int mCurrentUser = USER_NULL; @@ -191,7 +191,7 @@ public final class SensorPrivacyService extends SystemService { public void onBootPhase(int phase) { if (phase == PHASE_SYSTEM_SERVICES_READY) { mKeyguardManager = mContext.getSystemService(KeyguardManager.class); - mEmergencyCallHelper = new EmergencyCallHelper(); + mCallStateHelper = new CallStateHelper(); } else if (phase == PHASE_ACTIVITY_MANAGER_READY) { mCameraPrivacyLightController = new CameraPrivacyLightController(mContext); } @@ -702,7 +702,7 @@ public final class SensorPrivacyService extends SystemService { } private boolean canChangeIndividualSensorPrivacy(@UserIdInt int userId, int sensor) { - if (sensor == MICROPHONE && mEmergencyCallHelper.isInEmergencyCall()) { + if (sensor == MICROPHONE && mCallStateHelper.isInEmergencyCall()) { // During emergency call the microphone toggle managed automatically Log.i(TAG, "Can't change mic toggle during an emergency call"); return false; @@ -1523,16 +1523,16 @@ public final class SensorPrivacyService extends SystemService { } } - private class EmergencyCallHelper { + private class CallStateHelper { private OutgoingEmergencyStateCallback mEmergencyStateCallback; private CallStateCallback mCallStateCallback; private boolean mIsInEmergencyCall; private boolean mMicUnmutedForEmergencyCall; - private Object mEmergencyStateLock = new Object(); + private Object mCallStateLock = new Object(); - EmergencyCallHelper() { + CallStateHelper() { mEmergencyStateCallback = new OutgoingEmergencyStateCallback(); mCallStateCallback = new CallStateCallback(); @@ -1543,7 +1543,7 @@ public final class SensorPrivacyService extends SystemService { } boolean isInEmergencyCall() { - synchronized (mEmergencyStateLock) { + synchronized (mCallStateLock) { return mIsInEmergencyCall; } } @@ -1563,12 +1563,14 @@ public final class SensorPrivacyService extends SystemService { public void onCallStateChanged(int state) { if (state == TelephonyManager.CALL_STATE_IDLE) { onCallOver(); + } else { + onCall(); } } } private void onEmergencyCall() { - synchronized (mEmergencyStateLock) { + synchronized (mCallStateLock) { if (!mIsInEmergencyCall) { mIsInEmergencyCall = true; if (mSensorPrivacyServiceImpl @@ -1583,8 +1585,19 @@ public final class SensorPrivacyService extends SystemService { } } + private void onCall() { + long token = Binder.clearCallingIdentity(); + try { + synchronized (mCallStateLock) { + mSensorPrivacyServiceImpl.showSensorUseDialog(MICROPHONE); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + private void onCallOver() { - synchronized (mEmergencyStateLock) { + synchronized (mCallStateLock) { if (mIsInEmergencyCall) { mIsInEmergencyCall = false; if (mMicUnmutedForEmergencyCall) { diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index f6a93591591d..adee325f90c1 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -2392,7 +2392,8 @@ public class StatsPullAtomService extends SystemService { metrics.unaccountedKb, metrics.gpuTotalUsageKb, metrics.gpuPrivateAllocationsKb, - metrics.dmaBufTotalExportedKb)); + metrics.dmaBufTotalExportedKb, + metrics.shmemKb)); return StatsManager.PULL_SUCCESS; } @@ -4490,6 +4491,9 @@ public class StatsPullAtomService extends SystemService { } int pullMediaCapabilitiesStats(int atomTag, List<StatsEvent> pulledData) { + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { + return StatsManager.PULL_SKIP; + } AudioManager audioManager = mContext.getSystemService(AudioManager.class); if (audioManager == null) { return StatsManager.PULL_SKIP; diff --git a/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java b/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java index 30b6e688bab6..9ebf59ef8363 100644 --- a/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java +++ b/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java @@ -82,6 +82,7 @@ final class SystemMemoryUtil { result.vmallocUsedKb = (int) mInfos[Debug.MEMINFO_VM_ALLOC_USED]; result.pageTablesKb = (int) mInfos[Debug.MEMINFO_PAGE_TABLES]; result.kernelStackKb = (int) mInfos[Debug.MEMINFO_KERNEL_STACK]; + result.shmemKb = (int) mInfos[Debug.MEMINFO_SHMEM]; result.totalIonKb = totalIonKb; result.gpuTotalUsageKb = gpuTotalUsageKb; result.gpuPrivateAllocationsKb = gpuPrivateAllocationsKb; @@ -95,6 +96,7 @@ final class SystemMemoryUtil { public int vmallocUsedKb; public int pageTablesKb; public int kernelStackKb; + public int shmemKb; public int totalIonKb; public int gpuTotalUsageKb; public int gpuPrivateAllocationsKb; diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 94f483c65eb5..2049f3d3be24 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -2031,11 +2031,14 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void updateMediaTapToTransferReceiverDisplay( @StatusBarManager.MediaTransferReceiverState int displayState, - MediaRoute2Info routeInfo) { + MediaRoute2Info routeInfo, + @Nullable Icon appIcon, + @Nullable CharSequence appName) { enforceMediaContentControl(); if (mBar != null) { try { - mBar.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo); + mBar.updateMediaTapToTransferReceiverDisplay( + displayState, routeInfo, appIcon, appName); } catch (RemoteException e) { Slog.e(TAG, "updateMediaTapToTransferReceiverDisplay", e); } diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java index 27c0beee9c27..10e868d06766 100644 --- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java +++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java @@ -15,6 +15,13 @@ */ package com.android.server.tracing; +import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT; +import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BEGIN; +import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BIND_PERM_INCORRECT; +import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_COMM_ERROR; +import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_HANDOFF; +import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_PERM_MISSING; + import android.Manifest; import android.annotation.NonNull; import android.content.ComponentName; @@ -39,6 +46,7 @@ import android.util.LruCache; import android.util.Slog; import com.android.internal.infra.ServiceConnector; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.SystemService; import java.io.IOException; @@ -71,6 +79,17 @@ public class TracingServiceProxy extends SystemService { private static final String INTENT_ACTION_NOTIFY_SESSION_STOLEN = "com.android.traceur.NOTIFY_SESSION_STOLEN"; + private static final int REPORT_BEGIN = + TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BEGIN; + private static final int REPORT_SVC_HANDOFF = + TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_HANDOFF; + private static final int REPORT_BIND_PERM_INCORRECT = + TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BIND_PERM_INCORRECT; + private static final int REPORT_SVC_PERM_MISSING = + TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_PERM_MISSING; + private static final int REPORT_SVC_COMM_ERROR = + TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_COMM_ERROR; + private final Context mContext; private final PackageManager mPackageManager; private final LruCache<ComponentName, ServiceConnector<IMessenger>> mCachedReporterServices; @@ -134,17 +153,24 @@ public class TracingServiceProxy extends SystemService { } private void reportTrace(@NonNull TraceReportParams params) { + FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_BEGIN, + params.uuidLsb, params.uuidMsb); + // We don't need to do any permission checks on the caller because access // to this service is guarded by SELinux. ComponentName component = new ComponentName(params.reporterPackageName, params.reporterClassName); if (!hasBindServicePermission(component)) { + FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_BIND_PERM_INCORRECT, + params.uuidLsb, params.uuidMsb); return; } boolean hasDumpPermission = hasPermission(component, Manifest.permission.DUMP); boolean hasUsageStatsPermission = hasPermission(component, Manifest.permission.PACKAGE_USAGE_STATS); if (!hasDumpPermission || !hasUsageStatsPermission) { + FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_SVC_PERM_MISSING, + params.uuidLsb, params.uuidMsb); return; } final long ident = Binder.clearCallingIdentity(); @@ -178,8 +204,13 @@ public class TracingServiceProxy extends SystemService { message.what = TraceReportService.MSG_REPORT_TRACE; message.obj = params; messenger.send(message); + + FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_SVC_HANDOFF, + params.uuidLsb, params.uuidMsb); }).whenComplete((res, err) -> { if (err != null) { + FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_SVC_COMM_ERROR, + params.uuidLsb, params.uuidMsb); Slog.e(TAG, "Failed to report trace", err); } try { diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 6aafd4aec0ab..b4c54f9beeb7 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -69,6 +69,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.Xml; +import android.view.Display; import android.view.IWindowManager; import android.view.WindowManagerGlobal; @@ -76,7 +77,9 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.util.DumpUtils; import com.android.internal.widget.LockPatternUtils; +import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -143,6 +146,7 @@ public class TrustManagerService extends SystemService { private final LockPatternUtils mLockPatternUtils; private final UserManager mUserManager; private final ActivityManager mActivityManager; + private VirtualDeviceManagerInternal mVirtualDeviceManager; @GuardedBy("mUserIsTrusted") private final SparseBooleanArray mUserIsTrusted = new SparseBooleanArray(); @@ -1293,9 +1297,57 @@ public class TrustManagerService extends SystemService { mHandler.obtainMessage(MSG_UNREGISTER_LISTENER, trustListener).sendToTarget(); } + /** + * @param uid: uid of the calling app (obtained via getCallingUid()) + * @param displayId: the id of a Display + * @return Returns true if both of the following conditions hold - + * 1) the uid belongs to an app instead of a system core component; and + * 2) either the uid is running on a virtual device or the displayId + * is owned by a virtual device + */ + private boolean isAppOrDisplayOnAnyVirtualDevice(int uid, int displayId) { + if (UserHandle.isCore(uid)) { + return false; + } + + if (mVirtualDeviceManager == null) { + mVirtualDeviceManager = LocalServices.getService( + VirtualDeviceManagerInternal.class); + if (mVirtualDeviceManager == null) { + // VirtualDeviceManager service may not have been published + return false; + } + } + + switch (displayId) { + case Display.INVALID_DISPLAY: + // There is no Display object associated with the Context of the calling app. + if (mVirtualDeviceManager.isAppRunningOnAnyVirtualDevice(uid)) { + return true; + } + break; + case Display.DEFAULT_DISPLAY: + // The DEFAULT_DISPLAY is by definition not virtual. + break; + default: + // Other display IDs can belong to logical displays created for other purposes. + if (mVirtualDeviceManager.isDisplayOwnedByAnyVirtualDevice(displayId)) { + return true; + } + break; + } + return false; + } + @Override - public boolean isDeviceLocked(int userId) throws RemoteException { - userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId, + public boolean isDeviceLocked(int userId, int displayId) throws RemoteException { + int uid = getCallingUid(); + if (isAppOrDisplayOnAnyVirtualDevice(uid, displayId)) { + // Virtual displays are considered insecure because they may be used for streaming + // to other devices. + return false; + } + userId = ActivityManager.handleIncomingUser(getCallingPid(), uid, userId, false /* allowAll */, true /* requireFull */, "isDeviceLocked", null); final long token = Binder.clearCallingIdentity(); @@ -1310,8 +1362,15 @@ public class TrustManagerService extends SystemService { } @Override - public boolean isDeviceSecure(int userId) throws RemoteException { - userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId, + public boolean isDeviceSecure(int userId, int displayId) throws RemoteException { + int uid = getCallingUid(); + if (isAppOrDisplayOnAnyVirtualDevice(uid, displayId)) { + // Virtual displays are considered insecure because they may be used for streaming + // to other devices. + return false; + } + + userId = ActivityManager.handleIncomingUser(getCallingPid(), uid, userId, false /* allowAll */, true /* requireFull */, "isDeviceSecure", null); final long token = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index f15d2bb05c92..e02fabd7366b 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -2543,6 +2543,7 @@ public final class TvInputManagerService extends SystemService { @Override public int getClientPriority(int useCase, String sessionId) { + ensureTunerResourceAccessPermission(); final int callingPid = Binder.getCallingPid(); final long identity = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 56985af9ef9f..87c8a79f874a 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -429,7 +429,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (needsExtraction) { extractColors(wallpaper); } - notifyColorListeners(wallpaper.primaryColors, which, wallpaper.userId, displayId); + notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper), which, + wallpaper.userId, displayId); } private static <T extends IInterface> boolean emptyCallbackList(RemoteCallbackList<T> list) { @@ -1519,7 +1520,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (mWallpaper.mWallpaperDimAmount != 0f) { try { connector.mEngine.applyDimming(mWallpaper.mWallpaperDimAmount); - notifyWallpaperColorsChanged(mWallpaper, FLAG_SYSTEM); } catch (RemoteException e) { Slog.w(TAG, "Failed to dim wallpaper", e); } @@ -2711,8 +2711,31 @@ public class WallpaperManagerService extends IWallpaperManager.Stub extractColors(wallpaperData); } + return getAdjustedWallpaperColorsOnDimming(wallpaperData); + } + + /** + * Gets the adjusted {@link WallpaperColors} if the wallpaper colors were not extracted from + * bitmap (i.e. it's a live wallpaper) and the dim amount is not 0. If these conditions apply, + * default to using color hints that do not support dark theme and dark text. + * + * @param wallpaperData WallpaperData containing the WallpaperColors and mWallpaperDimAmount + */ + WallpaperColors getAdjustedWallpaperColorsOnDimming(WallpaperData wallpaperData) { synchronized (mLock) { - return wallpaperData.primaryColors; + WallpaperColors wallpaperColors = wallpaperData.primaryColors; + + if (wallpaperColors != null + && (wallpaperColors.getColorHints() & WallpaperColors.HINT_FROM_BITMAP) == 0 + && wallpaperData.mWallpaperDimAmount != 0f) { + int adjustedColorHints = wallpaperColors.getColorHints() + & ~WallpaperColors.HINT_SUPPORTS_DARK_TEXT + & ~WallpaperColors.HINT_SUPPORTS_DARK_THEME; + return new WallpaperColors( + wallpaperColors.getPrimaryColor(), wallpaperColors.getSecondaryColor(), + wallpaperColors.getTertiaryColor(), adjustedColorHints); + } + return wallpaperColors; } } @@ -2782,6 +2805,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper.fromForegroundApp = fromForegroundApp; wallpaper.cropHint.set(cropHint); wallpaper.allowBackup = allowBackup; + wallpaper.mWallpaperDimAmount = getWallpaperDimAmount(); } return pfd; } finally { diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index ec4c58f9aa35..d526845f749a 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -1088,7 +1088,7 @@ class ActivityClientController extends IActivityClientController.Stub { final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); if (r != null && r.isState(RESUMED, PAUSING)) { r.mDisplayContent.mAppTransition.overridePendingAppTransition( - packageName, enterAnim, exitAnim, null, null, + packageName, enterAnim, exitAnim, backgroundColor, null, null, r.mOverrideTaskTransition); r.mTransitionController.setOverrideAnimation( TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName, diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 9532a5c79d5b..6ef6c7ae074a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -811,6 +811,27 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // How long we wait until giving up transfer splash screen. private static final int TRANSFER_SPLASH_SCREEN_TIMEOUT = 2000; + /** + * The icon is shown when the launching activity sets the splashScreenStyle to + * SPLASH_SCREEN_STYLE_ICON. If the launching activity does not specify any style, + * follow the system behavior. + * + * @see android.R.attr#windowSplashScreenBehavior + */ + private static final int SPLASH_SCREEN_BEHAVIOR_DEFAULT = 0; + /** + * The icon is shown unless the launching app specified SPLASH_SCREEN_STYLE_EMPTY. + * + * @see android.R.attr#windowSplashScreenBehavior + */ + private static final int SPLASH_SCREEN_BEHAVIOR_ICON_PREFERRED = 1; + + @IntDef(prefix = {"SPLASH_SCREEN_BEHAVIOR_"}, value = { + SPLASH_SCREEN_BEHAVIOR_DEFAULT, + SPLASH_SCREEN_BEHAVIOR_ICON_PREFERRED + }) + @interface SplashScreenBehavior { } + // TODO: Have a WindowContainer state for tracking exiting/deferred removal. boolean mIsExiting; // Force an app transition to be ran in the case the visibility of the app did not change. @@ -2483,10 +2504,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void removeStartingWindow() { + boolean prevEligibleForLetterboxEducation = isEligibleForLetterboxEducation(); + if (transferSplashScreenIfNeeded()) { return; } removeStartingWindowAnimation(true /* prepareAnimation */); + + // TODO(b/215316431): Add tests + final Task task = getTask(); + if (prevEligibleForLetterboxEducation != isEligibleForLetterboxEducation() + && task != null) { + // Trigger TaskInfoChanged to update the letterbox education. + task.dispatchTaskInfoChangedIfNeeded(true /* force */); + } } void removeStartingWindowAnimation(boolean prepareAnimation) { @@ -4485,6 +4516,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pendingOptions.getPackageName(), pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(), + pendingOptions.getCustomBackgroundColor(), pendingOptions.getAnimationStartedListener(), pendingOptions.getAnimationFinishedListener(), pendingOptions.getOverrideTaskTransition()); @@ -6594,8 +6626,23 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return null; } + private boolean isIconStylePreferred(int theme) { + if (theme == 0) { + return false; + } + final AttributeCache.Entry ent = AttributeCache.instance().get(packageName, theme, + R.styleable.Window, mWmService.mCurrentUserId); + if (ent != null) { + if (ent.array.hasValue(R.styleable.Window_windowSplashScreenBehavior)) { + return ent.array.getInt(R.styleable.Window_windowSplashScreenBehavior, + SPLASH_SCREEN_BEHAVIOR_DEFAULT) + == SPLASH_SCREEN_BEHAVIOR_ICON_PREFERRED; + } + } + return false; + } private boolean shouldUseEmptySplashScreen(ActivityRecord sourceRecord, boolean startActivity, - ActivityOptions options) { + ActivityOptions options, int resolvedTheme) { if (sourceRecord == null && !startActivity) { // Use empty style if this activity is not top activity. This could happen when adding // a splash screen window to the warm start activity which is re-create because top is @@ -6605,11 +6652,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return true; } } + + // setSplashScreenStyle decide in priority of windowSplashScreenBehavior. if (options != null) { final int optionsStyle = options.getSplashScreenStyle(); if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_EMPTY) { return true; - } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON) { + } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON + || isIconStylePreferred(resolvedTheme)) { return false; } // Choose the default behavior for Launcher and SystemUI when the SplashScreen style is @@ -6619,6 +6669,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) { return true; } + } else if (isIconStylePreferred(resolvedTheme)) { + return false; } if (sourceRecord == null) { sourceRecord = searchCandidateLaunchingActivity(); @@ -6691,13 +6743,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } - mSplashScreenStyleEmpty = shouldUseEmptySplashScreen( - sourceRecord, startActivity, startOptions); - final int splashScreenTheme = startActivity ? getSplashscreenTheme(startOptions) : 0; final int resolvedTheme = evaluateStartingWindowTheme(prev, packageName, theme, splashScreenTheme); + mSplashScreenStyleEmpty = shouldUseEmptySplashScreen(sourceRecord, startActivity, + startOptions, resolvedTheme); + final boolean activityCreated = mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal(); // If this activity is just created and all activities below are finish, treat this @@ -7660,6 +7712,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * <li>The activity is eligible for fixed orientation letterbox. * <li>The activity is in fullscreen. * <li>The activity is portrait-only. + * <li>The activity doesn't have a starting window (education should only be displayed + * once the starting window is removed in {@link #removeStartingWindow}). * </ul> */ // TODO(b/215316431): Add tests @@ -7667,7 +7721,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mWmService.mLetterboxConfiguration.getIsEducationEnabled() && mIsEligibleForFixedOrientationLetterbox && getWindowingMode() == WINDOWING_MODE_FULLSCREEN - && getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT; + && getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT + && mStartingWindow == null; } /** diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 5164bf053148..fb3d17ab3daa 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2003,8 +2003,8 @@ class ActivityStarter { * @param targetTask the target task for launching activity, which could be different from * the one who hosting the embedding. */ - private boolean canEmbedActivity(@NonNull TaskFragment taskFragment, ActivityRecord starting, - boolean newTask, Task targetTask) { + private boolean canEmbedActivity(@NonNull TaskFragment taskFragment, + @NonNull ActivityRecord starting, boolean newTask, Task targetTask) { final Task hostTask = taskFragment.getTask(); if (hostTask == null) { return false; @@ -2016,8 +2016,7 @@ class ActivityStarter { return true; } - // Not allowed embedding an activity of another app. - if (hostUid != starting.getUid()) { + if (!taskFragment.isAllowedToEmbedActivity(starting)) { return false; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 580ab1798f53..9f8d0978ceea 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1695,6 +1695,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public String getVoiceInteractorPackageName(IBinder callingVoiceInteractor) { + return LocalServices.getService(VoiceInteractionManagerInternal.class) + .getVoiceInteractorPackageName(callingVoiceInteractor); + } + + @Override public int startAssistantActivity(String callingPackage, @NonNull String callingFeatureId, int callingPid, int callingUid, Intent intent, String resolvedType, Bundle bOptions, int userId) { diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java index 5899a4e89ca7..a7430912c0b9 100644 --- a/services/core/java/com/android/server/wm/AnimationAdapter.java +++ b/services/core/java/com/android/server/wm/AnimationAdapter.java @@ -42,6 +42,22 @@ interface AnimationAdapter { boolean getShowWallpaper(); /** + * @return Whether we should show a background behind the animating windows. + * @see Animation#getShowBackground() + */ + default boolean getShowBackground() { + return false; + } + + /** + * @return The background color to use during an animation if getShowBackground returns true. + * @see Animation#getBackgroundColor() + */ + default int getBackgroundColor() { + return 0; + } + + /** * Requests to start the animation. * * @param animationLeash The surface to run the animation on. See {@link SurfaceAnimator} for an diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 56adcfd76403..05efb29cc775 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -93,6 +93,7 @@ import static com.android.server.wm.WindowManagerInternal.KeyguardExitAnimationS import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE; +import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -198,6 +199,7 @@ public class AppTransition implements Dump { private IRemoteCallback mAnimationFinishedCallback; private int mNextAppTransitionEnter; private int mNextAppTransitionExit; + private @ColorInt int mNextAppTransitionBackgroundColor; private int mNextAppTransitionInPlace; private boolean mNextAppTransitionIsSync; @@ -829,6 +831,7 @@ public class AppTransition implements Dump { break; } a = animAttr != 0 ? loadAnimationAttr(lp, animAttr, transit) : null; + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b " + "Callers=%s", @@ -836,6 +839,11 @@ public class AppTransition implements Dump { Debug.getCallers(3)); } setAppTransitionFinishedCallbackIfNeeded(a); + + if (mNextAppTransitionBackgroundColor != 0) { + a.setBackgroundColor(mNextAppTransitionBackgroundColor); + } + return a; } @@ -861,14 +869,15 @@ public class AppTransition implements Dump { } void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim, - IRemoteCallback startedCallback, IRemoteCallback endedCallback, - boolean overrideTaskTransaction) { + @ColorInt int backgroundColor, IRemoteCallback startedCallback, + IRemoteCallback endedCallback, boolean overrideTaskTransaction) { if (canOverridePendingAppTransition()) { clear(); mNextAppTransitionOverrideRequested = true; mNextAppTransitionPackage = packageName; mNextAppTransitionEnter = enterAnim; mNextAppTransitionExit = exitAnim; + mNextAppTransitionBackgroundColor = backgroundColor; postAnimationCallback(); mNextAppTransitionCallback = startedCallback; mAnimationFinishedCallback = endedCallback; diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 5a2cf17ffd18..afa4f190c6e3 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -544,6 +544,11 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return getActivityType() == ACTIVITY_TYPE_RECENTS; } + final boolean isActivityTypeHomeOrRecents() { + final int activityType = getActivityType(); + return activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS; + } + public boolean isActivityTypeAssistant() { return getActivityType() == ACTIVITY_TYPE_ASSISTANT; } diff --git a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java index f61cc94b6181..a83a033985c5 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java +++ b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java @@ -65,7 +65,16 @@ public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerControl @Override public void binderDied() { synchronized (mGlobalLock) { - mOrganizersByFeatureIds.remove(mFeature).destroy(); + IDisplayAreaOrganizer featureOrganizer = getOrganizerByFeature(mFeature); + if (featureOrganizer != null) { + IBinder organizerBinder = featureOrganizer.asBinder(); + if (!organizerBinder.equals(mOrganizer.asBinder()) && + organizerBinder.isBinderAlive()) { + Slog.d(TAG, "Dead organizer replaced for feature=" + mFeature); + return; + } + mOrganizersByFeatureIds.remove(mFeature).destroy(); + } } } } @@ -172,7 +181,7 @@ public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerControl organizer.asBinder(), uid); mOrganizersByFeatureIds.entrySet().removeIf((entry) -> { final boolean matches = entry.getValue().mOrganizer.asBinder() - == organizer.asBinder(); + .equals(organizer.asBinder()); if (matches) { entry.getValue().destroy(); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 092cff384a80..e845034adc3d 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1054,6 +1054,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier); mAppTransition.registerListenerLocked(mFixedRotationTransitionListener); mAppTransitionController = new AppTransitionController(mWmService, this); + mTransitionController.registerLegacyListener(mFixedRotationTransitionListener); mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this); final InputChannel inputChannel = mWmService.mInputManager.monitorInput( @@ -1111,6 +1112,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp t.remove(mSurfaceControl); mLastSurfacePosition.set(0, 0); + mLastDeltaRotation = Surface.ROTATION_0; configureSurfaces(t); @@ -1604,7 +1606,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ @Rotation int rotationForActivityInDifferentOrientation(@NonNull ActivityRecord r) { - if (mTransitionController.isShellTransitionsEnabled()) { + if (mTransitionController.useShellTransitionsRotation()) { return ROTATION_UNDEFINED; } if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM) { @@ -1645,18 +1647,30 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // It has been set and not yet finished. return true; } - if (!r.occludesParent() || r.isVisible()) { + if (!r.occludesParent()) { // While entering or leaving a translucent or floating activity (e.g. dialog style), // there is a visible activity in the background. Then it still needs rotation animation // to cover the activity configuration change. return false; } + if (mTransitionController.isShellTransitionsEnabled() + ? mTransitionController.wasVisibleAtStart(r) : r.isVisible()) { + // If activity is already visible, then it's not "launching". However, shell-transitions + // will make it visible immediately. + return false; + } if (checkOpening) { - if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) { - // Apply normal rotation animation in case of the activity set different requested - // orientation without activity switch, or the transition is unset due to starting - // window was transferred ({@link #mSkipAppTransitionAnimation}). - return false; + if (mTransitionController.isShellTransitionsEnabled()) { + if (!mTransitionController.isCollecting(r)) { + return false; + } + } else { + if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) { + // Apply normal rotation animation in case of the activity set different + // requested orientation without activity switch, or the transition is unset due + // to starting window was transferred ({@link #mSkipAppTransitionAnimation}). + return false; + } } if (r.isState(RESUMED) && !r.getRootTask().mInResumeTopActivity) { // If the activity is executing or has done the lifecycle callback, use normal @@ -1733,15 +1747,19 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) { + final boolean useAsyncRotation = !mTransitionController.isShellTransitionsEnabled(); if (mFixedRotationLaunchingApp == null && r != null) { - mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation); - startAsyncRotation( - // Delay the hide animation to avoid blinking by clicking navigation bar that - // may toggle fixed rotation in a short time. - r == mFixedRotationTransitionListener.mAnimatingRecents /* shouldDebounce */); + mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, + rotation); + if (useAsyncRotation) { + startAsyncRotation( + // Delay the hide animation to avoid blinking by clicking navigation bar + // that may toggle fixed rotation in a short time. + r == mFixedRotationTransitionListener.mAnimatingRecents); + } } else if (mFixedRotationLaunchingApp != null && r == null) { mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this); - finishAsyncRotationIfPossible(); + if (useAsyncRotation) finishAsyncRotationIfPossible(); } mFixedRotationLaunchingApp = r; } @@ -1760,7 +1778,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (prevRotatedLaunchingApp != null && prevRotatedLaunchingApp.getWindowConfiguration().getRotation() == rotation // It is animating so we can expect there will have a transition callback. - && prevRotatedLaunchingApp.isAnimating(TRANSITION | PARENTS)) { + && (prevRotatedLaunchingApp.isAnimating(TRANSITION | PARENTS) + || mTransitionController.inTransition(prevRotatedLaunchingApp))) { // It may be the case that multiple activities launch consecutively. Because their // rotation are the same, the transformed state can be shared to avoid duplicating // the heavy operations. This also benefits that the states of multiple activities @@ -1798,6 +1817,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } // Update directly because the app which will change the orientation of display is ready. if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) { + mTransitionController.setSeamlessRotation(this); sendNewConfiguration(); return; } @@ -3107,12 +3127,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mDisplayPolicy.switchUser(); } - @Override - void removeIfPossible() { - if (isAnimating(TRANSITION | PARENTS) + private boolean shouldDeferRemoval() { + return isAnimating(TRANSITION | PARENTS) // isAnimating is a legacy transition query and will be removed, so also add a // check for whether this is in a shell-transition when not using legacy. - || mTransitionController.inTransition()) { + || mTransitionController.isTransitionOnDisplay(this); + } + + @Override + void removeIfPossible() { + if (shouldDeferRemoval()) { mDeferredRemoval = true; return; } @@ -3129,6 +3153,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mChangingContainers.clear(); mUnknownAppVisibilityController.clear(); mAppTransition.removeAppTransitionTimeoutCallbacks(); + mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener); handleAnimatingStoppedAndTransition(); mWmService.stopFreezingDisplayLocked(); super.removeImmediately(); @@ -3140,6 +3165,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInputMonitor.onDisplayRemoved(); mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this); mWmService.mAccessibilityController.onDisplayRemoved(mDisplayId); + mRootWindowContainer.mTaskSupervisor + .getKeyguardController().onDisplayRemoved(mDisplayId); } finally { mDisplayReady = false; } @@ -3153,7 +3180,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Returns true if a removal action is still being deferred. */ @Override boolean handleCompleteDeferredRemoval() { - final boolean stillDeferringRemoval = super.handleCompleteDeferredRemoval(); + final boolean stillDeferringRemoval = + super.handleCompleteDeferredRemoval() || shouldDeferRemoval(); if (!stillDeferringRemoval && mDeferredRemoval) { removeImmediately(); @@ -4062,7 +4090,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final boolean renewImeSurface = mImeScreenshot == null || mImeScreenshot.getWidth() != mInputMethodWindow.getFrame().width() || mImeScreenshot.getHeight() != mInputMethodWindow.getFrame().height(); - if (task != null && !task.isHomeOrRecentsRootTask()) { + if (task != null && !task.isActivityTypeHomeOrRecents()) { SurfaceControl.ScreenshotHardwareBuffer imeBuffer = renewImeSurface ? mWmService.mTaskSnapshotController.snapshotImeFromAttachedTask(task) : null; @@ -5761,7 +5789,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mCurrentOverrideConfigurationChanges = currOverrideConfig.diff(overrideConfiguration); super.onRequestedOverrideConfigurationChanged(overrideConfiguration); mCurrentOverrideConfigurationChanges = 0; - mWmService.setNewDisplayOverrideConfiguration(currOverrideConfig, this); + if (mWaitingForConfig) { + mWaitingForConfig = false; + mWmService.mLastFinishedFreezeSource = "new-config"; + } mAtmService.addWindowLayoutReasons( ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED); } @@ -5891,8 +5922,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp forAllRootTasks(t -> {t.removeIfPossible("releaseSelfIfNeeded");}); } else if (getTopRootTask() == null) { removeIfPossible(); - mRootWindowContainer.mTaskSupervisor - .getKeyguardController().onDisplayRemoved(mDisplayId); } } diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 2a05d05d4786..6438d79e5761 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.util.RotationUtils.deltaRotation; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; @@ -39,7 +40,6 @@ import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_D import android.annotation.AnimRes; import android.annotation.IntDef; -import android.annotation.UserIdInt; import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; @@ -50,7 +50,6 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.power.Boost; -import android.net.Uri; import android.os.Handler; import android.os.RemoteException; import android.os.SystemProperties; @@ -58,6 +57,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; import android.util.SparseArray; +import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.view.IDisplayWindowRotationCallback; import android.view.IWindowManager; @@ -77,6 +77,7 @@ import com.android.server.statusbar.StatusBarManagerInternal; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayDeque; /** * Defines the mapping between orientation and rotation of a display. @@ -106,6 +107,7 @@ public class DisplayRotation { private final int mDeskDockRotation; private final int mUndockedHdmiRotation; private final RotationAnimationPair mTmpRotationAnim = new RotationAnimationPair(); + private final RotationHistory mRotationHistory = new RotationHistory(); private OrientationListener mOrientationListener; private StatusBarManagerInternal mStatusBarManagerInternal; @@ -139,6 +141,8 @@ public class DisplayRotation { @VisibleForTesting int mUpsideDownRotation; // "other" portrait + int mLastSensorRotation = -1; + private boolean mAllowSeamlessRotationDespiteNavBarMoving; private int mDeferredRotationPauseCount; @@ -351,6 +355,7 @@ public class DisplayRotation { } void applyCurrentRotation(@Surface.Rotation int rotation) { + mRotationHistory.addRecord(this, rotation); if (mOrientationListener != null) { mOrientationListener.setCurrentRotation(rotation); } @@ -1149,6 +1154,7 @@ public class DisplayRotation { int sensorRotation = mOrientationListener != null ? mOrientationListener.getProposedRotation() // may be -1 : -1; + mLastSensorRotation = sensorRotation; if (sensorRotation < 0) { sensorRotation = lastRotation; } @@ -1537,6 +1543,15 @@ public class DisplayRotation { pw.println(" mUndockedHdmiRotation=" + Surface.rotationToString(mUndockedHdmiRotation)); pw.println(prefix + " mLidOpenRotation=" + Surface.rotationToString(mLidOpenRotation)); pw.println(prefix + " mFixedToUserRotation=" + isFixedToUserRotation()); + + if (!mRotationHistory.mRecords.isEmpty()) { + pw.println(); + pw.println(prefix + " RotationHistory"); + prefix = " " + prefix; + for (RotationHistory.Record r : mRotationHistory.mRecords) { + r.dump(prefix, pw); + } + } } void dumpDebug(ProtoOutputStream proto, long fieldId) { @@ -1650,9 +1665,69 @@ public class DisplayRotation { } } - @VisibleForTesting - interface ContentObserverRegister { - void registerContentObserver(Uri uri, boolean notifyForDescendants, - ContentObserver observer, @UserIdInt int userHandle); + private static class RotationHistory { + private static final int MAX_SIZE = 8; + private static class Record { + final @Surface.Rotation int mFromRotation; + final @Surface.Rotation int mToRotation; + final @Surface.Rotation int mUserRotation; + final @WindowManagerPolicy.UserRotationMode int mUserRotationMode; + final int mSensorRotation; + final boolean mIgnoreOrientationRequest; + final String mNonDefaultRequestingTaskDisplayArea; + final String mLastOrientationSource; + final @ActivityInfo.ScreenOrientation int mSourceOrientation; + final long mTimestamp = System.currentTimeMillis(); + + Record(DisplayRotation dr, int fromRotation, int toRotation) { + mFromRotation = fromRotation; + mToRotation = toRotation; + mUserRotation = dr.mUserRotation; + mUserRotationMode = dr.mUserRotationMode; + final OrientationListener listener = dr.mOrientationListener; + mSensorRotation = (listener == null || !listener.mEnabled) + ? -2 /* disabled */ : dr.mLastSensorRotation; + final DisplayContent dc = dr.mDisplayContent; + mIgnoreOrientationRequest = dc.mIgnoreOrientationRequest; + final TaskDisplayArea requestingTda = dc.getOrientationRequestingTaskDisplayArea(); + mNonDefaultRequestingTaskDisplayArea = requestingTda == null + ? "none" : requestingTda != dc.getDefaultTaskDisplayArea() + ? requestingTda.toString() : null; + final WindowContainer<?> source = dc.getLastOrientationSource(); + if (source != null) { + mLastOrientationSource = source.toString(); + mSourceOrientation = source.mOrientation; + } else { + mLastOrientationSource = null; + mSourceOrientation = SCREEN_ORIENTATION_UNSET; + } + } + + void dump(String prefix, PrintWriter pw) { + pw.println(prefix + TimeUtils.logTimeOfDay(mTimestamp) + + " " + Surface.rotationToString(mFromRotation) + + " to " + Surface.rotationToString(mToRotation)); + pw.println(prefix + " source=" + mLastOrientationSource + + " " + ActivityInfo.screenOrientationToString(mSourceOrientation)); + pw.println(prefix + " mode=" + + WindowManagerPolicy.userRotationModeToString(mUserRotationMode) + + " user=" + Surface.rotationToString(mUserRotation) + + " sensor=" + Surface.rotationToString(mSensorRotation)); + if (mIgnoreOrientationRequest) pw.println(prefix + " ignoreRequest=true"); + if (mNonDefaultRequestingTaskDisplayArea != null) { + pw.println(prefix + " requestingTda=" + mNonDefaultRequestingTaskDisplayArea); + } + } + } + + final ArrayDeque<Record> mRecords = new ArrayDeque<>(MAX_SIZE); + + void addRecord(DisplayRotation dr, int toRotation) { + if (mRecords.size() >= MAX_SIZE) { + mRecords.removeFirst(); + } + final int fromRotation = dr.mDisplayContent.getWindowConfiguration().getRotation(); + mRecords.addLast(new Record(dr, fromRotation, toRotation)); + } } } diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java index 8db43066eae5..2ab08e6da478 100644 --- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java +++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java @@ -156,6 +156,7 @@ class EmbeddedWindowController { final IWindow mClient; @Nullable final WindowState mHostWindowState; @Nullable final ActivityRecord mHostActivityRecord; + final String mName; final int mOwnerUid; final int mOwnerPid; final WindowManagerService mWmService; @@ -186,7 +187,7 @@ class EmbeddedWindowController { */ EmbeddedWindow(Session session, WindowManagerService service, IWindow clientToken, WindowState hostWindowState, int ownerUid, int ownerPid, int windowType, - int displayId, IBinder focusGrantToken) { + int displayId, IBinder focusGrantToken, String inputHandleName) { mSession = session; mWmService = service; mClient = clientToken; @@ -198,14 +199,15 @@ class EmbeddedWindowController { mWindowType = windowType; mDisplayId = displayId; mFocusGrantToken = focusGrantToken; + final String hostWindowName = + (mHostWindowState != null) ? "-" + mHostWindowState.getWindowTag().toString() + : ""; + mName = "Embedded{" + inputHandleName + hostWindowName + "}"; } @Override public String toString() { - final String hostWindowName = (mHostWindowState != null) - ? mHostWindowState.getWindowTag().toString() : "Internal"; - return "EmbeddedWindow{ u" + UserHandle.getUserId(mOwnerUid) + " " + hostWindowName - + "}"; + return mName; } InputApplicationHandle getApplicationHandle() { diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index 8d3e07116693..aa5e20d9ec7f 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -104,7 +104,8 @@ class EnsureActivitiesVisibleHelper { for (int i = mTaskFragment.mChildren.size() - 1; i >= 0; --i) { final WindowContainer child = mTaskFragment.mChildren.get(i); final TaskFragment childTaskFragment = child.asTaskFragment(); - if (childTaskFragment != null && childTaskFragment.topRunningActivity() != null) { + if (childTaskFragment != null + && childTaskFragment.getTopNonFinishingActivity() != null) { childTaskFragment.updateActivityVisibilities(starting, configChanges, preserveWindows, notifyClients); mBehindFullyOccludedContainer |= diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 2f95119d67cb..d28dfd54c37f 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -144,15 +144,21 @@ class InsetsPolicy { getStatusControlTarget(focusedWin, false /* fake */); final InsetsControlTarget navControlTarget = getNavControlTarget(focusedWin, false /* fake */); + final WindowState notificationShade = mPolicy.getNotificationShade(); + final WindowState topApp = mPolicy.getTopFullscreenOpaqueWindow(); mStateController.onBarControlTargetChanged( statusControlTarget, statusControlTarget == mDummyControlTarget ? getStatusControlTarget(focusedWin, true /* fake */) - : null, + : statusControlTarget == notificationShade + ? getStatusControlTarget(topApp, true /* fake */) + : null, navControlTarget, navControlTarget == mDummyControlTarget ? getNavControlTarget(focusedWin, true /* fake */) - : null); + : navControlTarget == notificationShade + ? getNavControlTarget(topApp, true /* fake */) + : null); mStatusBar.updateVisibility(statusControlTarget, ITYPE_STATUS_BAR); mNavBar.updateVisibility(navControlTarget, ITYPE_NAVIGATION_BAR); } diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java index a3eb980992c7..61f9fe29b2f8 100644 --- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java @@ -51,6 +51,16 @@ class LocalAnimationAdapter implements AnimationAdapter { } @Override + public boolean getShowBackground() { + return mSpec.getShowBackground(); + } + + @Override + public int getBackgroundColor() { + return mSpec.getBackgroundColor(); + } + + @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) { mAnimator.startAnimation(mSpec, animationLeash, t, @@ -97,6 +107,20 @@ class LocalAnimationAdapter implements AnimationAdapter { } /** + * @see AnimationAdapter#getShowBackground + */ + default boolean getShowBackground() { + return false; + } + + /** + * @see AnimationAdapter#getBackgroundColor + */ + default int getBackgroundColor() { + return 0; + } + + /** * @see AnimationAdapter#getStatusBarTransitionsStartTime */ default long calculateStatusBarTransitionStartTime() { diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index d4a7a5d68929..e259238c2879 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -529,7 +529,7 @@ class RecentTasks { */ void notifyTaskPersisterLocked(Task task, boolean flush) { final Task rootTask = task != null ? task.getRootTask() : null; - if (rootTask != null && rootTask.isHomeOrRecentsRootTask()) { + if (rootTask != null && rootTask.isActivityTypeHomeOrRecents()) { // Never persist the home or recents task. return; } @@ -563,7 +563,7 @@ class RecentTasks { private static boolean shouldPersistTaskLocked(Task task) { final Task rootTask = task.getRootTask(); - return task.isPersistable && (rootTask == null || !rootTask.isHomeOrRecentsRootTask()); + return task.isPersistable && (rootTask == null || !rootTask.isActivityTypeHomeOrRecents()); } void onSystemReadyLocked() { @@ -994,7 +994,7 @@ class RecentTasks { } final Task rootTask = task.getRootTask(); if ((task.isPersistable || task.inRecents) - && (rootTask == null || !rootTask.isHomeOrRecentsRootTask())) { + && (rootTask == null || !rootTask.isActivityTypeHomeOrRecents())) { if (TaskPersister.DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task); persistentTaskIds.add(task.mTaskId); } else { diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 1183094c4fa3..30906e5b3db8 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -18,8 +18,7 @@ package com.android.server.wm; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.RemoteAnimationTarget.MODE_CLOSING; @@ -129,7 +128,6 @@ public class RecentsAnimationController implements DeathRecipient { private ActivityRecord mTargetActivityRecord; private DisplayContent mDisplayContent; private int mTargetActivityType; - private Rect mMinimizedHomeBounds = new Rect(); // We start the RecentsAnimationController in a pending-start state since we need to wait for // the wallpaper/activity to draw before we can give control to the handler to start animating @@ -480,11 +478,6 @@ public class RecentsAnimationController implements DeathRecipient { mDisplayContent.setLayoutNeeded(); } - // Save the minimized home height - final Task rootHomeTask = - mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask(); - mMinimizedHomeBounds = rootHomeTask != null ? rootHomeTask.getBounds() : null; - mService.mWindowPlacerLocked.performSurfacePlacement(); mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(targetActivity); @@ -502,9 +495,7 @@ public class RecentsAnimationController implements DeathRecipient { */ private boolean skipAnimation(Task task) { final WindowConfiguration config = task.getWindowConfiguration(); - return task.isAlwaysOnTop() - || config.tasksAreFloating() - || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; + return task.isAlwaysOnTop() || config.tasksAreFloating(); } @VisibleForTesting @@ -568,10 +559,6 @@ public class RecentsAnimationController implements DeathRecipient { // insets for the target app window after a rotation mDisplayContent.performLayout(false /* initial */, false /* updateInputWindows */); - final Rect minimizedHomeBounds = mTargetActivityRecord != null - && mTargetActivityRecord.inSplitScreenSecondaryWindowingMode() - ? mMinimizedHomeBounds - : null; final Rect contentInsets; final WindowState targetAppMainWindow = getTargetAppMainWindow(); if (targetAppMainWindow != null) { @@ -585,7 +572,7 @@ public class RecentsAnimationController implements DeathRecipient { contentInsets = mTmpRect; } mRunner.onAnimationStart(mController, appTargets, wallpaperTargets, contentInsets, - minimizedHomeBounds); + null); ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "startAnimation(): Notify animation start: %s", mPendingAnimations.stream() @@ -623,16 +610,17 @@ public class RecentsAnimationController implements DeathRecipient { for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { final TaskAnimationAdapter adapter = mPendingAnimations.get(i); final Task task = adapter.mTask; - final boolean isSplitScreenSecondary = - task.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; - if (task.isHomeOrRecentsRootTask() - // TODO(b/178449492): Will need to update for the new split screen mode once - // it's ready. - // Skip if the task is the secondary split screen and in landscape. - || (isSplitScreenSecondary && isDisplayLandscape)) { + final TaskFragment adjacentTask = task.getRootTask().getAdjacentTaskFragment(); + final boolean inSplitScreen = task.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW + && adjacentTask != null; + if (task.isActivityTypeHomeOrRecents() + // Skip if the task is in split screen and in landscape. + || (inSplitScreen && isDisplayLandscape) + // Skip if the task is the top task in split screen. + || (inSplitScreen && task.getBounds().top < adjacentTask.getBounds().top)) { continue; } - shouldTranslateNavBar = isSplitScreenSecondary; + shouldTranslateNavBar = inSplitScreen; mNavBarAttachedApp = task.getTopVisibleActivity(); break; } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 76a7981fa946..d5350183cdcb 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -637,36 +637,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return null; } - /** - * Set new display override config. If called for the default display, global configuration - * will also be updated. - */ - void setDisplayOverrideConfigurationIfNeeded(Configuration newConfiguration, - @NonNull DisplayContent displayContent) { - - final Configuration currentConfig = displayContent.getRequestedOverrideConfiguration(); - final boolean configChanged = currentConfig.diff(newConfiguration) != 0; - if (!configChanged) { - return; - } - - displayContent.onRequestedOverrideConfigurationChanged(newConfiguration); - - if (displayContent.getDisplayId() == DEFAULT_DISPLAY) { - // Override configuration of the default display duplicates global config. In this case - // we also want to update the global config. - setGlobalConfigurationIfNeeded(newConfiguration); - } - } - - private void setGlobalConfigurationIfNeeded(Configuration newConfiguration) { - final boolean configChanged = getConfiguration().diff(newConfiguration) != 0; - if (!configChanged) { - return; - } - onConfigurationChanged(newConfiguration); - } - @Override void dispatchConfigurationToChild(DisplayContent child, Configuration config) { if (child.isDefaultDisplay) { diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 7c5144e3892a..de8ea8c60522 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -837,7 +837,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { @Override public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type, - IBinder focusGrantToken, InputChannel outInputChannel) { + IBinder focusGrantToken, String inputHandleName, InputChannel outInputChannel) { if (hostInputToken == null && !mCanAddInternalSystemWindow) { // Callers without INTERNAL_SYSTEM_WINDOW permission cannot grant input channel to // embedded windows without providing a host window input token @@ -853,7 +853,8 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { try { mService.grantInputChannel(this, mUid, mPid, displayId, surface, window, hostInputToken, flags, mCanAddInternalSystemWindow ? privateFlags : 0, - mCanAddInternalSystemWindow ? type : 0, focusGrantToken, outInputChannel); + mCanAddInternalSystemWindow ? type : 0, focusGrantToken, inputHandleName, + outInputChannel); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index b84ef773f85a..6df54cd939cb 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2711,7 +2711,7 @@ class Task extends TaskFragment { // they extend past their root task and sysui uses the root task surface to control // cropping. // TODO(b/158242495): get rid of this when drag/drop can use surface bounds. - if (isActivityTypeHome() || isActivityTypeRecents()) { + if (isActivityTypeHomeOrRecents()) { // Make sure this is the top-most non-organizer root task (if not top-most, it means // another translucent task could be above this, so this needs to stay cropped. final Task rootTask = getRootTask(); @@ -3306,7 +3306,7 @@ class Task extends TaskFragment { if (control != null) { // We let the transition to be controlled by RecentsAnimation, and callback task's // RemoteAnimationTarget for remote runner to animate. - if (enter && !isHomeOrRecentsRootTask()) { + if (enter && !isActivityTypeHomeOrRecents()) { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "applyAnimationUnchecked, control: %s, task: %s, transit: %s", control, asTask(), AppTransition.appTransitionOldToString(transit)); @@ -4591,10 +4591,6 @@ class Task extends TaskFragment { !PRESERVE_WINDOWS); } - final boolean isHomeOrRecentsRootTask() { - return isActivityTypeHome() || isActivityTypeRecents(); - } - final boolean isOnHomeDisplay() { return getDisplayId() == DEFAULT_DISPLAY; } @@ -5044,7 +5040,7 @@ class Task extends TaskFragment { // The transition animation and starting window are not needed if {@code allowMoveToFront} // is false, because the activity won't be visible. - if ((!isHomeOrRecentsRootTask() || hasActivity()) && allowMoveToFront) { + if ((!isActivityTypeHomeOrRecents() || hasActivity()) && allowMoveToFront) { final DisplayContent dc = mDisplayContent; if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: starting " + r); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 177d2e61d5f0..c880aba03423 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -25,6 +25,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -95,6 +96,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; +import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -102,6 +104,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; @@ -505,6 +508,56 @@ class TaskFragment extends WindowContainer<WindowContainer> { } /** + * Checks if the organized task fragment is allowed to have the specified activity, which is + * allowed if an activity allows embedding in untrusted mode, or if the trusted mode can be + * enabled. + * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord) + */ + boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a) { + if ((a.info.flags & FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING) + == FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING) { + return true; + } + + return isAllowedToEmbedActivityInTrustedMode(a); + } + + /** + * Checks if the organized task fragment is allowed to embed activity in fully trusted mode, + * which means that all transactions are allowed. This is supported in the following cases: + * <li>the activity belongs to the same app as the organizer host;</li> + * <li>the activity has declared the organizer host as trusted explicitly via known + * certificate.</li> + */ + private boolean isAllowedToEmbedActivityInTrustedMode(@NonNull ActivityRecord a) { + if (mTaskFragmentOrganizerUid == a.getUid()) { + // Activities from the same UID can be embedded freely by the host. + return true; + } + + Set<String> knownActivityEmbeddingCerts = a.info.getKnownActivityEmbeddingCerts(); + if (knownActivityEmbeddingCerts.isEmpty()) { + // An application must either declare that it allows untrusted embedding, or specify + // a set of app certificates that are allowed to embed it in trusted mode. + return false; + } + + AndroidPackage hostPackage = mAtmService.getPackageManagerInternalLocked() + .getPackage(mTaskFragmentOrganizerUid); + + return hostPackage != null && hostPackage.getSigningDetails().hasAncestorOrSelfWithDigest( + knownActivityEmbeddingCerts); + } + + /** + * Checks if all activities in the task fragment are allowed to be embedded in trusted mode. + * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord) + */ + boolean isAllowedToBeEmbeddedInTrustedMode() { + return forAllActivities(this::isAllowedToEmbedActivityInTrustedMode); + } + + /** * Returns the TaskFragment that is being organized, which could be this or the ascendant * TaskFragment. */ @@ -945,6 +998,10 @@ class TaskFragment extends WindowContainer<WindowContainer> { // we still want to check if the visibility of other windows have changed (e.g. bringing // a fullscreen window forward to cover another freeform activity.) if (taskDisplayArea.inMultiWindowMode()) { + if (taskDisplayArea.mDisplayContent != null + && taskDisplayArea.mDisplayContent.mFocusedApp != next) { + taskDisplayArea.mDisplayContent.setFocusedApp(next); + } taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, false /* preserveWindows */, true /* notifyClients */); } diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 123ca889c73e..19f921deab4c 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -294,14 +294,28 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } - /** Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. */ + /** + * Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns + * {@code null} if it doesn't, or if the organizer has activity(ies) embedded in untrusted mode. + */ @Nullable public RemoteAnimationDefinition getRemoteAnimationDefinition( ITaskFragmentOrganizer organizer) { synchronized (mGlobalLock) { final TaskFragmentOrganizerState organizerState = mTaskFragmentOrganizerState.get(organizer.asBinder()); - return organizerState != null ? organizerState.mRemoteAnimationDefinition : null; + if (organizerState == null) { + return null; + } + for (TaskFragment tf : organizerState.mOrganizedTaskFragments) { + if (!tf.isAllowedToBeEmbeddedInTrustedMode()) { + // Disable client-driven animations for organizer if at least one of the + // embedded task fragments is not embedding in trusted mode. + // TODO(b/197364677): replace with a stub or Shell-driven one instead of skip? + return null; + } + } + return organizerState.mRemoteAnimationDefinition; } } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index d86382d1858f..9f2188b72730 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -158,8 +158,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe /** The final animation targets derived from participants after promotion. */ private ArrayList<WindowContainer> mTargets; - /** The main display running this transition. */ - private DisplayContent mTargetDisplay; + /** The displays that this transition is running on. */ + private final ArrayList<DisplayContent> mTargetDisplays = new ArrayList<>(); /** * Set of participating windowtokens (activity/wallpaper) which are visible at the end of @@ -209,6 +209,16 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return mTransientLaunches != null && mTransientLaunches.contains(activity); } + boolean isOnDisplay(@NonNull DisplayContent dc) { + return mTargetDisplays.contains(dc); + } + + void setSeamlessRotation(@NonNull WindowContainer wc) { + final ChangeInfo info = mChanges.get(wc); + if (info == null) return; + info.mFlags = info.mFlags | ChangeInfo.FLAG_SEAMLESS_ROTATION; + } + @VisibleForTesting int getSyncId() { return mSyncId; @@ -274,6 +284,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mChanges.put(wc, info); } mParticipants.add(wc); + if (wc.getDisplayContent() != null && !mTargetDisplays.contains(wc.getDisplayContent())) { + mTargetDisplays.add(wc.getDisplayContent()); + } if (info.mShowWallpaper) { // Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set. final WindowState wallpaper = @@ -510,15 +523,23 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */); } - final AsyncRotationController asyncRotationController = - mTargetDisplay.getAsyncRotationController(); - if (asyncRotationController != null) { - asyncRotationController.onTransitionFinished(); - } - // Transient-launch activities cannot be IME target (WindowState#canBeImeTarget), - // so re-compute in case the IME target is changed after transition. - if (mTransientLaunches != null) { - mTargetDisplay.computeImeTarget(true /* updateImeTarget */); + for (int i = 0; i < mTargetDisplays.size(); ++i) { + final DisplayContent dc = mTargetDisplays.get(i); + final AsyncRotationController asyncRotationController = dc.getAsyncRotationController(); + if (asyncRotationController != null) { + asyncRotationController.onTransitionFinished(); + } + if (mTransientLaunches != null) { + // Transient-launch activities cannot be IME target (WindowState#canBeImeTarget), + // so re-compute in case the IME target is changed after transition. + for (int t = 0; t < mTransientLaunches.size(); ++t) { + if (mTransientLaunches.valueAt(t).getDisplayContent() == dc) { + dc.computeImeTarget(true /* updateImeTarget */); + break; + } + } + } + dc.handleCompleteDeferredRemoval(); } } @@ -549,19 +570,13 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId); return; } - boolean hasWallpaper = false; - DisplayContent dc = null; - for (int i = mParticipants.size() - 1; i >= 0; --i) { - final WindowContainer<?> wc = mParticipants.valueAt(i); - if (dc == null && wc.mDisplayContent != null) { - dc = wc.mDisplayContent; - } - if (!hasWallpaper && isWallpaper(wc)) { - hasWallpaper = true; - } + if (mTargetDisplays.isEmpty()) { + mTargetDisplays.add(mController.mAtm.mRootWindowContainer.getDefaultDisplay()); } - if (dc == null) dc = mController.mAtm.mRootWindowContainer.getDefaultDisplay(); - mTargetDisplay = dc; + // While there can be multiple DC's involved. For now, we just use the first one as + // the "primary" one for most things. Eventually, this will need to change, but, for the + // time being, we don't have full cross-display transitions so it isn't a problem. + final DisplayContent dc = mTargetDisplays.get(0); if (mState == STATE_ABORT) { mController.abort(this); @@ -571,8 +586,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return; } // Ensure that wallpaper visibility is updated with the latest wallpaper target. - if (hasWallpaper) { - dc.mWallpaperController.adjustWallpaperWindows(); + for (int i = mParticipants.size() - 1; i >= 0; --i) { + final WindowContainer<?> wc = mParticipants.valueAt(i); + if (isWallpaper(wc) && wc.getDisplayContent() != null) { + wc.getDisplayContent().mWallpaperController.adjustWallpaperWindows(); + } } mState = STATE_PLAYING; @@ -819,7 +837,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // the bottom of the screen, so we need to animate it. for (int i = 0; i < mTargets.size(); ++i) { final Task task = mTargets.get(i).asTask(); - if (task == null || !task.isHomeOrRecentsRootTask()) continue; + if (task == null || !task.isActivityTypeHomeOrRecents()) continue; animate = task.isVisibleRequested(); break; } @@ -1122,6 +1140,15 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // hardware-screen-level surfaces. return asDC.getWindowingLayer(); } + if (!wc.mTransitionController.useShellTransitionsRotation()) { + final WindowToken asToken = wc.asWindowToken(); + if (asToken != null) { + // WindowTokens can have a fixed-rotation applied to them. In the current + // implementation this fact is hidden from the player, so we must create a leash. + final SurfaceControl leash = asToken.getOrCreateFixedRotationLeash(); + if (leash != null) return leash; + } + } return wc.getSurfaceControl(); } @@ -1224,6 +1251,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe final ActivityRecord topMostActivity = task.getTopMostActivity(); change.setAllowEnterPip(topMostActivity != null && topMostActivity.checkEnterPictureInPictureAppOpsState()); + } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) { + change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS); } final ActivityRecord activityRecord = target.asActivityRecord(); if (activityRecord != null) { @@ -1337,6 +1366,21 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe @VisibleForTesting static class ChangeInfo { + private static final int FLAG_NONE = 0; + + /** + * When set, the associated WindowContainer has been explicitly requested to be a + * seamless rotation. This is currently only used by DisplayContent during fixed-rotation. + */ + private static final int FLAG_SEAMLESS_ROTATION = 1; + + @IntDef(prefix = { "FLAG_" }, value = { + FLAG_NONE, + FLAG_SEAMLESS_ROTATION + }) + @Retention(RetentionPolicy.SOURCE) + @interface Flag {} + // Usually "post" change state. WindowContainer mParent; @@ -1350,6 +1394,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe int mRotation = ROTATION_UNDEFINED; @ActivityInfo.Config int mKnownConfigChanges; + /** These are just extra info. They aren't used for change-detection. */ + @Flag int mFlags = FLAG_NONE; + ChangeInfo(@NonNull WindowContainer origState) { mVisible = origState.isVisibleRequested(); mWindowingMode = origState.getWindowingMode(); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 60307ce22000..c267cbacdf24 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -34,6 +34,7 @@ import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.SystemClock; +import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -58,6 +59,10 @@ import java.util.function.LongConsumer; class TransitionController { private static final String TAG = "TransitionController"; + /** Whether to use shell-transitions rotation instead of fixed-rotation. */ + private static final boolean SHELL_TRANSITIONS_ROTATION = + SystemProperties.getBoolean("persist.debug.shell_transit_rotate", false); + /** The same as legacy APP_TRANSITION_TIMEOUT_MS. */ private static final int DEFAULT_TIMEOUT_MS = 5000; /** Less duration for CHANGE type because it does not involve app startup. */ @@ -203,6 +208,11 @@ class TransitionController { return mTransitionPlayer != null; } + /** @return {@code true} if using shell-transitions rotation instead of fixed-rotation. */ + boolean useShellTransitionsRotation() { + return isShellTransitionsEnabled() && SHELL_TRANSITIONS_ROTATION; + } + /** * @return {@code true} if transition is actively collecting changes. This is {@code false} * once a transition is playing @@ -246,6 +256,17 @@ class TransitionController { return false; } + /** @return {@code true} if wc is in a participant subtree */ + boolean isTransitionOnDisplay(@NonNull DisplayContent dc) { + if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) { + return true; + } + for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { + if (mPlayingTransitions.get(i).isOnDisplay(dc)) return true; + } + return false; + } + /** * @return {@code true} if {@param ar} is part of a transient-launch activity in an active * transition. @@ -260,6 +281,21 @@ class TransitionController { return false; } + /** + * Temporary work-around to deal with integration of legacy fixed-rotation. Returns whether + * the activity was visible before the collecting transition. + * TODO: at-least replace the polling mechanism. + */ + boolean wasVisibleAtStart(@NonNull ActivityRecord ar) { + if (mCollectingTransition == null) return ar.isVisible(); + final Transition.ChangeInfo ci = mCollectingTransition.mChanges.get(ar); + if (ci == null) { + // not part of transition, so use current state. + return ar.isVisible(); + } + return ci.mVisible; + } + @WindowManager.TransitionType int getCollectingTransitionType() { return mCollectingTransition != null ? mCollectingTransition.mType : TRANSIT_NONE; @@ -484,6 +520,11 @@ class TransitionController { } } + void setSeamlessRotation(@NonNull WindowContainer wc) { + if (mCollectingTransition == null) return; + mCollectingTransition.setSeamlessRotation(wc); + } + void legacyDetachNavigationBarFromApp(@NonNull IBinder token) { final Transition transition = Transition.fromBinder(token); if (transition == null || !mPlayingTransitions.contains(transition)) { diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 36bb55e5f557..6ee30bb956f0 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -111,6 +111,14 @@ class WallpaperWindowToken extends WindowToken { changed = true; } if (mTransitionController.isShellTransitionsEnabled()) { + // Apply legacy fixed rotation to wallpaper if it is becoming visible + if (!mTransitionController.useShellTransitionsRotation() && changed && visible) { + final WindowState wallpaperTarget = + mDisplayContent.mWallpaperController.getWallpaperTarget(); + if (wallpaperTarget != null && wallpaperTarget.mToken.hasFixedRotationTransform()) { + linkFixedRotationTransform(wallpaperTarget.mToken); + } + } return changed; } diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java index 073a508083f0..5bfd546f7620 100644 --- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java +++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java @@ -80,6 +80,16 @@ public class WindowAnimationSpec implements AnimationSpec { } @Override + public boolean getShowBackground() { + return mAnimation.getShowBackground(); + } + + @Override + public int getBackgroundColor() { + return mAnimation.getBackgroundColor(); + } + + @Override public long getDuration() { return mAnimation.computeDurationHint(); } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 8a373bf5c09c..e1746cca455a 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -39,6 +39,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION; +import static com.android.server.wm.AppTransition.isActivityTransitOld; import static com.android.server.wm.AppTransition.isTaskTransitOld; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.IdentifierProto.HASH_CODE; @@ -78,13 +79,14 @@ import android.os.Trace; import android.util.ArraySet; import android.util.Pair; import android.util.Pools; +import android.util.RotationUtils; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; -import android.view.InsetsState; import android.view.MagnificationSpec; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationTarget; +import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.Builder; import android.view.SurfaceControlViewHost; @@ -98,6 +100,7 @@ import android.window.WindowContainerToken; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ToBooleanFunction; import com.android.server.wm.SurfaceAnimator.Animatable; @@ -197,6 +200,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< private final Point mTmpPos = new Point(); protected final Point mLastSurfacePosition = new Point(); + protected @Surface.Rotation int mLastDeltaRotation = Surface.ROTATION_0; /** Total number of elements in this subtree, including our own hierarchy element. */ private int mTreeWeight = 1; @@ -473,6 +477,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< t.remove(mSurfaceControl); // Clear the last position so the new SurfaceControl will get correct position mLastSurfacePosition.set(0, 0); + mLastDeltaRotation = Surface.ROTATION_0; final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(null) .setContainerLayer() @@ -644,6 +649,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< getSyncTransaction().remove(mSurfaceControl); setSurfaceControl(null); mLastSurfacePosition.set(0, 0); + mLastDeltaRotation = Surface.ROTATION_0; scheduleAnimation(); } if (mOverlayHost != null) { @@ -2786,7 +2792,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @TransitionOldType int transit, boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) { final Task task = asTask(); - if (task != null && !enter && !task.isHomeOrRecentsRootTask()) { + if (task != null && !enter && !task.isActivityTypeHomeOrRecents()) { final InsetsControlTarget imeTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING); final boolean isImeLayeringTarget = imeTarget != null && imeTarget.getWindow() != null && imeTarget.getWindow().getTask() == task; @@ -2816,6 +2822,25 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } + final ActivityRecord activityRecord = asActivityRecord(); + if (activityRecord != null && isActivityTransitOld(transit) + && adapter.getShowBackground()) { + final @ColorInt int backgroundColorForTransition; + if (adapter.getBackgroundColor() != 0) { + // If available use the background color provided through getBackgroundColor + // which if set originates from a call to overridePendingAppTransition. + backgroundColorForTransition = adapter.getBackgroundColor(); + } else { + // Otherwise default to the window's background color if provided through + // the theme as the background color for the animation - the top most window + // with a valid background color and showBackground set takes precedence. + final Task arTask = activityRecord.getTask(); + backgroundColorForTransition = ColorUtils.setAlphaComponent( + arTask.getTaskDescription().getBackgroundColor(), 255); + } + animationRunnerBuilder.setTaskBackgroundColor(backgroundColorForTransition); + } + animationRunnerBuilder.build() .startAnimation(getPendingTransaction(), adapter, !isVisible(), ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter); @@ -3127,12 +3152,43 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } getRelativePosition(mTmpPos); - if (mTmpPos.equals(mLastSurfacePosition)) { + final int deltaRotation = getRelativeDisplayRotation(); + if (mTmpPos.equals(mLastSurfacePosition) && deltaRotation == mLastDeltaRotation) { return; } t.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y); + // set first, since we don't want rotation included in this (for now). mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y); + + if (mTransitionController.isShellTransitionsEnabled() + && !mTransitionController.useShellTransitionsRotation()) { + if (deltaRotation != Surface.ROTATION_0) { + updateSurfaceRotation(t, deltaRotation, null /* positionLeash */); + } else if (deltaRotation != mLastDeltaRotation) { + t.setMatrix(mSurfaceControl, 1, 0, 0, 1); + } + } + mLastDeltaRotation = deltaRotation; + } + + /** + * Updates the surface transform based on a difference in displayed-rotation from its parent. + * @param positionLeash If non-null, the rotated position will be set on this surface instead + * of the window surface. {@see WindowToken#getOrCreateFixedRotationLeash}. + */ + protected void updateSurfaceRotation(Transaction t, @Surface.Rotation int deltaRotation, + @Nullable SurfaceControl positionLeash) { + // parent must be non-null otherwise deltaRotation would be 0. + RotationUtils.rotateSurface(t, mSurfaceControl, deltaRotation); + mTmpPos.set(mLastSurfacePosition.x, mLastSurfacePosition.y); + final Rect parentBounds = getParent().getBounds(); + final boolean flipped = (deltaRotation % 2) != 0; + RotationUtils.rotatePoint(mTmpPos, deltaRotation, + flipped ? parentBounds.height() : parentBounds.width(), + flipped ? parentBounds.width() : parentBounds.height()); + t.setPosition(positionLeash != null ? positionLeash : mSurfaceControl, + mTmpPos.x, mTmpPos.y); } @VisibleForTesting @@ -3170,6 +3226,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } + /** @return the difference in displayed-rotation from parent. */ + @Surface.Rotation + int getRelativeDisplayRotation() { + final WindowContainer parent = getParent(); + if (parent == null) return Surface.ROTATION_0; + final int rotation = getWindowConfiguration().getDisplayRotation(); + final int parentRotation = parent.getWindowConfiguration().getDisplayRotation(); + return RotationUtils.deltaRotation(rotation, parentRotation); + } + void waitForAllWindowsDrawn() { forAllWindows(w -> { w.requestDrawIfNeeded(mWaitingForDrawn); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0f3b903c2ac2..4d1bc22f2437 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -421,7 +421,7 @@ public class WindowManagerService extends IWindowManager.Stub "persist.wm.enable_remote_keyguard_animation"; private static final int sEnableRemoteKeyguardAnimation = - SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1); + SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 2); /** * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY @@ -1364,6 +1364,7 @@ public class WindowManagerService extends IWindowManager.Stub float lightRadius = a.getDimension(R.styleable.Lighting_lightRadius, 0); float ambientShadowAlpha = a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0); float spotShadowAlpha = a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0); + a.recycle(); float[] ambientColor = {0.f, 0.f, 0.f, ambientShadowAlpha}; float[] spotColor = {0.f, 0.f, 0.f, spotShadowAlpha}; SurfaceControl.setGlobalShadowSettings(ambientColor, spotColor, lightY, lightZ, @@ -2893,16 +2894,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - void setNewDisplayOverrideConfiguration(Configuration overrideConfig, - @NonNull DisplayContent dc) { - if (dc.mWaitingForConfig) { - dc.mWaitingForConfig = false; - mLastFinishedFreezeSource = "new-config"; - } - - mRoot.setDisplayOverrideConfigurationIfNeeded(overrideConfig, dc); - } - // TODO(multi-display): remove when no default display use case. void prepareAppTransitionNone() { if (!checkCallingPermission(MANAGE_APP_TOKENS, "prepareAppTransition()")) { @@ -8352,9 +8343,9 @@ public class WindowManagerService extends IWindowManager.Stub * views. */ void grantInputChannel(Session session, int callingUid, int callingPid, int displayId, - SurfaceControl surface, IWindow window, IBinder hostInputToken, - int flags, int privateFlags, int type, IBinder focusGrantToken, - InputChannel outInputChannel) { + SurfaceControl surface, IWindow window, IBinder hostInputToken, + int flags, int privateFlags, int type, IBinder focusGrantToken, + String inputHandleName, InputChannel outInputChannel) { final InputApplicationHandle applicationHandle; final String name; final InputChannel clientChannel; @@ -8362,7 +8353,7 @@ public class WindowManagerService extends IWindowManager.Stub EmbeddedWindowController.EmbeddedWindow win = new EmbeddedWindowController.EmbeddedWindow(session, this, window, mInputToWindowMap.get(hostInputToken), callingUid, callingPid, type, - displayId, focusGrantToken); + displayId, focusGrantToken, inputHandleName); clientChannel = win.openInputChannel(); mEmbeddedWindowController.add(clientChannel.getToken(), win); applicationHandle = win.getApplicationHandle(); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 1d5c1841fde1..4c7891b9fc51 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -19,6 +19,8 @@ package com.android.server.wm; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.ActivityManager.isStartResultSuccessful; import static android.view.Display.DEFAULT_DISPLAY; +import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION; +import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION_RECT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT; @@ -1224,10 +1226,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub while (entries.hasNext()) { final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next(); // Only allow to apply changes to TaskFragment that is created by this organizer. - enforceTaskFragmentOrganized(func, WindowContainer.fromBinder(entry.getKey()), - organizer); + WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); + enforceTaskFragmentOrganized(func, wc, organizer); + enforceTaskFragmentConfigChangeAllowed(func, wc, entry.getValue(), organizer); } + // TODO(b/197364677): Enforce safety of hierarchy operations in untrusted mode. E.g. one + // could first change a trusted TF, and then start/reparent untrusted activity there. + // Hierarchy changes final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps(); for (int i = hops.size() - 1; i >= 0; i--) { @@ -1297,6 +1303,57 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } + /** + * Makes sure that SurfaceControl transactions and the ability to set bounds outside of the + * parent bounds are not allowed for embedding without full trust between the host and the + * target. + * TODO(b/197364677): Allow SC transactions when the client-driven animations are protected from + * tapjacking. + */ + private void enforceTaskFragmentConfigChangeAllowed(String func, @Nullable WindowContainer wc, + WindowContainerTransaction.Change change, ITaskFragmentOrganizer organizer) { + if (wc == null) { + Slog.e(TAG, "Attempt to operate on task fragment that no longer exists"); + return; + } + // Check if TaskFragment is embedded in fully trusted mode + if (wc.asTaskFragment().isAllowedToBeEmbeddedInTrustedMode()) { + // Fully trusted, no need to check further + return; + } + + if (change == null) { + return; + } + final int changeMask = change.getChangeMask(); + if ((changeMask & (CHANGE_BOUNDS_TRANSACTION | CHANGE_BOUNDS_TRANSACTION_RECT)) != 0) { + String msg = "Permission Denial: " + func + " from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " trying to apply SurfaceControl changes to TaskFragment in non-trusted " + + "embedding mode, TaskFragmentOrganizer=" + organizer; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + if (change.getWindowSetMask() == 0) { + // Nothing else to check. + return; + } + WindowConfiguration requestedWindowConfig = change.getConfiguration().windowConfiguration; + WindowContainer wcParent = wc.getParent(); + if (wcParent == null) { + Slog.e(TAG, "Attempt to set bounds on task fragment that has no parent"); + return; + } + if (!wcParent.getBounds().contains(requestedWindowConfig.getBounds())) { + String msg = "Permission Denial: " + func + " from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " trying to apply bounds outside of parent for non-trusted host," + + " TaskFragmentOrganizer=" + organizer; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + } + void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams, @Nullable IBinder errorCallbackToken, @NonNull CallerInfo caller) { final ActivityRecord ownerActivity = diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 11a6141c6c6d..498eaabb9386 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2385,6 +2385,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP dc.getDisplayPolicy().removeWindowLw(this); disposeInputChannel(); + mOnBackInvokedCallback = null; mSession.windowRemovedLocked(); try { @@ -2438,6 +2439,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP try { disposeInputChannel(); + mOnBackInvokedCallback = null; ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b " diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index db231f6a1747..f3980342ec2e 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -44,6 +44,7 @@ import android.os.IBinder; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; import android.view.InsetsState; +import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowManager.LayoutParams.WindowType; import android.window.WindowContext; @@ -99,6 +100,7 @@ class WindowToken extends WindowContainer<WindowState> { final boolean mOwnerCanManageAppTokens; private FixedRotationTransformState mFixedRotationTransformState; + private SurfaceControl mFixedRotationTransformLeash; /** * When set to {@code true}, this window token is created from {@link WindowContext} @@ -521,8 +523,14 @@ class WindowToken extends WindowContainer<WindowState> { if (state == null) { return; } - - state.resetTransform(); + if (!mTransitionController.isShellTransitionsEnabled()) { + state.resetTransform(); + } else { + // Remove all the leashes + for (int i = state.mAssociatedTokens.size() - 1; i >= 0; --i) { + state.mAssociatedTokens.get(i).removeFixedRotationLeash(); + } + } // Clear the flag so if the display will be updated to the same orientation, the transform // won't take effect. state.mIsTransforming = false; @@ -554,6 +562,43 @@ class WindowToken extends WindowContainer<WindowState> { } /** + * Gets or creates a leash which can be treated as if this window is not-rotated. This is + * used to adapt mismatched-rotation surfaces into code that expects all windows to share + * the same rotation. + */ + @Nullable + SurfaceControl getOrCreateFixedRotationLeash() { + if (!mTransitionController.isShellTransitionsEnabled()) return null; + final int rotation = getRelativeDisplayRotation(); + if (rotation == Surface.ROTATION_0) return mFixedRotationTransformLeash; + if (mFixedRotationTransformLeash != null) return mFixedRotationTransformLeash; + + final SurfaceControl.Transaction t = getSyncTransaction(); + final SurfaceControl leash = makeSurface().setContainerLayer() + .setParent(getParentSurfaceControl()) + .setName(getSurfaceControl() + " - rotation-leash") + .setHidden(false) + .setEffectLayer() + .setCallsite("WindowToken.getOrCreateFixedRotationLeash") + .build(); + t.setPosition(leash, mLastSurfacePosition.x, mLastSurfacePosition.y); + t.show(leash); + t.reparent(getSurfaceControl(), leash); + t.setAlpha(getSurfaceControl(), 1.f); + mFixedRotationTransformLeash = leash; + updateSurfaceRotation(t, rotation, mFixedRotationTransformLeash); + return mFixedRotationTransformLeash; + } + + void removeFixedRotationLeash() { + if (mFixedRotationTransformLeash == null) return; + final SurfaceControl.Transaction t = getSyncTransaction(); + t.reparent(getSurfaceControl(), getParentSurfaceControl()); + t.remove(mFixedRotationTransformLeash); + mFixedRotationTransformLeash = null; + } + + /** * It is called when the window is using fixed rotation transform, and before display applies * the same rotation, the rotation change for display is canceled, e.g. the orientation from * sensor is updated to previous direction. @@ -575,7 +620,7 @@ class WindowToken extends WindowContainer<WindowState> { @Override void updateSurfacePosition(SurfaceControl.Transaction t) { super.updateSurfacePosition(t); - if (isFixedRotationTransforming()) { + if (!mTransitionController.isShellTransitionsEnabled() && isFixedRotationTransforming()) { final ActivityRecord r = asActivityRecord(); final Task rootTask = r != null ? r.getRootTask() : null; // Don't transform the activity in PiP because the PiP task organizer will handle it. @@ -588,6 +633,20 @@ class WindowToken extends WindowContainer<WindowState> { } @Override + protected void updateSurfaceRotation(SurfaceControl.Transaction t, + @Surface.Rotation int deltaRotation, SurfaceControl positionLeash) { + final ActivityRecord r = asActivityRecord(); + if (r != null) { + final Task rootTask = r.getRootTask(); + // Don't transform the activity in PiP because the PiP task organizer will handle it. + if (rootTask != null && rootTask.inPinnedWindowingMode()) { + return; + } + } + super.updateSurfaceRotation(t, deltaRotation, positionLeash); + } + + @Override void resetSurfacePositionForAnimationLeash(SurfaceControl.Transaction t) { // Keep the transformed position to animate because the surface will show in different // rotation than the animator of leash. diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 95e1aec8ca52..0d49f5fffb4b 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -160,7 +160,7 @@ cc_defaults { "android.hardware.graphics.common@1.2", "android.hardware.graphics.common-V3-ndk", "android.hardware.graphics.mapper@4.0", - "android.hardware.input.classifier@1.0", + "android.hardware.input.processor-V1-ndk", "android.hardware.ir@1.0", "android.hardware.light@2.0", "android.hardware.memtrack-V1-ndk", diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp index adc91fc3f2e8..8197b67355d4 100644 --- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp +++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp @@ -243,22 +243,34 @@ static int openUinput(const char* readableName, jint vendorId, jint productId, c xAbsSetup.code = ABS_MT_POSITION_X; xAbsSetup.absinfo.maximum = screenWidth - 1; xAbsSetup.absinfo.minimum = 0; - ioctl(fd, UI_ABS_SETUP, xAbsSetup); + if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput x axis: %s", strerror(errno)); + return -errno; + } uinput_abs_setup yAbsSetup; yAbsSetup.code = ABS_MT_POSITION_Y; yAbsSetup.absinfo.maximum = screenHeight - 1; yAbsSetup.absinfo.minimum = 0; - ioctl(fd, UI_ABS_SETUP, yAbsSetup); + if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput y axis: %s", strerror(errno)); + return -errno; + } uinput_abs_setup majorAbsSetup; majorAbsSetup.code = ABS_MT_TOUCH_MAJOR; majorAbsSetup.absinfo.maximum = screenWidth - 1; majorAbsSetup.absinfo.minimum = 0; - ioctl(fd, UI_ABS_SETUP, majorAbsSetup); + if (ioctl(fd, UI_ABS_SETUP, &majorAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput major axis: %s", strerror(errno)); + return -errno; + } uinput_abs_setup pressureAbsSetup; pressureAbsSetup.code = ABS_MT_PRESSURE; pressureAbsSetup.absinfo.maximum = 255; pressureAbsSetup.absinfo.minimum = 0; - ioctl(fd, UI_ABS_SETUP, pressureAbsSetup); + if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno)); + return -errno; + } } if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) { ALOGE("Error creating uinput device: %s", strerror(errno)); @@ -266,6 +278,7 @@ static int openUinput(const char* readableName, jint vendorId, jint productId, c } } else { // UI_DEV_SETUP was not introduced until version 5. Try setting up manually. + ALOGI("Falling back to version %d manual setup", version); uinput_user_dev fallback; memset(&fallback, 0, sizeof(fallback)); strlcpy(fallback.name, readableName, UINPUT_MAX_NAME_SIZE); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 80011d172ad0..091d879a1876 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -7331,7 +7331,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public void reportPasswordChanged(@UserIdInt int userId) { + public void reportPasswordChanged(PasswordMetrics metrics, @UserIdInt int userId) { if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } @@ -7363,6 +7363,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { affectedUserIds.addAll(removeCaApprovalsIfNeeded(userId)); saveSettingsForUsersLocked(affectedUserIds); } + if (mInjector.securityLogIsLoggingEnabled()) { + SecurityLog.writeEvent(SecurityLog.TAG_PASSWORD_CHANGED, + /* complexity */ metrics.determineComplexity(), /*user*/ userId); + } } /** @@ -9616,18 +9620,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { "Cannot set the profile owner on a user which is already set-up"); if (!mIsWatch) { - // Only the default supervision profile owner can be set as profile owner after SUW + final String supervisionRolePackage = mContext.getResources().getString( + com.android.internal.R.string.config_systemSupervision); + // Only the default supervision profile owner or supervision role holder + // can be set as profile owner after SUW final String supervisor = mContext.getResources().getString( com.android.internal.R.string .config_defaultSupervisionProfileOwnerComponent); - if (supervisor == null) { + if (supervisor == null && supervisionRolePackage == null) { throw new IllegalStateException("Unable to set profile owner post-setup, no" + "default supervisor profile owner defined"); } final ComponentName supervisorComponent = ComponentName.unflattenFromString( supervisor); - if (!owner.equals(supervisorComponent)) { + if (!owner.equals(supervisorComponent) + && !owner.getPackageName().equals(supervisionRolePackage)) { throw new IllegalStateException("Unable to set non-default profile owner" + " post-setup " + owner); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index a7539b507e62..5c3721d482c6 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -264,6 +264,10 @@ public final class SystemServer implements Dumpable { "com.android.server.companion.virtual.VirtualDeviceManagerService"; private static final String STATS_COMPANION_APEX_PATH = "/apex/com.android.os.statsd/javalib/service-statsd.jar"; + private static final String SCHEDULING_APEX_PATH = + "/apex/com.android.scheduling/javalib/service-scheduling.jar"; + private static final String REBOOT_READINESS_LIFECYCLE_CLASS = + "com.android.server.scheduling.RebootReadinessManagerService$Lifecycle"; private static final String CONNECTIVITY_SERVICE_APEX_PATH = "/apex/com.android.tethering/javalib/service-connectivity.jar"; private static final String STATS_COMPANION_LIFECYCLE_CLASS = @@ -2575,6 +2579,12 @@ public final class SystemServer implements Dumpable { STATS_COMPANION_LIFECYCLE_CLASS, STATS_COMPANION_APEX_PATH); t.traceEnd(); + // Reboot Readiness + t.traceBegin("StartRebootReadinessManagerService"); + mSystemServiceManager.startServiceFromJar( + REBOOT_READINESS_LIFECYCLE_CLASS, SCHEDULING_APEX_PATH); + t.traceEnd(); + // Statsd pulled atoms t.traceBegin("StartStatsPullAtomService"); mSystemServiceManager.startService(STATS_PULL_ATOM_SERVICE_CLASS); diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index 717168f65c7c..f72b23ce41c7 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -43,6 +43,7 @@ import android.media.midi.MidiManager; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; +import android.os.ParcelUuid; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; @@ -64,6 +65,14 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.UUID; + +// NOTE about locking order: +// if there is a path that syncs on BOTH mDevicesByInfo AND mDeviceConnections, +// this order must be observed +// 1. synchronized (mDevicesByInfo) +// 2. synchronized (mDeviceConnections) +//TODO Introduce a single lock object to lock the whole state and avoid the requirement above. public class MidiService extends IMidiManager.Stub { @@ -122,6 +131,9 @@ public class MidiService extends IMidiManager.Stub { // UID of BluetoothMidiService private int mBluetoothServiceUid; + private static final UUID MIDI_SERVICE = UUID.fromString( + "03B80E5A-EDE8-4B33-A751-6CE34EC4C700"); + // PackageMonitor for listening to package changes private final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override @@ -434,14 +446,19 @@ public class MidiService extends IMidiManager.Stub { public void onServiceConnected(ComponentName name, IBinder service) { IMidiDeviceServer server = null; if (mBluetoothDevice != null) { - IBluetoothMidiService mBluetoothMidiService = IBluetoothMidiService.Stub.asInterface(service); - try { - // We need to explicitly add the device in a separate method - // because onBind() is only called once. - IBinder deviceBinder = mBluetoothMidiService.addBluetoothDevice(mBluetoothDevice); - server = IMidiDeviceServer.Stub.asInterface(deviceBinder); - } catch(RemoteException e) { - Log.e(TAG, "Could not call addBluetoothDevice()", e); + IBluetoothMidiService mBluetoothMidiService = + IBluetoothMidiService.Stub.asInterface(service); + if (mBluetoothMidiService != null) { + try { + // We need to explicitly add the device in a separate method + // because onBind() is only called once. + IBinder deviceBinder = + mBluetoothMidiService.addBluetoothDevice( + mBluetoothDevice); + server = IMidiDeviceServer.Stub.asInterface(deviceBinder); + } catch (RemoteException e) { + Log.e(TAG, "Could not call addBluetoothDevice()", e); + } } } else { server = IMidiDeviceServer.Stub.asInterface(service); @@ -482,19 +499,19 @@ public class MidiService extends IMidiManager.Stub { } public void removeDeviceConnection(DeviceConnection connection) { - synchronized (mDeviceConnections) { - mDeviceConnections.remove(connection); + synchronized (mDevicesByInfo) { + synchronized (mDeviceConnections) { + mDeviceConnections.remove(connection); - if (mDeviceConnections.size() == 0 && mServiceConnection != null) { - mContext.unbindService(mServiceConnection); - mServiceConnection = null; - if (mBluetoothDevice != null) { - // Bluetooth devices are ephemeral - remove when no clients exist - synchronized (mDevicesByInfo) { + if (mDeviceConnections.size() == 0 && mServiceConnection != null) { + mContext.unbindService(mServiceConnection); + mServiceConnection = null; + if (mBluetoothDevice != null) { + // Bluetooth devices are ephemeral - remove when no clients exist closeLocked(); + } else { + setDeviceServer(null); } - } else { - setDeviceServer(null); } } } @@ -589,6 +606,20 @@ public class MidiService extends IMidiManager.Stub { } } + // Note, this isn't useful at connect-time because the service UUIDs haven't + // been gathered yet. + private boolean isBLEMIDIDevice(BluetoothDevice btDevice) { + ParcelUuid[] uuids = btDevice.getUuids(); + if (uuids != null) { + for (ParcelUuid uuid : uuids) { + if (uuid.getUuid().equals(MIDI_SERVICE)) { + return true; + } + } + } + return false; + } + private final BroadcastReceiver mBleMidiReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -603,6 +634,8 @@ public class MidiService extends IMidiManager.Stub { Log.d(TAG, "ACTION_ACL_CONNECTED"); BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + // We can't determine here if this is a BLD-MIDI device, so go ahead and try + // to open as a MIDI device, further down it will get figured out. openBluetoothDevice(btDevice); } break; @@ -611,7 +644,11 @@ public class MidiService extends IMidiManager.Stub { Log.d(TAG, "ACTION_ACL_DISCONNECTED"); BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - closeBluetoothDevice(btDevice); + // We DO know at this point if we are disconnecting a MIDI device, so + // don't bother if we are not. + if (isBLEMIDIDevice(btDevice)) { + closeBluetoothDevice(btDevice); + } } break; } diff --git a/services/proguard.flags b/services/proguard.flags index 5d01d3e7f85c..0e081f182d0d 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -1,21 +1,105 @@ -# TODO(b/196084106): Refine and optimize this configuration. Note that this +# TODO(b/210510433): Refine and optimize this configuration. Note that this # configuration is only used when `SOONG_CONFIG_ANDROID_SYSTEM_OPTIMIZE_JAVA=true`. --keep,allowoptimization,allowaccessmodification class ** { - !synthetic *; + +# Preserve line number information for debugging stack traces. +-keepattributes SourceFile,LineNumberTable + +# Allows making private and protected methods/fields public as part of +# optimization. This enables inlining of trivial getter/setter methods. +-allowaccessmodification + +# Process entrypoint +-keep class com.android.server.SystemServer { + public static void main(java.lang.String[]); } -# Various classes subclassed in ethernet-service (avoid marking final). +# APIs referenced by dependent JAR files and modules +-keep @interface android.annotation.SystemApi +-keep @android.annotation.SystemApi class * { + public protected *; +} +-keepclasseswithmembers class * { + @android.annotation.SystemApi *; +} + +# Derivatives of SystemService and other services created via reflection +-keep,allowoptimization,allowaccessmodification class * extends com.android.server.SystemService { + public <methods>; +} +-keep,allowoptimization,allowaccessmodification class * extends com.android.server.devicepolicy.BaseIDevicePolicyManager { + public <init>(...); +} +-keep,allowoptimization,allowaccessmodification class com.android.server.wallpaper.WallpaperManagerService { + public <init>(...); +} + +# Binder interfaces +-keep,allowoptimization,allowaccessmodification class * extends android.os.IInterface +-keep,allowoptimization,allowaccessmodification class * extends android.os.IHwInterface + +# Global entities normally kept through explicit Manifest entries +# TODO(b/210510433): Revisit and consider generating from frameworks/base/core/res/AndroidManifest.xml, +# by including that manifest with the library rule that triggers optimization. +-keep,allowoptimization,allowaccessmodification class * extends android.app.backup.BackupAgent +-keep,allowoptimization,allowaccessmodification class * extends android.content.BroadcastReceiver +-keep,allowoptimization,allowaccessmodification class * extends android.content.ContentProvider + +# Various classes subclassed in or referenced via JNI in ethernet-service -keep public class android.net.** { *; } +-keep,allowoptimization,allowaccessmodification class com.android.net.module.util.* { *; } +-keep,allowoptimization,allowaccessmodification public class com.android.server.net.IpConfigStore { *; } +-keep,allowoptimization,allowaccessmodification public class com.android.server.net.BaseNetworkObserver { *; } -# Referenced via CarServiceHelperService in car-frameworks-service (avoid removing). +# Referenced via CarServiceHelperService in car-frameworks-service (avoid removing) -keep public class com.android.server.utils.Slogf { *; } -# Allows making private and protected methods/fields public as part of -# optimization. This enables inlining of trivial getter/setter methods. --allowaccessmodification +# Notification extractors +# TODO(b/210510433): Revisit and consider generating from frameworks/base/core/res/res/values/config.xml. +-keep,allowoptimization,allowaccessmodification public class com.android.server.notification.** implements com.android.server.notification.NotificationSignalExtractor -# Disallow accessmodification for soundtrigger classes. Logging via reflective -# public member traversal can cause infinite loops. See b/210901706. --keep,allowoptimization class com.android.server.soundtrigger_middleware.** { - !synthetic *; +# JNI keep rules +# TODO(b/210510433): Revisit and fix with @Keep, or consider auto-generating from +# frameworks/base/services/core/jni/onload.cpp. +-keep,allowoptimization,allowaccessmodification class com.android.server.broadcastradio.hal1.BroadcastRadioService { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.broadcastradio.hal1.Convert { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.broadcastradio.hal1.Tuner { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.broadcastradio.hal1.TunerCallback { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.GnssConfiguration$HalInterfaceVersion { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.GnssPowerStats { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.hal.GnssNative { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$ProximityActiveListener { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorService { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.soundtrigger_middleware.ExternalCaptureStateTracker { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.storage.AppFuseBridge { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.tv.TvInputHal { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbAlsaJackDetector { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbMidiDevice { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorController$OnVibrationCompleteListener { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorManagerService$OnSyncedVibrationCompleteListener { *; } +-keepclasseswithmembers,allowoptimization,allowaccessmodification class com.android.server.** { + *** *FromNative(...); } +-keep,allowoptimization,allowaccessmodification class com.android.server.input.InputManagerService { + <methods>; +} +-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbHostManager { + *** usbDeviceRemoved(...); + *** usbDeviceAdded(...); +} +-keep,allowoptimization,allowaccessmodification class **.*NativeWrapper* { *; } + +# Miscellaneous reflection keep rules +# TODO(b/210510433): Revisit and fix with @Keep. +-keep,allowoptimization,allowaccessmodification class com.android.server.usage.AppStandbyController { + public <init>(...); +} +-keep,allowoptimization,allowaccessmodification class android.hardware.usb.gadget.** { *; } + +# Needed when optimizations enabled +# TODO(b/210510433): Revisit and fix with @Keep. +-keep,allowoptimization,allowaccessmodification class com.android.server.SystemService { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.SystemService$TargetUser { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.usage.StorageStatsManagerLocal { *; } +-keep,allowoptimization,allowaccessmodification class com.android.internal.util.** { *; } +-keep,allowoptimization,allowaccessmodification class android.os.** { *; } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt index cc663d955612..11300cec4da9 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt @@ -508,7 +508,8 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag AndroidPackage::shouldInheritKeyStoreKeys, ParsingPackage::setInheritKeyStoreKeys, true - ) + ), + getter(AndroidPackage::getKnownActivityEmbeddingCerts, setOf("TESTEMBEDDINGCERT")) ) override fun initialObject() = PackageImpl.forParsing( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt index a89b717fd90d..5180786e4077 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt @@ -53,7 +53,7 @@ class ParsedActivityTest : ParsedMainComponentTest( ParsedActivity::getTaskAffinity, ParsedActivity::getTheme, ParsedActivity::getUiOptions, - ParsedActivity::isSupportsSizeChanges, + ParsedActivity::isSupportsSizeChanges ) override fun mainComponentSubclassExtraParams() = listOf( @@ -73,6 +73,7 @@ class ParsedActivityTest : ParsedMainComponentTest( ActivityInfo.WindowLayout::minHeight ) } - ) + ), + getter(ParsedActivity::getKnownActivityEmbeddingCerts, setOf("TESTEMBEDDINGCERT")) ) } diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index 75669d50c75d..3e60af33e408 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -53,6 +53,7 @@ android_test { "service-jobscheduler", "service-permission.impl", "service-supplementalprocess.impl", + "services.companion", "services.core", "services.devicepolicy", "services.net", diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 58854706f837..f05658bf6b0b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -108,6 +108,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; +import android.Manifest; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; @@ -124,6 +125,7 @@ import android.app.usage.UsageStatsManagerInternal; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.PermissionChecker; import android.content.pm.PackageManagerInternal; import android.database.ContentObserver; import android.net.Uri; @@ -389,6 +391,7 @@ public class AlarmManagerServiceTest { .mockStatic(LocalServices.class) .spyStatic(Looper.class) .mockStatic(MetricsHelper.class) + .mockStatic(PermissionChecker.class) .mockStatic(PermissionManagerService.class) .mockStatic(ServiceManager.class) .mockStatic(Settings.Global.class) @@ -445,6 +448,10 @@ public class AlarmManagerServiceTest { doReturn(true) .when(() -> DateFormat.is24HourFormat(eq(mMockContext), anyInt())); + doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( + () -> PermissionChecker.checkPermissionForPreflight(any(), + eq(Manifest.permission.USE_EXACT_ALARM), anyInt(), anyInt(), anyString())); + when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager); registerAppIds(new String[]{TEST_CALLING_PACKAGE}, @@ -2158,6 +2165,7 @@ public class AlarmManagerServiceTest { @Test public void canScheduleExactAlarmsBinderCall() throws RemoteException { + // Policy permission is denied in setUp(). mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); // No permission, no exemption. @@ -2168,6 +2176,14 @@ public class AlarmManagerServiceTest { mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); + // Policy permission only, no exemption. + mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); + doReturn(PermissionChecker.PERMISSION_GRANTED).when( + () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.USE_EXACT_ALARM), anyInt(), eq(TEST_CALLING_UID), + eq(TEST_CALLING_PACKAGE))); + assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); + // Permission, no exemption. mockExactAlarmPermissionGrant(true, false, MODE_DEFAULT); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); @@ -2699,7 +2715,8 @@ public class AlarmManagerServiceTest { mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false); // No permission revoked. - verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(anyInt(), anyString()); + verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(anyInt(), anyString(), + anyBoolean()); // Permission got granted only for (appId1, userId2). final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -2754,14 +2771,14 @@ public class AlarmManagerServiceTest { // Permission got revoked only for (appId1, userId2) verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked( - eq(UserHandle.getUid(userId1, appId1)), eq(packages[0])); + eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]), eq(true)); verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked( - eq(UserHandle.getUid(userId1, appId2)), eq(packages[1])); + eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]), eq(true)); verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked( - eq(UserHandle.getUid(userId2, appId2)), eq(packages[1])); + eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]), eq(true)); verify(mService).removeExactAlarmsOnPermissionRevokedLocked( - eq(UserHandle.getUid(userId2, appId1)), eq(packages[0])); + eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]), eq(true)); } @Test @@ -2774,7 +2791,7 @@ public class AlarmManagerServiceTest { mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); assertAndHandleMessageSync(REMOVE_EXACT_ALARMS); verify(mService).removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, - TEST_CALLING_PACKAGE); + TEST_CALLING_PACKAGE, true); } @Test @@ -2859,7 +2876,8 @@ public class AlarmManagerServiceTest { null); assertEquals(6, mService.mAlarmStore.size()); - mService.removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, TEST_CALLING_PACKAGE); + mService.removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, TEST_CALLING_PACKAGE, + true); final ArrayList<Alarm> remaining = mService.mAlarmStore.asList(); assertEquals(3, remaining.size()); @@ -3080,7 +3098,7 @@ public class AlarmManagerServiceTest { SCHEDULE_EXACT_ALARM)).thenReturn(exactAlarmRequesters); final Intent packageAdded = new Intent(Intent.ACTION_PACKAGE_ADDED) - .setPackage(TEST_CALLING_PACKAGE) + .setData(Uri.fromParts("package", TEST_CALLING_PACKAGE, null)) .putExtra(Intent.EXTRA_REPLACING, true); mPackageChangesReceiver.onReceive(mMockContext, packageAdded); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java index a112bafbe4f9..936940fcde6e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java @@ -17,12 +17,15 @@ package com.android.server.am; import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; +import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET; import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED; import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED; import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET; +import static android.app.ActivityManager.isLowRamDeviceStatic; import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM; import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; @@ -46,10 +49,17 @@ import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS; -import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_BG; -import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_FG; -import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_FGS; +import static com.android.server.am.AppBatteryTracker.AppBatteryPolicy.getFloatArray; +import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_INDEX_BACKGROUND; +import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND; +import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND_SERVICE; +import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATT_DIMENS; +import static com.android.server.am.AppPermissionTracker.AppPermissionPolicy; import static com.android.server.am.AppRestrictionController.STOCK_PM_FLAGS; +import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_FGS_LOCATION; +import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_FGS_MEDIA_PLAYBACK; +import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_MEDIA_SESSION; +import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_PERMISSION; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -100,6 +110,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UidBatteryConsumer; import android.os.UserHandle; +import android.permission.PermissionManager; import android.provider.DeviceConfig; import android.telephony.TelephonyManager; import android.util.Log; @@ -107,12 +118,14 @@ import android.util.Pair; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.R; import com.android.server.AppStateTracker; import com.android.server.DeviceIdleInternal; import com.android.server.am.AppBatteryExemptionTracker.AppBatteryExemptionPolicy; import com.android.server.am.AppBatteryExemptionTracker.UidBatteryStates; import com.android.server.am.AppBatteryExemptionTracker.UidStateEventWithBattery; import com.android.server.am.AppBatteryTracker.AppBatteryPolicy; +import com.android.server.am.AppBatteryTracker.ImmutableBatteryUsage; import com.android.server.am.AppBindServiceEventsTracker.AppBindServiceEventsPolicy; import com.android.server.am.AppBroadcastEventsTracker.AppBroadcastEventsPolicy; import com.android.server.am.AppFGSTracker.AppFGSPolicy; @@ -122,6 +135,7 @@ import com.android.server.am.AppRestrictionController.NotificationHelper; import com.android.server.am.AppRestrictionController.UidBatteryUsageProvider; import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent; import com.android.server.apphibernation.AppHibernationManagerInternal; +import com.android.server.notification.NotificationManagerInternal; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.usage.AppStandbyInternal; @@ -146,6 +160,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.BiConsumer; +import java.util.stream.Collectors; /** * Tests for {@link AppRestrictionController}. @@ -210,6 +225,8 @@ public final class BackgroundRestrictionTest { @Mock private PackageManager mPackageManager; @Mock private PackageManagerInternal mPackageManagerInternal; @Mock private NotificationManager mNotificationManager; + @Mock private NotificationManagerInternal mNotificationManagerInternal; + @Mock private PermissionManager mPermissionManager; @Mock private PermissionManagerServiceInternal mPermissionManagerServiceInternal; @Mock private MediaSessionManager mMediaSessionManager; @Mock private RoleManager mRoleManager; @@ -245,6 +262,7 @@ public final class BackgroundRestrictionTest { private AppBindServiceEventsTracker mAppBindServiceEventsTracker; private AppFGSTracker mAppFGSTracker; private AppMediaSessionTracker mAppMediaSessionTracker; + private AppPermissionTracker mAppPermissionTracker; @Before public void setUp() throws Exception { @@ -284,12 +302,16 @@ public final class BackgroundRestrictionTest { doReturn(AppOpsManager.MODE_IGNORED) .when(mAppOpsManager) .checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, uid, packageName); - doReturn(PERMISSION_DENIED) - .when(mPermissionManagerServiceInternal) - .checkUidPermission(uid, ACCESS_BACKGROUND_LOCATION); - doReturn(PERMISSION_DENIED) - .when(mPermissionManagerServiceInternal) - .checkPermission(packageName, ACCESS_BACKGROUND_LOCATION, userId); + final String[] permissions = new String[] {ACCESS_BACKGROUND_LOCATION, + ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}; + for (String permission : permissions) { + doReturn(PERMISSION_DENIED) + .when(mPermissionManagerServiceInternal) + .checkUidPermission(uid, permission); + doReturn(PERMISSION_DENIED) + .when(mPermissionManagerServiceInternal) + .checkPermission(packageName, permission, userId); + } } doReturn(appStandbyInfoList).when(mAppStandbyInternal).getAppStandbyBuckets(userId); } @@ -531,11 +553,14 @@ public final class BackgroundRestrictionTest { final float bgRestrictedThreshold = 4.0f; final float bgRestrictedThresholdMah = BATTERY_FULL_CHARGE_MAH * bgRestrictedThreshold / 100.0f; + final int testPid = 1234; + final int notificationId = 1000; DeviceConfigSession<Boolean> bgCurrentDrainMonitor = null; DeviceConfigSession<Long> bgCurrentDrainWindow = null; DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketThreshold = null; DeviceConfigSession<Float> bgCurrentDrainBgRestrictedThreshold = null; + DeviceConfigSession<Boolean> bgPromptFgsWithNotiToBgRestricted = null; mBgRestrictionController.addAppBackgroundRestrictionListener(listener); @@ -552,34 +577,54 @@ public final class BackgroundRestrictionTest { DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED, DeviceConfig::getBoolean, - AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED); + mContext.getResources().getBoolean( + R.bool.config_bg_current_drain_monitor_enabled)); bgCurrentDrainMonitor.set(true); bgCurrentDrainWindow = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW, DeviceConfig::getLong, - AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS); + (long) mContext.getResources().getInteger( + R.integer.config_bg_current_drain_window)); bgCurrentDrainWindow.set(windowMs); bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET, DeviceConfig::getFloat, - AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD); + getFloatArray(mContext.getResources().obtainTypedArray( + R.array.config_bg_current_drain_threshold_to_restricted_bucket))[ + isLowRamDeviceStatic() ? 1 : 0]); bgCurrentDrainRestrictedBucketThreshold.set(restrictBucketThreshold); bgCurrentDrainBgRestrictedThreshold = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED, DeviceConfig::getFloat, - AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD); + getFloatArray(mContext.getResources().obtainTypedArray( + R.array.config_bg_current_drain_threshold_to_bg_restricted))[ + isLowRamDeviceStatic() ? 1 : 0]); bgCurrentDrainBgRestrictedThreshold.set(bgRestrictedThreshold); + bgPromptFgsWithNotiToBgRestricted = new DeviceConfigSession<>( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + ConstantsObserver.KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED, + DeviceConfig::getBoolean, + mContext.getResources().getBoolean( + R.bool.config_bg_prompt_fgs_with_noti_to_bg_restricted)); + bgPromptFgsWithNotiToBgRestricted.set(true); + mCurrentTimeMillis = 10_000L; doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis).when(stats).getStatsEndTimestamp(); doReturn(statsList).when(mBatteryStatsInternal).getBatteryUsageStats(anyObject()); + doReturn(true).when(mNotificationManagerInternal).isNotificationShown( + testPkgName, null, notificationId, testUser); + mAppFGSTracker.onForegroundServiceStateChanged(testPkgName, testUid, + testPid, true); + mAppFGSTracker.onForegroundServiceNotificationUpdated( + testPkgName, testUid, notificationId); runTestBgCurrentDrainMonitorOnce(listener, stats, uids, new double[]{restrictBucketThresholdMah - 1, 0}, @@ -710,6 +755,85 @@ public final class BackgroundRestrictionTest { Thread.sleep(windowMs); clearInvocations(mInjector.getAppStandbyInternal()); clearInvocations(mBgRestrictionController); + + // We're not going to prompt the user if the abusive app has a FGS with notification. + bgPromptFgsWithNotiToBgRestricted.set(false); + + runTestBgCurrentDrainMonitorOnce(listener, stats, uids, + new double[]{bgRestrictedThresholdMah + 1, 0}, + new double[]{0, restrictBucketThresholdMah - 1}, zeros, + () -> { + doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp(); + doReturn(mCurrentTimeMillis + windowMs) + .when(stats).getStatsEndTimestamp(); + mCurrentTimeMillis += windowMs + 1; + // We won't change restriction level automatically because it needs + // user consent. + try { + listener.verify(timeout, testUid, testPkgName, + RESTRICTION_LEVEL_BACKGROUND_RESTRICTED); + fail("There shouldn't be level change event like this"); + } catch (Exception e) { + // Expected. + } + verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket( + eq(testPkgName), + eq(STANDBY_BUCKET_RARE), + eq(testUser), + anyInt(), anyInt()); + // We should have requested to goto background restricted level. + verify(mBgRestrictionController, times(1)).handleRequestBgRestricted( + eq(testPkgName), + eq(testUid)); + // However, we won't have the prompt to user posted because the policy + // is not to show that for FGS with notification. + checkNotificationShown(new String[] {testPkgName}, never(), false); + }); + + // Pretend we have the notification dismissed. + mAppFGSTracker.onForegroundServiceNotificationUpdated( + testPkgName, testUid, -notificationId); + clearInvocations(mInjector.getAppStandbyInternal()); + clearInvocations(mBgRestrictionController); + + runTestBgCurrentDrainMonitorOnce(listener, stats, uids, + new double[]{bgRestrictedThresholdMah + 1, 0}, + new double[]{0, restrictBucketThresholdMah - 1}, zeros, + () -> { + doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp(); + doReturn(mCurrentTimeMillis + windowMs) + .when(stats).getStatsEndTimestamp(); + mCurrentTimeMillis += windowMs + 1; + // We won't change restriction level automatically because it needs + // user consent. + try { + listener.verify(timeout, testUid, testPkgName, + RESTRICTION_LEVEL_BACKGROUND_RESTRICTED); + fail("There shouldn't be level change event like this"); + } catch (Exception e) { + // Expected. + } + verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket( + eq(testPkgName), + eq(STANDBY_BUCKET_RARE), + eq(testUser), + anyInt(), anyInt()); + // We should have requested to goto background restricted level. + verify(mBgRestrictionController, times(1)).handleRequestBgRestricted( + eq(testPkgName), + eq(testUid)); + // Verify we have the notification posted now because its FGS is invisible. + checkNotificationShown(new String[] {testPkgName}, atLeast(1), true); + }); + + // Pretend notification is back on. + mAppFGSTracker.onForegroundServiceNotificationUpdated( + testPkgName, testUid, notificationId); + // Now we'll prompt the user even it has a FGS with notification. + bgPromptFgsWithNotiToBgRestricted.set(true); + clearInvocations(mInjector.getAppStandbyInternal()); + clearInvocations(mBgRestrictionController); + runTestBgCurrentDrainMonitorOnce(listener, stats, uids, new double[]{bgRestrictedThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, @@ -774,6 +898,7 @@ public final class BackgroundRestrictionTest { closeIfNotNull(bgCurrentDrainWindow); closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold); closeIfNotNull(bgCurrentDrainBgRestrictedThreshold); + closeIfNotNull(bgPromptFgsWithNotiToBgRestricted); } } @@ -1168,7 +1293,8 @@ public final class BackgroundRestrictionTest { .checkPermission(packageName, perm, UserHandle.getUserId(uid)); doReturn(PERMISSION_GRANTED) .when(mPermissionManagerServiceInternal) - .checkUidPermission(uid, ACCESS_BACKGROUND_LOCATION); + .checkUidPermission(uid, perm); + mInjector.getAppPermissionTracker().onPermissionsChanged(uid); } if (mediaControllers != null) { @@ -1193,7 +1319,8 @@ public final class BackgroundRestrictionTest { .checkPermission(packageName, perm, UserHandle.getUserId(uid)); doReturn(PERMISSION_DENIED) .when(mPermissionManagerServiceInternal) - .checkUidPermission(uid, ACCESS_BACKGROUND_LOCATION); + .checkUidPermission(uid, perm); + mInjector.getAppPermissionTracker().onPermissionsChanged(uid); } if (topStateThread != null) { topStateThread.join(); @@ -1261,6 +1388,10 @@ public final class BackgroundRestrictionTest { DeviceConfigSession<Long> bgLocationMinDurationThreshold = null; DeviceConfigSession<Boolean> bgCurrentDrainEventDurationBasedThresholdEnabled = null; DeviceConfigSession<Boolean> bgBatteryExemptionEnabled = null; + DeviceConfigSession<Integer> bgBatteryExemptionTypes = null; + DeviceConfigSession<Boolean> bgPermissionMonitorEnabled = null; + DeviceConfigSession<String> bgPermissionsInMonitor = null; + DeviceConfigSession<Boolean> bgCurrentDrainHighThresholdByBgLocation = null; mBgRestrictionController.addAppBackgroundRestrictionListener(listener); @@ -1280,64 +1411,76 @@ public final class BackgroundRestrictionTest { DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED, DeviceConfig::getBoolean, - AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED); + mContext.getResources().getBoolean( + R.bool.config_bg_current_drain_monitor_enabled)); bgCurrentDrainMonitor.set(true); bgCurrentDrainWindow = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW, DeviceConfig::getLong, - AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS); + (long) mContext.getResources().getInteger( + R.integer.config_bg_current_drain_window)); bgCurrentDrainWindow.set(windowMs); bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET, DeviceConfig::getFloat, - AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD); + getFloatArray(mContext.getResources().obtainTypedArray( + R.array.config_bg_current_drain_threshold_to_restricted_bucket))[ + isLowRamDeviceStatic() ? 1 : 0]); bgCurrentDrainRestrictedBucketThreshold.set(restrictBucketThreshold); bgCurrentDrainBgRestrictedThreshold = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED, DeviceConfig::getFloat, - AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD); + getFloatArray(mContext.getResources().obtainTypedArray( + R.array.config_bg_current_drain_threshold_to_bg_restricted))[ + isLowRamDeviceStatic() ? 1 : 0]); bgCurrentDrainBgRestrictedThreshold.set(bgRestrictedThreshold); bgCurrentDrainRestrictedBucketHighThreshold = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET, DeviceConfig::getFloat, - AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_HIGH_THRESHOLD); + getFloatArray(mContext.getResources().obtainTypedArray( + R.array.config_bg_current_drain_high_threshold_to_restricted_bucket))[ + isLowRamDeviceStatic() ? 1 : 0]); bgCurrentDrainRestrictedBucketHighThreshold.set(restrictBucketHighThreshold); bgCurrentDrainBgRestrictedHighThreshold = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED, DeviceConfig::getFloat, - AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD); + getFloatArray(mContext.getResources().obtainTypedArray( + R.array.config_bg_current_drain_high_threshold_to_bg_restricted))[ + isLowRamDeviceStatic() ? 1 : 0]); bgCurrentDrainBgRestrictedHighThreshold.set(bgRestrictedHighThreshold); bgMediaPlaybackMinDurationThreshold = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION, DeviceConfig::getLong, - AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION); + (long) mContext.getResources().getInteger( + R.integer.config_bg_current_drain_media_playback_min_duration)); bgMediaPlaybackMinDurationThreshold.set(bgMediaPlaybackMinDuration); bgLocationMinDurationThreshold = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION, DeviceConfig::getLong, - AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION); + (long) mContext.getResources().getInteger( + R.integer.config_bg_current_drain_location_min_duration)); bgLocationMinDurationThreshold.set(bgLocationMinDuration); bgCurrentDrainEventDurationBasedThresholdEnabled = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED, DeviceConfig::getBoolean, - AppBatteryPolicy - .DEFAULT_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED); + mContext.getResources().getBoolean( + R.bool.config_bg_current_drain_event_duration_based_threshold_enabled)); bgCurrentDrainEventDurationBasedThresholdEnabled.set(true); bgBatteryExemptionEnabled = new DeviceConfigSession<>( @@ -1347,6 +1490,38 @@ public final class BackgroundRestrictionTest { AppBatteryExemptionPolicy.DEFAULT_BG_BATTERY_EXEMPTION_ENABLED); bgBatteryExemptionEnabled.set(false); + bgBatteryExemptionTypes = new DeviceConfigSession<>( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES, + DeviceConfig::getInt, + mContext.getResources().getInteger( + R.integer.config_bg_current_drain_exempted_types)); + bgBatteryExemptionTypes.set(STATE_TYPE_MEDIA_SESSION | STATE_TYPE_FGS_MEDIA_PLAYBACK + | STATE_TYPE_FGS_LOCATION | STATE_TYPE_PERMISSION); + + bgPermissionMonitorEnabled = new DeviceConfigSession<>( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppPermissionPolicy.KEY_BG_PERMISSION_MONITOR_ENABLED, + DeviceConfig::getBoolean, + AppPermissionPolicy.DEFAULT_BG_PERMISSION_MONITOR_ENABLED); + bgPermissionMonitorEnabled.set(true); + + bgPermissionsInMonitor = new DeviceConfigSession<>( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppPermissionPolicy.KEY_BG_PERMISSION_MONITOR_ENABLED, + DeviceConfig::getString, + Arrays.stream(AppPermissionPolicy.DEFAULT_BG_PERMISSIONS_IN_MONITOR) + .collect(Collectors.joining(","))); + bgPermissionsInMonitor.set(ACCESS_FINE_LOCATION); + + bgCurrentDrainHighThresholdByBgLocation = new DeviceConfigSession<>( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION, + DeviceConfig::getBoolean, + mContext.getResources().getBoolean( + R.bool.config_bg_current_drain_high_threshold_by_bg_location)); + bgCurrentDrainHighThresholdByBgLocation.set(true); + mCurrentTimeMillis = 10_000L; doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis).when(stats).getStatsEndTimestamp(); @@ -1496,15 +1671,44 @@ public final class BackgroundRestrictionTest { setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros); mAppBatteryPolicy.reset(); - // Run with bg location permission, with higher current drain. + // Turn off the higher threshold for bg location access. + bgCurrentDrainHighThresholdByBgLocation.set(false); + + // Run with bg location permission, with moderate current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, 0, false, ACCESS_BACKGROUND_LOCATION, null, null, listener, stats, uids, - new double[]{restrictBucketHighThresholdMah - 1, 0}, + new double[]{restrictBucketThresholdMah - 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, null, windowMs, null, null, null); + // Run with bg location permission, with a bit higher current drain. + runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, + FOREGROUND_SERVICE_TYPE_NONE, 0, false, + ACCESS_BACKGROUND_LOCATION, null, null, listener, stats, uids, + new double[]{restrictBucketThresholdMah + 1, 0}, + new double[]{0, restrictBucketThresholdMah - 1}, zeros, + false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, + null, windowMs, null, null, null); + + // Start over. + resetBgRestrictionController(); + setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros); + mAppBatteryPolicy.reset(); + + // Turn on the higher threshold for bg location access. + bgCurrentDrainHighThresholdByBgLocation.set(true); + + // Run with bg location permission, with higher current drain. + runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, + FOREGROUND_SERVICE_TYPE_NONE, 0, false, + ACCESS_BACKGROUND_LOCATION , null, null, listener, stats, uids, + new double[]{restrictBucketHighThresholdMah - 1, 0}, + new double[]{0, restrictBucketThresholdMah - 1}, zeros, + true , RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false, + null, windowMs, null, null, null); + // Run with bg location permission, with even higher current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, 0, false, @@ -1587,6 +1791,36 @@ public final class BackgroundRestrictionTest { true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false, null, windowMs, initialBg, initialFgs, initialFg); + // Set the policy to exempt media session and permission. + bgBatteryExemptionTypes.set(STATE_TYPE_MEDIA_SESSION | STATE_TYPE_PERMISSION); + // Start over. + resetBgRestrictionController(); + setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros); + mAppBatteryPolicy.reset(); + + // Run with coarse location permission, with high current drain. + runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, + FOREGROUND_SERVICE_TYPE_NONE, 0, false, + ACCESS_COARSE_LOCATION, null, null, listener, stats, uids, + new double[]{restrictBucketThresholdMah + 1, 0}, + new double[]{0, restrictBucketThresholdMah - 1}, zeros, + false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, + null, windowMs, initialBg, initialFgs, initialFg); + + // Start over. + resetBgRestrictionController(); + setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros); + mAppBatteryPolicy.reset(); + + // Run with fine location permission, with high current drain. + runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, + FOREGROUND_SERVICE_TYPE_NONE, 0, false, + ACCESS_FINE_LOCATION, null, null, listener, stats, uids, + new double[]{restrictBucketThresholdMah + 1, 0}, + new double[]{0, restrictBucketThresholdMah - 1}, zeros, + true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, + null, windowMs, initialBg, initialFgs, initialFg); + // Start over. resetBgRestrictionController(); setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros); @@ -1616,6 +1850,10 @@ public final class BackgroundRestrictionTest { true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, null, windowMs, initialBg, initialFgs, initialFg); + // Set the policy to exempt all. + bgBatteryExemptionTypes.set(STATE_TYPE_MEDIA_SESSION | STATE_TYPE_FGS_MEDIA_PLAYBACK + | STATE_TYPE_FGS_LOCATION | STATE_TYPE_PERMISSION); + // Start over. resetBgRestrictionController(); setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros); @@ -1650,6 +1888,10 @@ public final class BackgroundRestrictionTest { closeIfNotNull(bgLocationMinDurationThreshold); closeIfNotNull(bgCurrentDrainEventDurationBasedThresholdEnabled); closeIfNotNull(bgBatteryExemptionEnabled); + closeIfNotNull(bgBatteryExemptionTypes); + closeIfNotNull(bgPermissionMonitorEnabled); + closeIfNotNull(bgPermissionsInMonitor); + closeIfNotNull(bgCurrentDrainHighThresholdByBgLocation); } } @@ -1670,6 +1912,15 @@ public final class BackgroundRestrictionTest { mAppBatteryExemptionTracker.reset(); mAppBatteryPolicy.reset(); } + if (perm != null) { + doReturn(PERMISSION_GRANTED) + .when(mPermissionManagerServiceInternal) + .checkPermission(packageName, perm, UserHandle.getUserId(uid)); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManagerServiceInternal) + .checkUidPermission(uid, perm); + mInjector.getAppPermissionTracker().onPermissionsChanged(uid); + } runExemptionTestOnce( packageName, uid, pid, serviceType, sleepMs, stopAfterSleep, perm, mediaControllers, topStateChanges, resetFGSTracker, false, @@ -1719,6 +1970,15 @@ public final class BackgroundRestrictionTest { ); } ); + if (perm != null) { + doReturn(PERMISSION_DENIED) + .when(mPermissionManagerServiceInternal) + .checkPermission(packageName, perm, UserHandle.getUserId(uid)); + doReturn(PERMISSION_DENIED) + .when(mPermissionManagerServiceInternal) + .checkUidPermission(uid, perm); + mInjector.getAppPermissionTracker().onPermissionsChanged(uid); + } } @Test @@ -1932,9 +2192,12 @@ public final class BackgroundRestrictionTest { private UidBatteryConsumer mockUidBatteryConsumer(int uid, double bg, double fgs, double fg) { UidBatteryConsumer uidConsumer = mock(UidBatteryConsumer.class); doReturn(uid).when(uidConsumer).getUid(); - doReturn(bg).when(uidConsumer).getConsumedPower(eq(BATT_DIMEN_BG)); - doReturn(fgs).when(uidConsumer).getConsumedPower(eq(BATT_DIMEN_FGS)); - doReturn(fg).when(uidConsumer).getConsumedPower(eq(BATT_DIMEN_FG)); + doReturn(bg).when(uidConsumer).getConsumedPower( + eq(BATT_DIMENS[BATTERY_USAGE_INDEX_BACKGROUND])); + doReturn(fgs).when(uidConsumer).getConsumedPower( + eq(BATT_DIMENS[BATTERY_USAGE_INDEX_FOREGROUND_SERVICE])); + doReturn(fg).when(uidConsumer).getConsumedPower( + eq(BATT_DIMENS[BATTERY_USAGE_INDEX_FOREGROUND])); return uidConsumer; } @@ -2234,8 +2497,8 @@ public final class BackgroundRestrictionTest { boolean[] isStart, long[] timestamps, double[] batteryUsage) { final LinkedList<UidStateEventWithBattery> result = new LinkedList<>(); for (int i = 0; i < isStart.length; i++) { - result.add(new UidStateEventWithBattery( - isStart[i], timestamps[i], batteryUsage[i], null)); + result.add(new UidStateEventWithBattery(isStart[i], timestamps[i], + new ImmutableBatteryUsage(0.0d, 0.0d, batteryUsage[i], 0.0d), null)); } return result; } @@ -2281,6 +2544,11 @@ public final class BackgroundRestrictionTest { BackgroundRestrictionTest.class), BackgroundRestrictionTest.this); controller.addAppStateTracker(mAppBindServiceEventsTracker); + mAppPermissionTracker = new AppPermissionTracker(mContext, controller, + TestAppPermissionTrackerInjector.class.getDeclaredConstructor( + BackgroundRestrictionTest.class), + BackgroundRestrictionTest.this); + controller.addAppStateTracker(mAppPermissionTracker); } catch (NoSuchMethodException e) { // Won't happen. } @@ -2375,6 +2643,11 @@ public final class BackgroundRestrictionTest { AppBatteryExemptionTracker getAppBatteryExemptionTracker() { return mAppBatteryExemptionTracker; } + + @Override + AppPermissionTracker getAppPermissionTracker() { + return mAppPermissionTracker; + } } private class TestBaseTrackerInjector<T extends BaseAppStatePolicy> @@ -2435,6 +2708,19 @@ public final class BackgroundRestrictionTest { } @Override + NotificationManagerInternal getNotificationManagerInternal() { + return BackgroundRestrictionTest.this.mNotificationManagerInternal; + } + + PackageManagerInternal getPackageManagerInternal() { + return BackgroundRestrictionTest.this.mPackageManagerInternal; + } + + PermissionManager getPermissionManager() { + return BackgroundRestrictionTest.this.mPermissionManager; + } + + @Override long getServiceStartForegroundTimeout() { return 1_000; // ms } @@ -2464,6 +2750,10 @@ public final class BackgroundRestrictionTest { extends TestBaseTrackerInjector<AppMediaSessionPolicy> { } + private class TestAppPermissionTrackerInjector + extends TestBaseTrackerInjector<AppPermissionPolicy> { + } + private class TestAppBroadcastEventsTrackerInjector extends TestBaseTrackerInjector<AppBroadcastEventsPolicy> { @Override diff --git a/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java new file mode 100644 index 000000000000..1b9cb28dc8b2 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual; + +import static android.hardware.camera2.CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_UNSUPPORTED; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraInjectionSession; +import android.hardware.camera2.CameraManager; +import android.os.Process; +import android.testing.TestableContext; + +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.server.LocalServices; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class CameraAccessControllerTest { + private static final String FRONT_CAMERA = "0"; + private static final String REAR_CAMERA = "1"; + private static final String TEST_APP_PACKAGE = "some.package"; + private static final String OTHER_APP_PACKAGE = "other.package"; + + private CameraAccessController mController; + + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getContext()); + + @Mock + private CameraManager mCameraManager; + @Mock + private PackageManager mPackageManager; + @Mock + private VirtualDeviceManagerInternal mDeviceManagerInternal; + @Mock + private CameraAccessController.CameraAccessBlockedCallback mBlockedCallback; + + private ApplicationInfo mTestAppInfo = new ApplicationInfo(); + private ApplicationInfo mOtherAppInfo = new ApplicationInfo(); + + @Captor + ArgumentCaptor<CameraInjectionSession.InjectionStatusCallback> mInjectionCallbackCaptor; + + + @Before + public void setUp() throws PackageManager.NameNotFoundException { + MockitoAnnotations.initMocks(this); + mContext.addMockSystemService(CameraManager.class, mCameraManager); + mContext.setMockPackageManager(mPackageManager); + LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); + LocalServices.addService(VirtualDeviceManagerInternal.class, mDeviceManagerInternal); + mController = new CameraAccessController(mContext, mDeviceManagerInternal, + mBlockedCallback); + mTestAppInfo.uid = Process.FIRST_APPLICATION_UID; + mOtherAppInfo.uid = Process.FIRST_APPLICATION_UID + 1; + when(mPackageManager.getApplicationInfo(eq(TEST_APP_PACKAGE), anyInt())).thenReturn( + mTestAppInfo); + when(mPackageManager.getApplicationInfo(eq(OTHER_APP_PACKAGE), anyInt())).thenReturn( + mOtherAppInfo); + mController.startObservingIfNeeded(); + } + + @Test + public void onCameraOpened_uidNotRunning_noCameraBlocking() throws CameraAccessException { + when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice( + eq(mTestAppInfo.uid))).thenReturn(false); + mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE); + verify(mCameraManager, never()).injectCamera(any(), any(), any(), any(), any()); + } + + + @Test + public void onCameraOpened_uidRunning_cameraBlocked() throws CameraAccessException { + when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice( + eq(mTestAppInfo.uid))).thenReturn(true); + mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE); + verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(), + any(), any()); + } + + @Test + public void onCameraClosed_injectionWasActive_cameraUnblocked() throws CameraAccessException { + when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice( + eq(mTestAppInfo.uid))).thenReturn(true); + mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE); + verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(), + any(), mInjectionCallbackCaptor.capture()); + CameraInjectionSession session = mock(CameraInjectionSession.class); + mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session); + + mController.onCameraClosed(FRONT_CAMERA); + verify(session).close(); + } + + + @Test + public void onCameraClosed_otherCameraClosed_cameraNotUnblocked() throws CameraAccessException { + when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice( + eq(mTestAppInfo.uid))).thenReturn(true); + mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE); + verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(), + any(), mInjectionCallbackCaptor.capture()); + CameraInjectionSession session = mock(CameraInjectionSession.class); + mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session); + + mController.onCameraClosed(REAR_CAMERA); + verify(session, never()).close(); + } + + @Test + public void onCameraClosed_twoCamerasBlocked_correctCameraUnblocked() + throws CameraAccessException { + when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice( + eq(mTestAppInfo.uid))).thenReturn(true); + when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice( + eq(mOtherAppInfo.uid))).thenReturn(true); + + mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE); + mController.onCameraOpened(REAR_CAMERA, OTHER_APP_PACKAGE); + + verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(), + any(), mInjectionCallbackCaptor.capture()); + CameraInjectionSession frontCameraSession = mock(CameraInjectionSession.class); + mInjectionCallbackCaptor.getValue().onInjectionSucceeded(frontCameraSession); + + verify(mCameraManager).injectCamera(eq(OTHER_APP_PACKAGE), eq(REAR_CAMERA), anyString(), + any(), mInjectionCallbackCaptor.capture()); + CameraInjectionSession rearCameraSession = mock(CameraInjectionSession.class); + mInjectionCallbackCaptor.getValue().onInjectionSucceeded(rearCameraSession); + + mController.onCameraClosed(REAR_CAMERA); + verify(frontCameraSession, never()).close(); + verify(rearCameraSession).close(); + } + + @Test + public void onInjectionError_callbackFires() throws CameraAccessException { + when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice( + eq(mTestAppInfo.uid))).thenReturn(true); + mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE); + verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(), + any(), mInjectionCallbackCaptor.capture()); + CameraInjectionSession session = mock(CameraInjectionSession.class); + mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session); + mInjectionCallbackCaptor.getValue().onInjectionError(ERROR_INJECTION_UNSUPPORTED); + verify(mBlockedCallback).onCameraAccessBlocked(eq(mTestAppInfo.uid)); + } + + @Test + public void twoCameraAccesses_onlyOneOnVirtualDisplay_callbackFiresForCorrectUid() + throws CameraAccessException { + when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice( + eq(mTestAppInfo.uid))).thenReturn(true); + mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE); + mController.onCameraOpened(REAR_CAMERA, OTHER_APP_PACKAGE); + + verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(), + any(), mInjectionCallbackCaptor.capture()); + CameraInjectionSession session = mock(CameraInjectionSession.class); + mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session); + mInjectionCallbackCaptor.getValue().onInjectionError(ERROR_INJECTION_UNSUPPORTED); + verify(mBlockedCallback).onCameraAccessBlocked(eq(mTestAppInfo.uid)); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java index f61d6ca750cd..e4c9f97ddf99 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -23,6 +23,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -59,6 +60,10 @@ import android.net.NetworkPolicyManager; import android.os.Build; import android.os.Looper; import android.os.SystemClock; +import android.telephony.CellSignalStrength; +import android.telephony.SignalStrength; +import android.telephony.TelephonyCallback; +import android.telephony.TelephonyManager; import android.util.DataUnit; import com.android.server.LocalServices; @@ -76,6 +81,7 @@ import org.mockito.junit.MockitoJUnitRunner; import java.time.Clock; import java.time.ZoneOffset; +import java.util.Set; @RunWith(MockitoJUnitRunner.class) public class ConnectivityControllerTest { @@ -302,6 +308,427 @@ public class ConnectivityControllerTest { } @Test + public void testStrongEnough_Cellular() { + mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = 0; + + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final TelephonyManager telephonyManager = mock(TelephonyManager.class); + when(mContext.getSystemService(TelephonyManager.class)) + .thenReturn(telephonyManager); + when(telephonyManager.createForSubscriptionId(anyInt())) + .thenReturn(telephonyManager); + final ArgumentCaptor<TelephonyCallback> signalStrengthsCaptor = + ArgumentCaptor.forClass(TelephonyCallback.class); + doNothing().when(telephonyManager) + .registerTelephonyCallback(any(), signalStrengthsCaptor.capture()); + final ArgumentCaptor<NetworkCallback> callbackCaptor = + ArgumentCaptor.forClass(NetworkCallback.class); + doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture()); + final Network net = mock(Network.class); + final NetworkCapabilities caps = createCapabilitiesBuilder() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_NOT_CONGESTED) + .setSubscriptionIds(Set.of(7357)) + .build(); + final JobInfo.Builder baseJobBuilder = createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), + DataUnit.MEBIBYTES.toBytes(1)) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); + final JobStatus jobExp = createJobStatus(baseJobBuilder.setExpedited(true)); + final JobStatus jobHigh = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_HIGH)); + final JobStatus jobDefEarly = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT), + now - 1000, now + 100_000); + final JobStatus jobDefLate = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT), + now - 100_000, now + 1000); + final JobStatus jobLow = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_LOW)); + final JobStatus jobMin = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN)); + final JobStatus jobMinRunner = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN)); + + final ConnectivityController controller = new ConnectivityController(mService); + + final NetworkCallback generalCallback = callbackCaptor.getValue(); + + when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L); + + when(mService.isBatteryCharging()).thenReturn(false); + when(mService.isBatteryNotLow()).thenReturn(false); + answerNetwork(generalCallback, null, null, net, caps); + + final TelephonyCallback.SignalStrengthsListener signalStrengthsListener = + (TelephonyCallback.SignalStrengthsListener) signalStrengthsCaptor.getValue(); + + controller.maybeStartTrackingJobLocked(jobMinRunner, null); + controller.prepareForExecutionLocked(jobMinRunner); + + final SignalStrength signalStrength = mock(SignalStrength.class); + + when(signalStrength.getLevel()).thenReturn( + CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN); + signalStrengthsListener.onSignalStrengthsChanged(signalStrength); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(signalStrength.getLevel()).thenReturn( + CellSignalStrength.SIGNAL_STRENGTH_POOR); + signalStrengthsListener.onSignalStrengthsChanged(signalStrength); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(mService.isBatteryCharging()).thenReturn(true); + when(mService.isBatteryNotLow()).thenReturn(false); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(mService.isBatteryCharging()).thenReturn(true); + when(mService.isBatteryNotLow()).thenReturn(true); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(mService.isBatteryCharging()).thenReturn(false); + when(mService.isBatteryNotLow()).thenReturn(false); + when(signalStrength.getLevel()).thenReturn( + CellSignalStrength.SIGNAL_STRENGTH_MODERATE); + signalStrengthsListener.onSignalStrengthsChanged(signalStrength); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(mService.isBatteryCharging()).thenReturn(false); + when(mService.isBatteryNotLow()).thenReturn(true); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(mService.isBatteryCharging()).thenReturn(true); + when(mService.isBatteryNotLow()).thenReturn(true); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(mService.isBatteryCharging()).thenReturn(false); + when(mService.isBatteryNotLow()).thenReturn(false); + when(signalStrength.getLevel()).thenReturn( + CellSignalStrength.SIGNAL_STRENGTH_GOOD); + signalStrengthsListener.onSignalStrengthsChanged(signalStrength); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(mService.isBatteryCharging()).thenReturn(false); + when(mService.isBatteryNotLow()).thenReturn(false); + when(signalStrength.getLevel()).thenReturn( + CellSignalStrength.SIGNAL_STRENGTH_GREAT); + signalStrengthsListener.onSignalStrengthsChanged(signalStrength); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + } + + @Test + public void testStrongEnough_Cellular_CheckDisabled() { + mConstants.CONN_USE_CELL_SIGNAL_STRENGTH = false; + mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = 0; + + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final TelephonyManager telephonyManager = mock(TelephonyManager.class); + when(mContext.getSystemService(TelephonyManager.class)) + .thenReturn(telephonyManager); + when(telephonyManager.createForSubscriptionId(anyInt())) + .thenReturn(telephonyManager); + final ArgumentCaptor<TelephonyCallback> signalStrengthsCaptor = + ArgumentCaptor.forClass(TelephonyCallback.class); + doNothing().when(telephonyManager) + .registerTelephonyCallback(any(), signalStrengthsCaptor.capture()); + final ArgumentCaptor<NetworkCallback> callbackCaptor = + ArgumentCaptor.forClass(NetworkCallback.class); + doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture()); + final Network net = mock(Network.class); + final NetworkCapabilities caps = createCapabilitiesBuilder() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_NOT_CONGESTED) + .setSubscriptionIds(Set.of(7357)) + .build(); + final JobInfo.Builder baseJobBuilder = createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), + DataUnit.MEBIBYTES.toBytes(1)) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); + final JobStatus jobExp = createJobStatus(baseJobBuilder.setExpedited(true)); + final JobStatus jobHigh = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_HIGH)); + final JobStatus jobDefEarly = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT), + now - 1000, now + 100_000); + final JobStatus jobDefLate = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT), + now - 100_000, now + 1000); + final JobStatus jobLow = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_LOW)); + final JobStatus jobMin = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN)); + final JobStatus jobMinRunner = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN)); + + final ConnectivityController controller = new ConnectivityController(mService); + + final NetworkCallback generalCallback = callbackCaptor.getValue(); + + when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L); + + when(mService.isBatteryCharging()).thenReturn(false); + when(mService.isBatteryNotLow()).thenReturn(false); + answerNetwork(generalCallback, null, null, net, caps); + + final TelephonyCallback.SignalStrengthsListener signalStrengthsListener = + (TelephonyCallback.SignalStrengthsListener) signalStrengthsCaptor.getValue(); + + controller.maybeStartTrackingJobLocked(jobMinRunner, null); + controller.prepareForExecutionLocked(jobMinRunner); + + final SignalStrength signalStrength = mock(SignalStrength.class); + + when(signalStrength.getLevel()).thenReturn( + CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN); + signalStrengthsListener.onSignalStrengthsChanged(signalStrength); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(signalStrength.getLevel()).thenReturn( + CellSignalStrength.SIGNAL_STRENGTH_POOR); + signalStrengthsListener.onSignalStrengthsChanged(signalStrength); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(mService.isBatteryCharging()).thenReturn(true); + when(mService.isBatteryNotLow()).thenReturn(false); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(mService.isBatteryCharging()).thenReturn(true); + when(mService.isBatteryNotLow()).thenReturn(true); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(mService.isBatteryCharging()).thenReturn(false); + when(mService.isBatteryNotLow()).thenReturn(false); + when(signalStrength.getLevel()).thenReturn( + CellSignalStrength.SIGNAL_STRENGTH_MODERATE); + signalStrengthsListener.onSignalStrengthsChanged(signalStrength); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(mService.isBatteryCharging()).thenReturn(false); + when(mService.isBatteryNotLow()).thenReturn(true); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(mService.isBatteryCharging()).thenReturn(true); + when(mService.isBatteryNotLow()).thenReturn(true); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(mService.isBatteryCharging()).thenReturn(false); + when(mService.isBatteryNotLow()).thenReturn(false); + when(signalStrength.getLevel()).thenReturn( + CellSignalStrength.SIGNAL_STRENGTH_GOOD); + signalStrengthsListener.onSignalStrengthsChanged(signalStrength); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + + when(mService.isBatteryCharging()).thenReturn(false); + when(mService.isBatteryNotLow()).thenReturn(false); + when(signalStrength.getLevel()).thenReturn( + CellSignalStrength.SIGNAL_STRENGTH_GREAT); + signalStrengthsListener.onSignalStrengthsChanged(signalStrength); + + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + } + + @Test + public void testStrongEnough_Cellular_VPN() { + mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = 0; + + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final TelephonyManager telephonyManager = mock(TelephonyManager.class); + when(mContext.getSystemService(TelephonyManager.class)) + .thenReturn(telephonyManager); + when(telephonyManager.createForSubscriptionId(anyInt())) + .thenReturn(telephonyManager); + final ArgumentCaptor<TelephonyCallback> signalStrengthsCaptor = + ArgumentCaptor.forClass(TelephonyCallback.class); + doNothing().when(telephonyManager) + .registerTelephonyCallback(any(), signalStrengthsCaptor.capture()); + final ArgumentCaptor<NetworkCallback> callbackCaptor = + ArgumentCaptor.forClass(NetworkCallback.class); + doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture()); + final Network net = mock(Network.class); + final NetworkCapabilities caps = createCapabilitiesBuilder() + .addTransportType(TRANSPORT_CELLULAR) + .addTransportType(TRANSPORT_VPN) + .addCapability(NET_CAPABILITY_NOT_CONGESTED) + .setSubscriptionIds(Set.of(7357)) + .build(); + final JobInfo.Builder baseJobBuilder = createJob() + .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), + DataUnit.MEBIBYTES.toBytes(1)) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); + final JobStatus jobExp = createJobStatus(baseJobBuilder.setExpedited(true)); + final JobStatus jobHigh = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_HIGH)); + final JobStatus jobDefEarly = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT), + now - 1000, now + 100_000); + final JobStatus jobDefLate = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT), + now - 100_000, now + 1000); + final JobStatus jobLow = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_LOW)); + final JobStatus jobMin = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN)); + final JobStatus jobMinRunner = createJobStatus( + baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN)); + + final ConnectivityController controller = new ConnectivityController(mService); + + final NetworkCallback generalCallback = callbackCaptor.getValue(); + + when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L); + + when(mService.isBatteryCharging()).thenReturn(false); + when(mService.isBatteryNotLow()).thenReturn(false); + answerNetwork(generalCallback, null, null, net, caps); + + final TelephonyCallback.SignalStrengthsListener signalStrengthsListener = + (TelephonyCallback.SignalStrengthsListener) signalStrengthsCaptor.getValue(); + + controller.maybeStartTrackingJobLocked(jobMinRunner, null); + controller.prepareForExecutionLocked(jobMinRunner); + + final SignalStrength signalStrength = mock(SignalStrength.class); + + when(signalStrength.getLevel()).thenReturn( + CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN); + signalStrengthsListener.onSignalStrengthsChanged(signalStrength); + + // We don't restrict data via VPN over cellular. + assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants)); + assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants)); + } + + @Test public void testRelaxed() throws Exception { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); final JobInfo.Builder job = createJob() diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java index 7d42a52f8427..0319d8d00397 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java @@ -43,6 +43,7 @@ import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.when; import android.app.job.JobInfo; @@ -137,7 +138,7 @@ public class JobStatusTest { .setOverrideDeadline(12) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .build(); - when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE); + when(mJobSchedulerInternal.getCloudMediaProviderPackage(eq(0))).thenReturn(TEST_PACKAGE); assertEffectiveBucketForMediaExemption(createJobStatus(triggerContentJob), false); } @@ -146,7 +147,7 @@ public class JobStatusTest { final JobInfo triggerContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT) .addTriggerContentUri(new JobInfo.TriggerContentUri(IMAGES_MEDIA_URI, 0)) .build(); - when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE); + when(mJobSchedulerInternal.getCloudMediaProviderPackage(eq(0))).thenReturn(TEST_PACKAGE); assertEffectiveBucketForMediaExemption(createJobStatus(triggerContentJob), false); } @@ -155,7 +156,7 @@ public class JobStatusTest { final JobInfo networkJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .build(); - when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE); + when(mJobSchedulerInternal.getCloudMediaProviderPackage(eq(0))).thenReturn(TEST_PACKAGE); assertEffectiveBucketForMediaExemption(createJobStatus(networkJob), false); } @@ -165,7 +166,8 @@ public class JobStatusTest { .addTriggerContentUri(new JobInfo.TriggerContentUri(IMAGES_MEDIA_URI, 0)) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .build(); - when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn("not.test.package"); + when(mJobSchedulerInternal.getCloudMediaProviderPackage(eq(0))) + .thenReturn("not.test.package"); assertEffectiveBucketForMediaExemption(createJobStatus(networkContentJob), false); } @@ -178,13 +180,25 @@ public class JobStatusTest { .addTriggerContentUri(new JobInfo.TriggerContentUri(nonEligibleUri, 0)) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .build(); - when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE); + when(mJobSchedulerInternal.getCloudMediaProviderPackage(eq(0))).thenReturn(TEST_PACKAGE); assertEffectiveBucketForMediaExemption(createJobStatus(networkContentJob), false); } @Test + public void testMediaBackupExemption_lowPriorityJobs() { + when(mJobSchedulerInternal.getCloudMediaProviderPackage(eq(0))).thenReturn(TEST_PACKAGE); + final JobInfo.Builder jobBuilder = new JobInfo.Builder(42, TEST_JOB_COMPONENT) + .addTriggerContentUri(new JobInfo.TriggerContentUri(IMAGES_MEDIA_URI, 0)) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); + assertEffectiveBucketForMediaExemption( + createJobStatus(jobBuilder.setPriority(JobInfo.PRIORITY_LOW).build()), false); + assertEffectiveBucketForMediaExemption( + createJobStatus(jobBuilder.setPriority(JobInfo.PRIORITY_MIN).build()), false); + } + + @Test public void testMediaBackupExemptionGranted() { - when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE); + when(mJobSchedulerInternal.getCloudMediaProviderPackage(eq(0))).thenReturn(TEST_PACKAGE); final JobInfo imageUriJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT) .addTriggerContentUri(new JobInfo.TriggerContentUri(IMAGES_MEDIA_URI, 0)) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index 40bd800a346a..a0ac50634a7b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -1794,6 +1794,98 @@ public class QuotaControllerTest { } } + /** + * Test getTimeUntilQuotaConsumedLocked when allowed time equals the bucket window size. + */ + @Test + public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (24 * HOUR_IN_MILLIS), + mQcConstants.MAX_EXECUTION_TIME_MS - 10 * MINUTE_IN_MILLIS, 5), + false); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), + false); + + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, + 10 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 10 * MINUTE_IN_MILLIS); + // window size = allowed time, so jobs can essentially run non-stop until they reach the + // max execution time. + setStandbyBucket(EXEMPTED_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(0, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 10 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); + } + } + + /** + * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket + * window and the session is rolling out of the window. + */ + @Test + public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_BucketWindow() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (24 * HOUR_IN_MILLIS), + 10 * MINUTE_IN_MILLIS, 5), false); + setStandbyBucket(RARE_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(0, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(10 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); + } + + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (8 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); + setStandbyBucket(FREQUENT_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(0, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(10 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); + } + + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (2 * HOUR_IN_MILLIS), + 10 * MINUTE_IN_MILLIS, 5), false); + setStandbyBucket(WORKING_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(0, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(10 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); + } + + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), + false); + // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the + // max execution time. + setStandbyBucket(ACTIVE_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(0, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 30 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT)); + } + } + @Test public void testIsWithinQuotaLocked_NeverApp() { synchronized (mQuotaController.mLock) { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt index b89f36fd1c76..f35986b178d8 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt @@ -230,7 +230,7 @@ class SharedLibrariesImplTest { val pair = createBasicAndroidPackage(STATIC_LIB_PACKAGE_NAME + "_" + 10, 10L, staticLibrary = STATIC_LIB_NAME, staticLibraryVersion = 10L) val parsedPackage = pair.second as ParsedPackage - val scanRequest = ScanRequest(parsedPackage, null, null, null, + val scanRequest = ScanRequest(parsedPackage, null, null, null, null, null, null, null, 0, 0, false, null, null) val scanResult = ScanResult(scanRequest, true, null, null, false, 0, null, null, null) @@ -329,7 +329,7 @@ class SharedLibrariesImplTest { val packageSetting = mRule.system() .createBasicSettingBuilder(pair.first.parentFile, parsedPackage.hideAsFinal()) .setPkgFlags(ApplicationInfo.FLAG_SYSTEM).build() - val scanRequest = ScanRequest(parsedPackage, null, null, null, + val scanRequest = ScanRequest(parsedPackage, null, null, null, null, null, null, null, 0, 0, false, null, null) val scanResult = ScanResult(scanRequest, true, packageSetting, null, false, 0, null, null, listOf(testInfo)) diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index 0b488b2add8e..5fb3a4ef8d90 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -33,6 +33,7 @@ import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER_CRO import static org.hamcrest.core.IsNot.not; 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; @@ -445,6 +446,48 @@ public class WallpaperManagerServiceTests { mService.getWallpaperDimAmount(), dimAmount, 0.0); } + @Test + public void testGetAdjustedWallpaperColorsOnDimming() throws RemoteException { + final int testUserId = USER_SYSTEM; + mService.switchUser(testUserId, null); + mService.setWallpaperComponent(sDefaultWallpaperComponent); + WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, testUserId); + + // Mock a wallpaper data with color hints that support dark text and dark theme + // but not HINT_FROM_BITMAP + wallpaper.primaryColors = new WallpaperColors(Color.valueOf(Color.WHITE), null, null, + WallpaperColors.HINT_SUPPORTS_DARK_TEXT | WallpaperColors.HINT_SUPPORTS_DARK_THEME); + mService.setWallpaperDimAmount(0.6f); + int colorHints = mService.getAdjustedWallpaperColorsOnDimming(wallpaper).getColorHints(); + // Dimmed wallpaper not extracted from bitmap does not support dark text and dark theme + assertNotEquals(WallpaperColors.HINT_SUPPORTS_DARK_TEXT, + colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT); + assertNotEquals(WallpaperColors.HINT_SUPPORTS_DARK_THEME, + colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME); + + // Remove dimming + mService.setWallpaperDimAmount(0f); + colorHints = mService.getAdjustedWallpaperColorsOnDimming(wallpaper).getColorHints(); + // Undimmed wallpaper not extracted from bitmap does support dark text and dark theme + assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_TEXT, + colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT); + assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_THEME, + colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME); + + // Mock a wallpaper data with color hints that support dark text and dark theme + // and was extracted from bitmap + wallpaper.primaryColors = new WallpaperColors(Color.valueOf(Color.WHITE), null, null, + WallpaperColors.HINT_SUPPORTS_DARK_TEXT | WallpaperColors.HINT_SUPPORTS_DARK_THEME + | WallpaperColors.HINT_FROM_BITMAP); + mService.setWallpaperDimAmount(0.6f); + colorHints = mService.getAdjustedWallpaperColorsOnDimming(wallpaper).getColorHints(); + // Dimmed wallpaper should still support dark text and dark theme + assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_TEXT, + colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT); + assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_THEME, + colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME); + } + // Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for // non-current user must not bind to wallpaper service. private void verifyNoConnectionBeforeLastUser(int lastUserId) { diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index d9f73d9aa54e..53cab9ed80cf 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -130,6 +130,19 @@ android:resource="@xml/test_account_type2_authenticator"/> </service> + <service + android:name="com.android.server.dreams.TestDreamService" + android:exported="false" + android:label="Test Dream" > + <intent-filter> + <action android:name="android.service.dreams.DreamService" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + <meta-data + android:name="android.service.dream" + android:resource="@xml/test_dream_metadata" /> + </service> + <receiver android:name="com.android.server.devicepolicy.ApplicationRestrictionsTest$AdminReceiver" android:permission="android.permission.BIND_DEVICE_ADMIN" android:exported="true"> diff --git a/services/tests/servicestests/res/xml/test_dream_metadata.xml b/services/tests/servicestests/res/xml/test_dream_metadata.xml new file mode 100644 index 000000000000..aa054f1e9fa4 --- /dev/null +++ b/services/tests/servicestests/res/xml/test_dream_metadata.xml @@ -0,0 +1,19 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<dream xmlns:android="http://schemas.android.com/apk/res/android" + android:settingsActivity="com.android.server.dreams/.TestDreamSettingsActivity" + android:showClockAndComplications="false" /> diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java deleted file mode 100644 index 489e2f769a3d..000000000000 --- a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import android.test.AndroidTestCase; - -/** - * Tests for {@link com.android.server.BootReceiver} - */ -public class BootReceiverTest extends AndroidTestCase { - public void testLogLinePotentiallySensitive() throws Exception { - /* - * Strings to be dropped from the log as potentially sensitive: register dumps, process - * names, hardware info. - */ - final String[] becomeNull = { - "CPU: 4 PID: 120 Comm: kunit_try_catch Tainted: G W 5.8.0-rc6+ #7", - "Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1 04/01/2014", - "[ 0.083207] RSP: 0000:ffffffff8fe07ca8 EFLAGS: 00010046 ORIG_RAX: 0000000000000000", - "[ 0.084709] RAX: 0000000000000000 RBX: ffffffffff240000 RCX: ffffffff815fcf01", - "[ 0.086109] RDX: dffffc0000000000 RSI: 0000000000000001 RDI: ffffffffff240004", - "[ 0.087509] RBP: ffffffff8fe07d60 R08: fffffbfff1fc0f21 R09: fffffbfff1fc0f21", - "[ 0.088911] R10: ffffffff8fe07907 R11: fffffbfff1fc0f20 R12: ffffffff8fe07d38", - "R13: 0000000000000001 R14: 0000000000000001 R15: ffffffff8fe07e80", - "x29: ffff00003ce07150 x28: ffff80001aa29cc0", - "x1 : 0000000000000000 x0 : ffff00000f628000", - }; - - /* Strings to be left unchanged, including non-sensitive registers and parts of reports. */ - final String[] leftAsIs = { - "FS: 0000000000000000(0000) GS:ffffffff92409000(0000) knlGS:0000000000000000", - "[ 69.2366] [ T6006]c7 6006 =======================================================", - "[ 69.245688] [ T6006] BUG: KFENCE: out-of-bounds in kfence_handle_page_fault", - "[ 69.257816] [ T6006]c7 6006 Out-of-bounds access at 0xffffffca75c45000 ", - "[ 69.273536] [ T6006]c7 6006 __do_kernel_fault+0xa8/0x11c", - "pc : __mutex_lock+0x428/0x99c ", - "sp : ffff00003ce07150", - "Call trace:", - "", - }; - - final String[][] stripped = { - { "Detected corrupted memory at 0xffffffffb6797ff9 [ 0xac . . . . . . ]:", - "Detected corrupted memory at 0xffffffffb6797ff9" }, - }; - for (int i = 0; i < becomeNull.length; i++) { - assertEquals(BootReceiver.stripSensitiveData(becomeNull[i]), null); - } - - for (int i = 0; i < leftAsIs.length; i++) { - assertEquals(BootReceiver.stripSensitiveData(leftAsIs[i]), leftAsIs[i]); - } - - for (int i = 0; i < stripped.length; i++) { - assertEquals(BootReceiver.stripSensitiveData(stripped[i][0]), stripped[i][1]); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS index 6a7d298514c5..68994e6b5a6f 100644 --- a/services/tests/servicestests/src/com/android/server/OWNERS +++ b/services/tests/servicestests/src/com/android/server/OWNERS @@ -1,6 +1,6 @@ per-file *Alarm* = file:/apex/jobscheduler/OWNERS per-file *AppOp* = file:/core/java/android/permission/OWNERS -per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS +per-file *Bluetooth* = file:platform/packages/modules/Bluetooth:master:/framework/java/android/bluetooth/OWNERS per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS per-file BatteryServiceTest.java = file:platform/hardware/interfaces:/health/aidl/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java index 7179c60f7d8d..022c137491df 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java @@ -18,8 +18,12 @@ package com.android.server.accessibility; import static android.view.accessibility.AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; -import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS; +import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS; +import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST; +import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST; +import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID; import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS; +import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE; import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID; import static org.junit.Assert.assertEquals; @@ -74,24 +78,37 @@ public class AccessibilityInteractionControllerNodeRequestsTest { @Captor private ArgumentCaptor<AccessibilityNodeInfo> mFindInfoCaptor; + @Captor + private ArgumentCaptor<List<AccessibilityNodeInfo>> mFindInfosCaptor; @Captor private ArgumentCaptor<List<AccessibilityNodeInfo>> mPrefetchInfoListCaptor; private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); private static final int MOCK_CLIENT_1_THREAD_AND_PROCESS_ID = 1; private static final int MOCK_CLIENT_2_THREAD_AND_PROCESS_ID = 2; - private static final String FRAME_LAYOUT_DESCRIPTION = "frameLayout"; + private static final String ROOT_FRAME_LAYOUT_DESCRIPTION = "rootFrameLayout"; private static final String TEXT_VIEW_1_DESCRIPTION = "textView1"; private static final String TEXT_VIEW_2_DESCRIPTION = "textView2"; - - private TestFrameLayout mFrameLayout; + private static final String CHILD_FRAME_DESCRIPTION = "childFrameLayout"; + private static final String TEXT_VIEW_3_DESCRIPTION = "textView3"; + private static final String TEXT_VIEW_4_DESCRIPTION = "textView4"; + private static final String VIRTUAL_VIEW_1_DESCRIPTION = "virtual descendant 1"; + private static final String VIRTUAL_VIEW_2_DESCRIPTION = "virtual descendant 2"; + private static final String VIRTUAL_VIEW_3_DESCRIPTION = "virtual descendant 3"; + + private TestFrameLayout mRootFrameLayout; + private TestFrameLayout mChildFrameLayout; private TestTextView mTextView1; - private TestTextView2 mTextView2; + private TestTextView mTextView2; + private TestTextView mTextView3; + private TestTextView mTextView4; private boolean mSendClient1RequestForTextAfterTextPrefetched; private boolean mSendClient2RequestForTextAfterTextPrefetched; private boolean mSendRequestForTextAndIncludeUnImportantViews; private boolean mSendClient1RequestForRootAfterTextPrefetched; + private boolean mSendClient2RequestForTextAfterRootPrefetched; + private int mMockClient1InteractionId; private int mMockClient2InteractionId; @@ -103,23 +120,45 @@ public class AccessibilityInteractionControllerNodeRequestsTest { final Context context = mInstrumentation.getTargetContext(); final ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay()); - mFrameLayout = new TestFrameLayout(context); - mTextView1 = new TestTextView(context); - mTextView2 = new TestTextView2(context); - - mFrameLayout.addView(mTextView1); - mFrameLayout.addView(mTextView2); + mTextView1 = new TestTextView(context, 1, TEXT_VIEW_1_DESCRIPTION); + mTextView2 = new TestTextView(context, 2, TEXT_VIEW_2_DESCRIPTION); + mTextView3 = new TestTextView(context, 4, TEXT_VIEW_3_DESCRIPTION); + mTextView4 = new TestTextView(context, 5, TEXT_VIEW_4_DESCRIPTION); + + mChildFrameLayout = new TestFrameLayout(context, 3, + CHILD_FRAME_DESCRIPTION, new ArrayList<>(List.of(mTextView4))); + mRootFrameLayout = new TestFrameLayout(context, 0, ROOT_FRAME_LAYOUT_DESCRIPTION, + new ArrayList<>( + List.of(mTextView1, mTextView2, mChildFrameLayout, mTextView3))); + + mRootFrameLayout.addView(mTextView1); + mRootFrameLayout.addView(mTextView2); + mChildFrameLayout.addView(mTextView4); + mRootFrameLayout.addView(mChildFrameLayout); + mRootFrameLayout.addView(mTextView3); + + // Layout + // mRootFrameLayout + // / | | \ + // mTextView1 mTextView2 mChildFrameLayout mTextView3 + // | + // mTextView4 // The controller retrieves views through this manager, and registration happens on - // when attached to a window, which we don't have. We can simply reference FrameLayout - // with ROOT_NODE_ID + // when attached to a window, which we don't have. We can simply reference + // RootFrameLayout with ROOT_NODE_ID. AccessibilityNodeIdManager.getInstance().registerViewWithId( mTextView1, mTextView1.getAccessibilityViewId()); AccessibilityNodeIdManager.getInstance().registerViewWithId( mTextView2, mTextView2.getAccessibilityViewId()); - + AccessibilityNodeIdManager.getInstance().registerViewWithId( + mTextView3, mTextView3.getAccessibilityViewId()); + AccessibilityNodeIdManager.getInstance().registerViewWithId( + mChildFrameLayout, mChildFrameLayout.getAccessibilityViewId()); + AccessibilityNodeIdManager.getInstance().registerViewWithId( + mTextView4, mTextView4.getAccessibilityViewId()); try { - viewRootImpl.setView(mFrameLayout, new WindowManager.LayoutParams(), null); + viewRootImpl.setView(mRootFrameLayout, new WindowManager.LayoutParams(), null); } catch (WindowManager.BadTokenException e) { // activity isn't running, we will ignore BadTokenException. @@ -137,66 +176,79 @@ public class AccessibilityInteractionControllerNodeRequestsTest { mTextView1.getAccessibilityViewId()); AccessibilityNodeIdManager.getInstance().unregisterViewWithId( mTextView2.getAccessibilityViewId()); + AccessibilityNodeIdManager.getInstance().unregisterViewWithId( + mTextView3.getAccessibilityViewId()); + AccessibilityNodeIdManager.getInstance().unregisterViewWithId( + mTextView4.getAccessibilityViewId()); + AccessibilityNodeIdManager.getInstance().unregisterViewWithId( + mChildFrameLayout.getAccessibilityViewId()); } /** * Tests a basic request for the root node with prefetch flag - * {@link AccessibilityNodeInfo#FLAG_PREFETCH_DESCENDANTS} + * {@link AccessibilityNodeInfo#FLAG_PREFETCH_DESCENDANTS_HYBRID} * * @throws RemoteException */ @Test public void testFindRootView_withOneClient_shouldReturnRootNodeAndPrefetchDescendants() throws RemoteException { - // Request for our FrameLayout + // Request for our RootFrameLayout. sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); + mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); mInstrumentation.waitForIdleSync(); - // Verify we get FrameLayout + // Verify we get RootFrameLayout. verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); - assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); + assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); - // The descendants are our two TextViews + // The descendants are RootFrameLayout's 5 descendants. List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); - assertEquals(2, prefetchedNodes.size()); + assertEquals(5, prefetchedNodes.size()); assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription()); - + assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription()); + assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription()); + assertEquals(TEXT_VIEW_4_DESCRIPTION, prefetchedNodes.get(4).getContentDescription()); } /** - * Tests a basic request for TestTextView1's node with prefetch flag - * {@link AccessibilityNodeInfo#FLAG_PREFETCH_SIBLINGS} + * Tests a basic request for TextView1's node with prefetch flag. + * {@link AccessibilityNodeInfo#FLAG_PREFETCH_SIBLINGS} and + * {@link AccessibilityNodeInfo#FLAG_PREFETCH_ANCESTORS}. * * @throws RemoteException */ @Test - public void testFindTextView_withOneClient_shouldReturnNodeAndPrefetchedSiblings() + public void testFindTextView_withOneClient_shouldReturnNodeAndPrefetchedSiblingsAndParent() throws RemoteException { - // Request for TextView1 + // Request for TextView1. sendNodeRequestToController(AccessibilityNodeInfo.makeNodeId( mTextView1.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID), - mMockClientCallback1, mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS); + mMockClientCallback1, mMockClient1InteractionId, + FLAG_PREFETCH_SIBLINGS | FLAG_PREFETCH_ANCESTORS); mInstrumentation.waitForIdleSync(); - // Verify we get TextView1 + // Verify we get TextView1. verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription()); - // Verify the prefetched sibling of TextView1 is TextView2 + // Verify the prefetched sibling of TextView1 is TextView2, ChildFrameLayout, and TextView3. + // The predecessor is RootFrameLayout. verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); - // TextView2 is the prefetched sibling List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); - assertEquals(1, prefetchedNodes.size()); - assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); + assertEquals(4, prefetchedNodes.size()); + assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); + assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription()); + assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription()); + assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription()); } /** @@ -213,7 +265,8 @@ public class AccessibilityInteractionControllerNodeRequestsTest { @Test public void testFindRootAndTextNodes_withTwoClients_shouldPreventClient1Prefetch() throws RemoteException { - mFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate() { + mSendClient2RequestForTextAfterRootPrefetched = true; + mRootFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate() { @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(host, info); @@ -221,41 +274,50 @@ public class AccessibilityInteractionControllerNodeRequestsTest { mTextView1.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID); - // Enqueue a request when this node is found from a different service for - // TextView1 + if (mSendClient2RequestForTextAfterRootPrefetched) { + mSendClient2RequestForTextAfterRootPrefetched = false; + + // Enqueue a request when this node is found from client 2 for TextView1. sendNodeRequestToController(nodeId, mMockClientCallback2, - mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS); + mMockClient2InteractionId, + FLAG_PREFETCH_SIBLINGS); + } } }); - // Client 1 request for FrameLayout + // Client 1 request for RootFrameLayout. sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); + mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); mInstrumentation.waitForIdleSync(); - // Verify client 1 gets FrameLayout + // Verify client 1 gets RootFrameLayout. verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); - assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); + assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); - // The second request is put in the queue in the FrameLayout's onInitializeA11yNodeInfo, - // meaning prefetching is interrupted and does not even begin for the first request + // The second request is put in the queue in the RootFrameLayout's onInitializeA11yNodeInfo, + // meaning prefetching is does not occur for first request. verify(mMockClientCallback1, never()) .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt()); - // Verify client 2 gets TextView1 + // Verify client 2 gets TextView1. verify(mMockClientCallback2).setFindAccessibilityNodeInfoResult( mFindInfoCaptor.capture(), eq(mMockClient2InteractionId)); infoSentToService = mFindInfoCaptor.getValue(); assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription()); - // Verify the prefetched sibling of TextView1 is TextView2 (FLAG_PREFETCH_SIBLINGS) + // Verify the prefetched sibling of TextView1 is TextView2 and ChildFrameLayout + // (FLAG_PREFETCH_SIBLINGS). The parent, RootFrameLayout, is also retrieved. Since + // predecessors takes priority over siblings, RootFrameLayout is the first node in the list. verify(mMockClientCallback2).setPrefetchAccessibilityNodeInfoResult( mPrefetchInfoListCaptor.capture(), eq(mMockClient2InteractionId)); List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); - assertEquals(1, prefetchedNodes.size()); - assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); + assertEquals(4, prefetchedNodes.size()); + assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); + assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription()); + assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription()); + assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription()); } /** @@ -282,43 +344,43 @@ public class AccessibilityInteractionControllerNodeRequestsTest { @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(host, info); - info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); final long nodeId = AccessibilityNodeInfo.makeNodeId( mTextView1.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID); if (mSendClient1RequestForTextAfterTextPrefetched) { - // Prevent a loop when processing second request + // Prevent a loop when processing this node's second request. mSendClient1RequestForTextAfterTextPrefetched = false; - // TextView1 is prefetched here after the FrameLayout is found. Now enqueue a - // same-client request for TextView1 + // TextView1 is prefetched here after the RootFrameLayout is found. Now enqueue + // a same-client request for TextView1. sendNodeRequestToController(nodeId, mMockClientCallback1, - ++mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS); + ++mMockClient1InteractionId, + FLAG_PREFETCH_SIBLINGS | FLAG_PREFETCH_ANCESTORS); } } }); - // Client 1 requests FrameLayout + // Client 1 requests RootFrameLayout. sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); + mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); - // Flush out all messages + // Flush out all messages. mInstrumentation.waitForIdleSync(); - // When TextView1 is prefetched for FrameLayout, we put a message on the queue in - // TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus get + // When TextView1 is prefetched for RootFrameLayout, we put a message on the queue in + // TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus gets // two node results for FrameLayout and TextView1. verify(mMockClientCallback1, times(2)) .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); List<AccessibilityNodeInfo> foundNodes = mFindInfoCaptor.getAllValues(); - assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription()); + assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription()); assertEquals(TEXT_VIEW_1_DESCRIPTION, foundNodes.get(1).getContentDescription()); - // The controller will look at FrameLayout's prefetched nodes and find matching nodes in - // pending requests. The prefetched TextView1 matches the second request. This is removed - // from the first request's prefetch list, which is now empty. The second - // request was removed from queue and prefetching for this request never occurred. + // The controller will look at RootFrameLayout's prefetched nodes and find matching nodes in + // pending requests. The prefetched TextView1 satisfied the second request. This is removed + // from the first request's prefetch list, which is now empty. The second request is removed + // from queue. verify(mMockClientCallback1, never()) .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId - 1)); @@ -347,42 +409,41 @@ public class AccessibilityInteractionControllerNodeRequestsTest { @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(host, info); - info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); final long nodeId = AccessibilityNodeInfo.makeNodeId( - mFrameLayout.getAccessibilityViewId(), + mRootFrameLayout.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID); if (mSendClient1RequestForRootAfterTextPrefetched) { - // Prevent a loop when processing second request + // Prevent a loop when processing this node's second request. mSendClient1RequestForRootAfterTextPrefetched = false; // TextView1 is prefetched here after the FrameLayout is found. Now enqueue a - // same-client request for FrameLayout + // same-client request for FrameLayout. sendNodeRequestToController(nodeId, mMockClientCallback1, - ++mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); + ++mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); } } }); - // Client 1 requests FrameLayout + // Client 1 requests RootFrameLayout. sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); + mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); - // Flush out all messages + // Flush out all messages. mInstrumentation.waitForIdleSync(); - // When TextView1 is prefetched for FrameLayout, we put a message on the queue in + // When TextView1 is prefetched for RootFrameLayout, we put a message on the queue in // TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus gets // two node results for FrameLayout and TextView1. verify(mMockClientCallback1, times(2)) .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); List<AccessibilityNodeInfo> foundNodes = mFindInfoCaptor.getAllValues(); - assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription()); - assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(1).getContentDescription()); + assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription()); + assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, foundNodes.get(1).getContentDescription()); - // The controller will look at FrameLayout's prefetched nodes and find matching nodes in + // The controller will look at RootFrameLayout's prefetched nodes and find matching nodes in // pending requests. The first requested node (FrameLayout) is also checked, and this - // satifies the second request. The second request is removed from queue and prefetching + // satisfies the second request. The second request is removed from queue and prefetching // for this request never occurs. verify(mMockClientCallback1, times(1)) .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), @@ -406,7 +467,6 @@ public class AccessibilityInteractionControllerNodeRequestsTest { @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(host, info); - info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); final long nodeId = AccessibilityNodeInfo.makeNodeId( mTextView1.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID); @@ -414,30 +474,30 @@ public class AccessibilityInteractionControllerNodeRequestsTest { if (mSendClient2RequestForTextAfterTextPrefetched) { mSendClient2RequestForTextAfterTextPrefetched = false; // TextView1 is prefetched here. Now enqueue client 2's request for - // TextView1 + // TextView1. sendNodeRequestToController(nodeId, mMockClientCallback2, mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS); } } }); - // Client 1 requests FrameLayout + // Client 1 requests RootFrameLayout. sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); + mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); mInstrumentation.waitForIdleSync(); - // Verify client 1 gets FrameLayout + // Verify client 1 gets RootFrameLayout. verify(mMockClientCallback1, times(1)) .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); - assertEquals(FRAME_LAYOUT_DESCRIPTION, + assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, mFindInfoCaptor.getValue().getContentDescription()); - // Verify client 1 doesn't have prefetched nodes + // Verify client 1 doesn't have prefetched nodes. verify(mMockClientCallback1, never()) .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); - // Verify client 2 gets TextView1 + // Verify client 2 gets TextView1. verify(mMockClientCallback2, times(1)) .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); @@ -458,7 +518,6 @@ public class AccessibilityInteractionControllerNodeRequestsTest { @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(host, info); - info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); final long nodeId = AccessibilityNodeInfo.makeNodeId( mTextView1.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID); @@ -466,17 +525,18 @@ public class AccessibilityInteractionControllerNodeRequestsTest { if (mSendRequestForTextAndIncludeUnImportantViews) { mSendRequestForTextAndIncludeUnImportantViews = false; // TextView1 is prefetched here for client 1. Now enqueue a request from a - // different client that holds different fetch flags for TextView1 + // different client that holds different fetch flags for TextView1. sendNodeRequestToController(nodeId, mMockClientCallback2, mMockClient2InteractionId, - FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS); + FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS + | FLAG_PREFETCH_ANCESTORS); } } }); // Mockito does not make copies of objects when called. It holds references, so // the captor would point to client 2's results after all requests are processed. Verify - // prefetched node immediately + // prefetched node immediately. doAnswer(invocation -> { List<AccessibilityNodeInfo> prefetched = invocation.getArgument(0); assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetched.get(0).getContentDescription()); @@ -484,39 +544,290 @@ public class AccessibilityInteractionControllerNodeRequestsTest { }).when(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(anyList(), eq(mMockClient1InteractionId)); - // Client 1 requests FrameLayout + // Client 1 requests RootFrameLayout. sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); + mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); mInstrumentation.waitForIdleSync(); - // Verify client 1 gets FrameLayout + // Verify client 1 gets RootFrameLayout. verify(mMockClientCallback1, times(1)) .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); - assertEquals(FRAME_LAYOUT_DESCRIPTION, + assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, mFindInfoCaptor.getValue().getContentDescription()); // Verify client 1 has prefetched results. The only prefetched node is TextView1 - // (from above doAnswer) + // (from above doAnswer). verify(mMockClientCallback1, times(1)) .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); - // Verify client 2 gets TextView1 + // Verify client 2 gets TextView1. verify(mMockClientCallback2, times(1)) .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), eq(mMockClient2InteractionId)); assertEquals(TEXT_VIEW_1_DESCRIPTION, mFindInfoCaptor.getValue().getContentDescription()); - // Verify client 2 has TextView2 as a prefetched node + // Verify client 2 gets TextView1's siblings and its parent as prefetched nodes. verify(mMockClientCallback2, times(1)) .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), eq(mMockClient2InteractionId)); List<AccessibilityNodeInfo> prefetchedNode = mPrefetchInfoListCaptor.getValue(); - assertEquals(1, prefetchedNode.size()); - assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNode.get(0).getContentDescription()); + assertEquals(4, prefetchedNode.size()); + assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, prefetchedNode.get(0).getContentDescription()); + assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNode.get(1).getContentDescription()); + assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNode.get(2).getContentDescription()); + assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNode.get(3).getContentDescription()); + } + + /** + * Tests a request for 4 nodes using depth first traversal. + * Request 1: Request the root node. + * Request 2: When TextView4 is prefetched, send a request for the root node. Depth first + * traversal completes here. + * Out of the 5 descendants, the root frame's 3rd child (TextView3) should not be prefetched, + * since this was not reached by the df-traversal. + * + * Layout + * mRootFrameLayout + * / | | \ + * mTextView1 mTextView2 mChildFrameLayout *mTextView3* + * | + * mTextView4 + * @throws RemoteException + */ + @Test + public void testFindRootView_depthFirstStrategy_shouldReturnRootNodeAndPrefetch4Descendants() + throws RemoteException { + mTextView4.setAccessibilityDelegate(new View.AccessibilityDelegate(){ + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + final long nodeId = AccessibilityNodeInfo.makeNodeId( + mRootFrameLayout.getAccessibilityViewId(), + AccessibilityNodeProvider.HOST_VIEW_ID); + // This request is satisfied by first request. + sendNodeRequestToController(nodeId, mMockClientCallback2, + mMockClient2InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); + + } + }); + // Request for our RootFrameLayout. + sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, + mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST); + mInstrumentation.waitForIdleSync(); + + // Verify we get RootFrameLayout. + verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( + mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); + AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); + assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); + + verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( + mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); + // Prefetch all the descendants besides TextView3. + List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); + assertEquals(4, prefetchedNodes.size()); + assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); + assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription()); + assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription()); + assertEquals(TEXT_VIEW_4_DESCRIPTION, prefetchedNodes.get(3).getContentDescription()); + } + + /** + * Tests a request for 4 nodes using breadth first traversal. + * Request 1: Request the root node + * Request 2: When TextView3 is prefetched, send a request for the root node. Breadth first + * traversal completes here. + * Out of the 5 descendants, the child frame's child (TextView4) should not be prefetched, since + * this was not reached by the bf-traversal. + * Layout + * mRootFrameLayout + * / | | \ + * mTextView1 mTextView2 mChildFrameLayout *mTextView3* + * | + * *mTextView4* + * @throws RemoteException + */ + @Test + public void testFindRootView_breadthFirstStrategy_shouldReturnRootNodeAndPrefetch4Descendants() + throws RemoteException { + + mTextView3.setAccessibilityDelegate(new View.AccessibilityDelegate(){ + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + final long nodeId = AccessibilityNodeInfo.makeNodeId( + mRootFrameLayout.getAccessibilityViewId(), + AccessibilityNodeProvider.HOST_VIEW_ID); + + // This request is satisfied by first request. + sendNodeRequestToController(nodeId, mMockClientCallback2, + mMockClient2InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); + } + }); + // Request for our RootFrameLayout. + sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, + mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST); + mInstrumentation.waitForIdleSync(); + + // Verify we get RootFrameLayout. + verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( + mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); + AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); + assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); + + verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( + mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); + // Prefetch all the descendants besides TextView4. + List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); + assertEquals(4, prefetchedNodes.size()); + assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); + assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription()); + assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription()); + assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(3).getContentDescription()); + } + + /** + * Tests a request that should not have prefetching interrupted. + * Request 1: Client 1 requests the root node + * Request 2: When the root node is initialized in + * {@link TestFrameLayout#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)}, + * Client 2 requests TextView1's node + * + * Request 1 is not interrupted during prefetch, and its prefetched node satisfies Request 2. + * + * @throws RemoteException + */ + @Test + public void testFindRootAndTextNodes_withNoInterruptStrategy_shouldSatisfySecondRequest() + throws RemoteException { + mRootFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate(){ + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + final long nodeId = AccessibilityNodeInfo.makeNodeId( + mTextView1.getAccessibilityViewId(), + AccessibilityNodeProvider.HOST_VIEW_ID); + + // TextView1 is prefetched here after the RootFrameLayout is found. Now enqueue a + // same-client request for RootFrameLayout. + sendNodeRequestToController(nodeId, mMockClientCallback2, + mMockClient2InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID); + } + }); + + // Client 1 request for RootFrameLayout. + sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, + mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_HYBRID + | FLAG_PREFETCH_UNINTERRUPTIBLE); + + mInstrumentation.waitForIdleSync(); + + // When the controller returns the nodes, it clears the sent list. Check immediately since + // the captor will be cleared. + doAnswer(invocation -> { + List<AccessibilityNodeInfo> nodes = invocation.getArgument(0); + assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, nodes.get(0).getContentDescription()); + assertEquals(TEXT_VIEW_2_DESCRIPTION, nodes.get(1).getContentDescription()); + assertEquals(CHILD_FRAME_DESCRIPTION, nodes.get(2).getContentDescription()); + assertEquals(TEXT_VIEW_3_DESCRIPTION, nodes.get(3).getContentDescription()); + assertEquals(TEXT_VIEW_4_DESCRIPTION, nodes.get(4).getContentDescription()); + return null; + }).when(mMockClientCallback1).setFindAccessibilityNodeInfosResult( + anyList(), eq(mMockClient1InteractionId)); + + verify(mMockClientCallback1, never()) + .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt()); + + // Verify client 2 gets TextView1. + verify(mMockClientCallback2).setFindAccessibilityNodeInfoResult( + mFindInfoCaptor.capture(), eq(mMockClient2InteractionId)); + AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); + assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription()); + } + + /** + * Tests a request for root node where a virtual hierarchy is prefetched. + * + * Layout + * mRootFrameLayout + * / | | \ + * mTextView1 mTextView2 mChildFrameLayout *mTextView3* + * | + * *mTextView4* + * | \ + * virtual view 1 virtual view 2 + * | + * virtual view 3 + * @throws RemoteException + */ + @Test + public void testFindRootView_withVirtualView() + throws RemoteException { + mTextView4.setAccessibilityDelegate(new View.AccessibilityDelegate(){ + @Override + public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) { + return new AccessibilityNodeProvider() { + @Override + public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { + if (virtualViewId == AccessibilityNodeProvider.HOST_VIEW_ID) { + AccessibilityNodeInfo node = new AccessibilityNodeInfo(host); + node.addChild(host, 1); + node.addChild(host, 2); + node.setContentDescription(TEXT_VIEW_4_DESCRIPTION); + return node; + } else if (virtualViewId == 1) { + AccessibilityNodeInfo node = new AccessibilityNodeInfo( + host, virtualViewId); + node.setParent(host); + node.setContentDescription(VIRTUAL_VIEW_1_DESCRIPTION); + node.addChild(host, 3); + return node; + } else if (virtualViewId == 2 || virtualViewId == 3) { + AccessibilityNodeInfo node = new AccessibilityNodeInfo( + host, virtualViewId); + node.setParent(host); + node.setContentDescription(virtualViewId == 2 + ? VIRTUAL_VIEW_2_DESCRIPTION + : VIRTUAL_VIEW_3_DESCRIPTION); + return node; + } + return null; + } + }; + } + }); + // Request for our RootFrameLayout. + sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, + mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST); + mInstrumentation.waitForIdleSync(); + + // Verify we get RootFrameLayout. + verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( + mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); + AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); + assertEquals(ROOT_FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); + + verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( + mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); + + List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); + assertEquals(8, prefetchedNodes.size()); + assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); + assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription()); + assertEquals(CHILD_FRAME_DESCRIPTION, prefetchedNodes.get(2).getContentDescription()); + assertEquals(TEXT_VIEW_4_DESCRIPTION, prefetchedNodes.get(3).getContentDescription()); + + assertEquals(VIRTUAL_VIEW_1_DESCRIPTION, prefetchedNodes.get(4).getContentDescription()); + assertEquals(VIRTUAL_VIEW_3_DESCRIPTION, prefetchedNodes.get(5).getContentDescription()); + + assertEquals(VIRTUAL_VIEW_2_DESCRIPTION, prefetchedNodes.get(6).getContentDescription()); + assertEquals(TEXT_VIEW_3_DESCRIPTION, prefetchedNodes.get(7).getContentDescription()); + + } private void sendNodeRequestToController(long requestedNodeId, @@ -536,9 +847,16 @@ public class AccessibilityInteractionControllerNodeRequestsTest { } private class TestFrameLayout extends FrameLayout { + private int mA11yId; + private String mContentDescription; + ArrayList<View> mChildren; - TestFrameLayout(Context context) { + TestFrameLayout(Context context, int a11yId, String contentDescription, + ArrayList<View> children) { super(context); + mA11yId = a11yId; + mContentDescription = contentDescription; + mChildren = children; } @Override @@ -556,14 +874,15 @@ public class AccessibilityInteractionControllerNodeRequestsTest { @Override public int getAccessibilityViewId() { // static id doesn't reset after tests so return the same one - return 0; + return mA11yId; } @Override public void addChildrenForAccessibility(ArrayList<View> outChildren) { // ViewGroup#addChildrenForAccessbility sorting logic will switch these two - outChildren.add(mTextView1); - outChildren.add(mTextView2); + for (View view : mChildren) { + outChildren.add(view); + } } @Override @@ -574,45 +893,17 @@ public class AccessibilityInteractionControllerNodeRequestsTest { @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); - info.setContentDescription(FRAME_LAYOUT_DESCRIPTION); + info.setContentDescription(mContentDescription); } } private class TestTextView extends TextView { - TestTextView(Context context) { - super(context); - } - - @Override - public int getWindowVisibility() { - return VISIBLE; - } - - @Override - public boolean isShown() { - return true; - } - - @Override - public int getAccessibilityViewId() { - return 1; - } - - @Override - public boolean includeForAccessibility() { - return true; - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); - } - } - - private class TestTextView2 extends TextView { - TestTextView2(Context context) { + private int mA11yId; + private String mContentDescription; + TestTextView(Context context, int a11yId, String contentDescription) { super(context); + mA11yId = a11yId; + mContentDescription = contentDescription; } @Override @@ -627,7 +918,7 @@ public class AccessibilityInteractionControllerNodeRequestsTest { @Override public int getAccessibilityViewId() { - return 2; + return mA11yId; } @Override @@ -638,7 +929,7 @@ public class AccessibilityInteractionControllerNodeRequestsTest { @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); - info.setContentDescription(TEXT_VIEW_2_DESCRIPTION); + info.setContentDescription(mContentDescription); } } } diff --git a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java index e9b5b6243089..18e0f29d4166 100644 --- a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java +++ b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java @@ -16,8 +16,23 @@ package com.android.server.am; +import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED; +import static android.content.Intent.ACTION_BOOT_COMPLETED; +import static android.content.Intent.ACTION_LOCKED_BOOT_COMPLETED; +import static android.content.Intent.ACTION_TIME_CHANGED; + +import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL; +import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY; +import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE; +import static com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import android.app.ActivityManagerInternal; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -25,10 +40,14 @@ import android.content.pm.ResolveInfo; import android.os.Process; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.util.SparseArray; import androidx.test.filters.SmallTest; +import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Collections; @@ -44,6 +63,24 @@ import java.util.List; @Presubmit public class BroadcastRecordTest { + private static final int USER0 = UserHandle.USER_SYSTEM; + private static final int USER1 = USER0 + 1; + private static final int[] USER_LIST = new int[] {USER0, USER1}; + private static final String PACKAGE1 = "pkg1"; + private static final String PACKAGE2 = "pkg2"; + private static final String PACKAGE3 = "pkg3"; + private static final String PACKAGE4 = "pkg4"; + private static final String[] PACKAGE_LIST = new String[] {PACKAGE1, PACKAGE2, PACKAGE3, + PACKAGE4}; + + @Mock + ActivityManagerInternal mActivityManagerInternal; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + @Test public void testCleanupDisabledPackageReceivers() { final int user0 = UserHandle.USER_SYSTEM; @@ -61,39 +98,232 @@ public class BroadcastRecordTest { // With given package: // Send to all users, cleanup a package of all users. - final BroadcastRecord recordAllAll = createBroadcastRecord(receiversM, UserHandle.USER_ALL); + final BroadcastRecord recordAllAll = createBroadcastRecord(receiversM, UserHandle.USER_ALL, + new Intent()); cleanupDisabledPackageReceivers(recordAllAll, pkgToCleanup, UserHandle.USER_ALL); assertNull(verifyRemaining(recordAllAll, excludeReceivers(receiversM, pkgToCleanup, -1))); // Send to all users, cleanup a package of one user. - final BroadcastRecord recordAllOne = createBroadcastRecord(receiversM, UserHandle.USER_ALL); + final BroadcastRecord recordAllOne = createBroadcastRecord(receiversM, UserHandle.USER_ALL, + new Intent()); cleanupDisabledPackageReceivers(recordAllOne, pkgToCleanup, user0); assertNull(verifyRemaining(recordAllOne, excludeReceivers(receiversM, pkgToCleanup, user0))); // Send to one user, cleanup a package of all users. - final BroadcastRecord recordOneAll = createBroadcastRecord(receiversU0, user0); + final BroadcastRecord recordOneAll = createBroadcastRecord(receiversU0, user0, + new Intent()); cleanupDisabledPackageReceivers(recordOneAll, pkgToCleanup, UserHandle.USER_ALL); assertNull(verifyRemaining(recordOneAll, excludeReceivers(receiversU0, pkgToCleanup, -1))); // Send to one user, cleanup a package one user. - final BroadcastRecord recordOneOne = createBroadcastRecord(receiversU0, user0); + final BroadcastRecord recordOneOne = createBroadcastRecord(receiversU0, user0, + new Intent()); cleanupDisabledPackageReceivers(recordOneOne, pkgToCleanup, user0); assertNull(verifyRemaining(recordOneOne, excludeReceivers(receiversU0, pkgToCleanup, -1))); // Without given package (e.g. stop user): // Send to all users, cleanup one user. - final BroadcastRecord recordAllM = createBroadcastRecord(receiversM, UserHandle.USER_ALL); + final BroadcastRecord recordAllM = createBroadcastRecord(receiversM, UserHandle.USER_ALL, + new Intent()); cleanupDisabledPackageReceivers(recordAllM, null /* packageName */, user1); assertNull(verifyRemaining(recordAllM, excludeReceivers(receiversM, null /* packageName */, user1))); // Send to one user, cleanup one user. - final BroadcastRecord recordU0 = createBroadcastRecord(receiversU0, user0); + final BroadcastRecord recordU0 = createBroadcastRecord(receiversU0, user0, new Intent()); cleanupDisabledPackageReceivers(recordU0, null /* packageName */, user0); assertNull(verifyRemaining(recordU0, Collections.emptyList())); } + // Test defer BOOT_COMPLETED and LOCKED_BOOT_COMPLETED broaddcasts. + @Test + public void testDeferBootCompletedBroadcast() { + testDeferBootCompletedBroadcast_defer_none(ACTION_BOOT_COMPLETED); + testDeferBootCompletedBroadcast_defer_all(ACTION_BOOT_COMPLETED); + testDeferBootCompletedBroadcast_defer_background_restricted_only(ACTION_BOOT_COMPLETED); + testDeferBootCompletedBroadcast_defer_none(ACTION_LOCKED_BOOT_COMPLETED); + testDeferBootCompletedBroadcast_defer_all(ACTION_LOCKED_BOOT_COMPLETED); + testDeferBootCompletedBroadcast_defer_background_restricted_only( + ACTION_LOCKED_BOOT_COMPLETED); + } + + // non-BOOT_COMPLETED broadcast does not get deferred. + @Test + public void testNoDeferOtherBroadcast() { + // no split for non-BOOT_COMPLETED broadcasts. + final BroadcastRecord br = createBootCompletedBroadcastRecord(ACTION_TIME_CHANGED); + final int origReceiversSize = br.receivers.size(); + + SparseArray<BroadcastRecord> deferred = br.splitDeferredBootCompletedBroadcastLocked( + mActivityManagerInternal, DEFER_BOOT_COMPLETED_BROADCAST_ALL); + // No receivers get deferred. + assertEquals(0, deferred.size()); + assertEquals(origReceiversSize, br.receivers.size()); + } + + private BroadcastRecord createBootCompletedBroadcastRecord(String action) { + final List<ResolveInfo> receivers = createReceiverInfos(PACKAGE_LIST, USER_LIST); + final BroadcastRecord br = createBroadcastRecord(receivers, UserHandle.USER_ALL, + new Intent(action)); + assertEquals(PACKAGE_LIST.length * USER_LIST.length, br.receivers.size()); + return br; + } + + // Test type DEFER_BOOT_COMPLETED_BROADCAST_NONE, this type does not defer any receiver. + private void testDeferBootCompletedBroadcast_defer_none(String action) { + final BroadcastRecord br = createBootCompletedBroadcastRecord(action); + final int origReceiversSize = br.receivers.size(); + + SparseArray<BroadcastRecord> deferred = br.splitDeferredBootCompletedBroadcastLocked( + mActivityManagerInternal, DEFER_BOOT_COMPLETED_BROADCAST_NONE); + // No receivers get deferred. + assertEquals(0, deferred.size()); + assertEquals(origReceiversSize, br.receivers.size()); + } + + // Test type DEFER_BOOT_COMPLETED_BROADCAST_ALL, this type defer all receivers. + private void testDeferBootCompletedBroadcast_defer_all(String action) { + final BroadcastRecord br = createBootCompletedBroadcastRecord(action); + + SparseArray<BroadcastRecord> deferred = br.splitDeferredBootCompletedBroadcastLocked( + mActivityManagerInternal, DEFER_BOOT_COMPLETED_BROADCAST_ALL); + // original BroadcastRecord receivers list is empty now. + assertTrue(br.receivers.isEmpty()); + + assertEquals(PACKAGE_LIST.length * USER_LIST.length, deferred.size()); + for (int i = 0; i < PACKAGE_LIST.length; i++) { + for (final int userId : USER_LIST) { + final int uid = UserHandle.getUid(userId, getAppId(i)); + assertTrue(deferred.contains(uid)); + assertEquals(1, deferred.get(uid).receivers.size()); + final ResolveInfo info = (ResolveInfo) deferred.get(uid).receivers.get(0); + assertEquals(PACKAGE_LIST[i], info.activityInfo.applicationInfo.packageName); + assertEquals(uid, info.activityInfo.applicationInfo.uid); + } + } + } + + // Test type DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY, + // This type defers receiver whose app package is background restricted. + private void testDeferBootCompletedBroadcast_defer_background_restricted_only(String action) { + final BroadcastRecord br = createBootCompletedBroadcastRecord(action); + final int origReceiversSize = br.receivers.size(); + + // First half packages in PACKAGE_LIST, return BACKGROUND_RESTRICTED. + for (int i = 0; i < PACKAGE_LIST.length / 2; i++) { + for (int u = 0; u < USER_LIST.length; u++) { + final int uid = UserHandle.getUid(USER_LIST[u], getAppId(i)); + doReturn(RESTRICTION_LEVEL_BACKGROUND_RESTRICTED).when(mActivityManagerInternal) + .getRestrictionLevel(eq(uid)); + } + } + + // the second half packages in PACKAGE_LIST, return not BACKGROUND_RESTRICTED. + for (int i = PACKAGE_LIST.length / 2; i < PACKAGE_LIST.length; i++) { + for (int u = 0; u < USER_LIST.length; u++) { + final int uid = UserHandle.getUid(USER_LIST[u], getAppId(i)); + doReturn(RESTRICTION_LEVEL_BACKGROUND_RESTRICTED - 10).when( + mActivityManagerInternal).getRestrictionLevel(eq(uid)); + } + } + + SparseArray<BroadcastRecord> deferred = br.splitDeferredBootCompletedBroadcastLocked( + mActivityManagerInternal, + DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY); + // original BroadcastRecord receivers list is half now. + assertEquals(origReceiversSize / 2, br.receivers.size()); + assertEquals(origReceiversSize / 2, deferred.size()); + + for (int i = 0; i < PACKAGE_LIST.length / 2; i++) { + for (int u = 0; u < USER_LIST.length; u++) { + final int uid = UserHandle.getUid(USER_LIST[u], getAppId(i)); + assertTrue(deferred.contains(uid)); + assertEquals(1, deferred.get(uid).receivers.size()); + final ResolveInfo info = (ResolveInfo) deferred.get(uid).receivers.get(0); + assertEquals(PACKAGE_LIST[i], info.activityInfo.applicationInfo.packageName); + assertEquals(uid, info.activityInfo.applicationInfo.uid); + } + } + + for (int i = PACKAGE_LIST.length / 2; i < PACKAGE_LIST.length; i++) { + for (int u = 0; u < USER_LIST.length; u++) { + final int uid = UserHandle.getUid(USER_LIST[u], getAppId(i)); + boolean found = false; + for (int r = 0; r < br.receivers.size(); r++) { + final ResolveInfo info = (ResolveInfo) br.receivers.get(r); + if (uid == info.activityInfo.applicationInfo.uid) { + found = true; + break; + } + } + assertTrue(found); + } + } + } + + /** + * Test the class {@link BroadcastDispatcher#DeferredBootCompletedBroadcastPerUser} + */ + @Test + public void testDeferBootCompletedBroadcast_dispatcher() { + testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_LOCKED_BOOT_COMPLETED, false); + testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_BOOT_COMPLETED, false); + testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_LOCKED_BOOT_COMPLETED, true); + testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_BOOT_COMPLETED, true); + } + + private void testDeferBootCompletedBroadcast_dispatcher_internal(String action, + boolean isAllUidReady) { + final List<ResolveInfo> receivers = createReceiverInfos(PACKAGE_LIST, new int[] {USER0}); + final BroadcastRecord br = createBroadcastRecord(receivers, USER0, new Intent(action)); + assertEquals(PACKAGE_LIST.length, br.receivers.size()); + + SparseArray<BroadcastRecord> deferred = br.splitDeferredBootCompletedBroadcastLocked( + mActivityManagerInternal, DEFER_BOOT_COMPLETED_BROADCAST_ALL); + // original BroadcastRecord receivers list is empty now. + assertTrue(br.receivers.isEmpty()); + assertEquals(PACKAGE_LIST.length, deferred.size()); + + DeferredBootCompletedBroadcastPerUser deferredPerUser = + new DeferredBootCompletedBroadcastPerUser(USER0); + deferredPerUser.enqueueBootCompletedBroadcasts(action, deferred); + + if (action.equals(ACTION_LOCKED_BOOT_COMPLETED)) { + assertEquals(PACKAGE_LIST.length, + deferredPerUser.mDeferredLockedBootCompletedBroadcasts.size()); + assertTrue(deferredPerUser.mLockedBootCompletedBroadcastReceived); + for (int i = 0; i < PACKAGE_LIST.length; i++) { + final int uid = UserHandle.getUid(USER0, getAppId(i)); + if (!isAllUidReady) { + deferredPerUser.updateUidReady(uid); + } + BroadcastRecord d = deferredPerUser.dequeueDeferredBootCompletedBroadcast( + isAllUidReady); + final ResolveInfo info = (ResolveInfo) d.receivers.get(0); + assertEquals(PACKAGE_LIST[i], info.activityInfo.applicationInfo.packageName); + assertEquals(uid, info.activityInfo.applicationInfo.uid); + } + assertEquals(0, deferredPerUser.mUidReadyForLockedBootCompletedBroadcast.size()); + } else if (action.equals(ACTION_BOOT_COMPLETED)) { + assertEquals(PACKAGE_LIST.length, + deferredPerUser.mDeferredBootCompletedBroadcasts.size()); + assertTrue(deferredPerUser.mBootCompletedBroadcastReceived); + for (int i = 0; i < PACKAGE_LIST.length; i++) { + final int uid = UserHandle.getUid(USER0, getAppId(i)); + if (!isAllUidReady) { + deferredPerUser.updateUidReady(uid); + } + BroadcastRecord d = deferredPerUser.dequeueDeferredBootCompletedBroadcast( + isAllUidReady); + final ResolveInfo info = (ResolveInfo) d.receivers.get(0); + assertEquals(PACKAGE_LIST[i], info.activityInfo.applicationInfo.packageName); + assertEquals(uid, info.activityInfo.applicationInfo.uid); + } + assertEquals(0, deferredPerUser.mUidReadyForBootCompletedBroadcast.size()); + } + } + private static void cleanupDisabledPackageReceivers(BroadcastRecord record, String packageName, int userId) { record.cleanupDisabledPackageReceiversLocked(packageName, null /* filterByClasses */, @@ -148,7 +378,7 @@ public class BroadcastRecordTest { for (int i = 0; i < packages.length; i++) { for (final int userId : userIds) { receivers.add(createResolveInfo(packages[i], - UserHandle.getUid(userId, Process.FIRST_APPLICATION_UID + i))); + UserHandle.getUid(userId, getAppId(i)))); } } return receivers; @@ -172,10 +402,11 @@ public class BroadcastRecordTest { return excludedList; } - private static BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId) { + private static BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId, + Intent intent) { return new BroadcastRecord( null /* queue */, - new Intent(), + intent, null /* callerApp */, null /* callerPackage */, null /* callerFeatureId */, @@ -200,4 +431,8 @@ public class BroadcastRecordTest { null /* activityStartsToken */, false /* timeoutExempt */ ); } + + private static int getAppId(int i) { + return Process.FIRST_APPLICATION_UID + i; + } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 69d8e89ac3b3..b94b6908f030 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -235,7 +235,8 @@ public class BiometricServiceTest { @Test public void testAuthenticate_credentialAllowedButNotSetup_returnsNoDeviceCredential() throws Exception { - when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(false); mBiometricService = new BiometricService(mContext, mInjector); mBiometricService.onStart(); @@ -254,7 +255,8 @@ public class BiometricServiceTest { // When no biometrics are enrolled, but credentials are set up, status bar should be // invoked right away with showAuthenticationDialog - when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(true); mBiometricService = new BiometricService(mContext, mInjector); mBiometricService.onStart(); @@ -532,7 +534,8 @@ public class BiometricServiceTest { public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG); when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false); - when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(true); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, true /* requireConfirmation */, Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK); @@ -601,7 +604,8 @@ public class BiometricServiceTest { public void testAuthenticate_no_Biometrics_noCredential() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG); when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false); - when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(false); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, true /* requireConfirmation */, @@ -872,7 +876,8 @@ public class BiometricServiceTest { @Test public void testBiometricOrCredentialAuth_whenBiometricLockout_showsCredential() throws Exception { - when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(true); setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt())) .thenReturn(LockoutTracker.LOCKOUT_PERMANENT); @@ -1168,13 +1173,15 @@ public class BiometricServiceTest { setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK); // When only biometric is requested, and sensor is not strong enough - when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(false); int authenticators = Authenticators.BIOMETRIC_STRONG; assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE, invokeCanAuthenticate(mBiometricService, authenticators)); // When credential and biometric are requested, and sensor is not strong enough - when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(true); authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL; assertEquals(BiometricManager.BIOMETRIC_SUCCESS, invokeCanAuthenticate(mBiometricService, authenticators)); @@ -1186,12 +1193,14 @@ public class BiometricServiceTest { mBiometricService.onStart(); // Credential requested but not set up - when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(false); assertEquals(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED, invokeCanAuthenticate(mBiometricService, Authenticators.DEVICE_CREDENTIAL)); // Credential requested and set up - when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(true); assertEquals(BiometricManager.BIOMETRIC_SUCCESS, invokeCanAuthenticate(mBiometricService, Authenticators.DEVICE_CREDENTIAL)); } @@ -1199,7 +1208,8 @@ public class BiometricServiceTest { @Test public void testCanAuthenticate_whenNoBiometricsEnrolled() throws Exception { // With credential set up, test the following. - when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(true); setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, false /* enrolled */); @@ -1218,7 +1228,8 @@ public class BiometricServiceTest { public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG); when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false); - when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(true); // When only biometric is requested int authenticators = Authenticators.BIOMETRIC_STRONG; @@ -1247,7 +1258,8 @@ public class BiometricServiceTest { invokeCanAuthenticate(mBiometricService, authenticators)); // When credential and biometric are requested, and credential is set up - when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(true); assertEquals(BiometricManager.BIOMETRIC_SUCCESS, invokeCanAuthenticate(mBiometricService, authenticators)); } @@ -1394,7 +1406,8 @@ public class BiometricServiceTest { // Requesting strong and credential, when credential is setup resetReceivers(); authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL; - when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(true); assertEquals(BiometricManager.BIOMETRIC_SUCCESS, invokeCanAuthenticate(mBiometricService, authenticators)); requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1, @@ -1527,7 +1540,8 @@ public class BiometricServiceTest { public void testWorkAuthentication_fingerprintFailsIfDisabledByDevicePolicyManager() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); - when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(true); when(mDevicePolicyManager .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */)) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java index 5746f6f2d446..e6acc904d811 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java @@ -88,27 +88,27 @@ public class BiometricContextProviderTest { } @Test - public void testIsAoD() throws RemoteException { + public void testIsAod() throws RemoteException { mListener.onDozeChanged(true); - assertThat(mProvider.isAoD()).isTrue(); + assertThat(mProvider.isAod()).isTrue(); mListener.onDozeChanged(false); - assertThat(mProvider.isAoD()).isFalse(); + assertThat(mProvider.isAod()).isFalse(); when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(false); mListener.onDozeChanged(true); - assertThat(mProvider.isAoD()).isFalse(); + assertThat(mProvider.isAod()).isFalse(); mListener.onDozeChanged(false); - assertThat(mProvider.isAoD()).isFalse(); + assertThat(mProvider.isAod()).isFalse(); } @Test - public void testSubscribesToAoD() throws RemoteException { + public void testSubscribesToAod() throws RemoteException { final List<Boolean> expected = ImmutableList.of(true, false, true, true, false); final List<Boolean> actual = new ArrayList<>(); mProvider.subscribe(mOpContext, ctx -> { assertThat(ctx).isSameInstanceAs(mOpContext); - actual.add(ctx.isAoD); + actual.add(ctx.isAod); }); for (boolean v : expected) { @@ -178,7 +178,7 @@ public class BiometricContextProviderTest { assertThat(context).isSameInstanceAs(mOpContext); assertThat(mOpContext.id).isEqualTo(0); assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN); - assertThat(mOpContext.isAoD).isEqualTo(false); + assertThat(mOpContext.isAod).isEqualTo(false); assertThat(mOpContext.isCrypto).isEqualTo(false); for (int type : List.of(StatusBarManager.SESSION_BIOMETRIC_PROMPT, @@ -192,7 +192,7 @@ public class BiometricContextProviderTest { assertThat(context).isSameInstanceAs(mOpContext); assertThat(mOpContext.id).isEqualTo(id); assertThat(mOpContext.reason).isEqualTo(reason(type)); - assertThat(mOpContext.isAoD).isEqualTo(aod); + assertThat(mOpContext.isAod).isEqualTo(aod); assertThat(mOpContext.isCrypto).isEqualTo(false); mSessionListener.onSessionEnded(type, InstanceId.fakeInstanceId(id)); @@ -202,7 +202,7 @@ public class BiometricContextProviderTest { assertThat(context).isSameInstanceAs(mOpContext); assertThat(mOpContext.id).isEqualTo(0); assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN); - assertThat(mOpContext.isAoD).isEqualTo(false); + assertThat(mOpContext.isAod).isEqualTo(false); assertThat(mOpContext.isCrypto).isEqualTo(false); } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 3b4aece5997e..2ae285409b73 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -16,6 +16,7 @@ package com.android.server.companion.virtual; +import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; @@ -39,6 +40,7 @@ import android.app.admin.DevicePolicyManager; import android.companion.AssociationInfo; import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.VirtualDeviceParams; +import android.companion.virtual.audio.IAudioSessionCallback; import android.content.Context; import android.content.ContextWrapper; import android.graphics.Point; @@ -111,6 +113,8 @@ public class VirtualDeviceManagerServiceTest { @Mock IThermalService mIThermalServiceMock; private PowerManager mPowerManager; + @Mock + private IAudioSessionCallback mCallback; @Before public void setUp() { @@ -250,6 +254,12 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void onAudioSessionStarting_noDisplay_failsSecurityException() { + assertThrows(SecurityException.class, + () -> mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback)); + } + + @Test public void createVirtualKeyboard_noPermission_failsSecurityException() { mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); doCallRealMethod().when(mContext).enforceCallingOrSelfPermission( @@ -283,6 +293,22 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void onAudioSessionStarting_noPermission_failsSecurityException() { + mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); + doCallRealMethod().when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); + assertThrows(SecurityException.class, + () -> mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback)); + } + + @Test + public void onAudioSessionEnded_noPermission_failsSecurityException() { + doCallRealMethod().when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); + assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionEnded()); + } + + @Test public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() { mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, @@ -316,6 +342,25 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void onAudioSessionStarting_hasVirtualAudioController() { + mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID); + + mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback); + + assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNotNull(); + } + + @Test + public void onAudioSessionEnded_noVirtualAudioController() { + mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID); + mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback); + + mDeviceImpl.onAudioSessionEnded(); + + assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNull(); + } + + @Test public void sendKeyEvent_noFd() { assertThrows( IllegalArgumentException.class, diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java new file mode 100644 index 000000000000..3160272ef9b1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual.audio; + +import static android.media.AudioAttributes.FLAG_SECURE; +import static android.media.AudioPlaybackConfiguration.PLAYER_STATE_STARTED; +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.companion.virtual.audio.IAudioSessionCallback; +import android.content.Context; +import android.content.ContextWrapper; +import android.media.AudioPlaybackConfiguration; +import android.media.AudioRecordingConfiguration; +import android.media.MediaRecorder; +import android.media.PlayerBase; +import android.os.Parcel; +import android.os.RemoteException; +import android.util.ArraySet; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.companion.virtual.GenericWindowPolicyController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class VirtualAudioControllerTest { + private static final int APP1_UID = 100; + private static final int APP2_UID = 200; + + private Context mContext; + private VirtualAudioController mVirtualAudioController; + private GenericWindowPolicyController mGenericWindowPolicyController; + @Mock IAudioSessionCallback mCallback; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); + mVirtualAudioController = new VirtualAudioController(mContext); + mGenericWindowPolicyController = new GenericWindowPolicyController( + FLAG_SECURE, + SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, + /* allowedUsers= */ new ArraySet<>(), + /* allowedActivities= */ new ArraySet<>(), + /* blockedActivities= */ new ArraySet<>(), + /* activityListener= */null, + /* activityBlockedCallback= */ null); + } + + @Test + public void startListening_receivesCallback() throws RemoteException { + ArraySet<Integer> runningUids = new ArraySet<>(); + runningUids.add(APP1_UID); + int[] appUids = new int[] {APP1_UID}; + + mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback); + + mGenericWindowPolicyController.onRunningAppsChanged(runningUids); + verify(mCallback).onAppsNeedingAudioRoutingChanged(appUids); + } + + @Test + public void stopListening_removesCallback() throws RemoteException { + ArraySet<Integer> runningUids = new ArraySet<>(); + runningUids.add(APP1_UID); + int[] appUids = new int[] {APP1_UID}; + mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback); + + mVirtualAudioController.stopListening(); + + mGenericWindowPolicyController.onRunningAppsChanged(runningUids); + verify(mCallback, never()).onAppsNeedingAudioRoutingChanged(appUids); + } + + @Test + public void onRunningAppsChanged_notifiesAudioRoutingModified() throws RemoteException { + mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback); + + ArraySet<Integer> runningUids = new ArraySet<>(); + runningUids.add(APP1_UID); + mVirtualAudioController.onRunningAppsChanged(runningUids); + + int[] appUids = new int[] {APP1_UID}; + verify(mCallback).onAppsNeedingAudioRoutingChanged(appUids); + } + + @Test + public void onRunningAppsChanged_audioIsPlaying_doesNothing() throws RemoteException { + mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback); + mVirtualAudioController.addPlayingAppsForTesting(APP2_UID); + + ArraySet<Integer> runningUids = new ArraySet<>(); + runningUids.add(APP1_UID); + mVirtualAudioController.onRunningAppsChanged(runningUids); + + int[] appUids = new int[]{APP1_UID}; + verify(mCallback, never()).onAppsNeedingAudioRoutingChanged(appUids); + } + + @Test + public void onRunningAppsChanged_lastPlayingAppRemoved_delaysReroutingAudio() { + ArraySet<Integer> runningUids = new ArraySet<>(); + runningUids.add(APP1_UID); + runningUids.add(APP2_UID); + mVirtualAudioController.onRunningAppsChanged(runningUids); + mVirtualAudioController.addPlayingAppsForTesting(APP2_UID); + + ArraySet<Integer> appUids = new ArraySet<>(); + appUids.add(APP1_UID); + mVirtualAudioController.onRunningAppsChanged(appUids); + + assertThat(mVirtualAudioController.hasPendingRunnable()).isTrue(); + } + + @Test + public void onPlaybackConfigChanged_sendsCallback() throws RemoteException { + mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback); + ArraySet<Integer> runningUids = new ArraySet<>(); + runningUids.add(APP1_UID); + mVirtualAudioController.onRunningAppsChanged(runningUids); + List<AudioPlaybackConfiguration> configs = createPlaybackConfigurations(runningUids); + + mVirtualAudioController.onPlaybackConfigChanged(configs); + + verify(mCallback).onPlaybackConfigChanged(configs); + } + + @Test + public void onRecordingConfigChanged_sendsCallback() throws RemoteException { + mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback); + ArraySet<Integer> runningUids = new ArraySet<>(); + runningUids.add(APP1_UID); + mVirtualAudioController.onRunningAppsChanged(runningUids); + List<AudioRecordingConfiguration> configs = createRecordingConfigurations(runningUids); + + mVirtualAudioController.onRecordingConfigChanged(configs); + + verify(mCallback).onRecordingConfigChanged(configs); + } + + private List<AudioPlaybackConfiguration> createPlaybackConfigurations( + ArraySet<Integer> appUids) { + List<AudioPlaybackConfiguration> configs = new ArrayList<>(); + for (int appUid : appUids) { + PlayerBase.PlayerIdCard playerIdCard = + PlayerBase.PlayerIdCard.CREATOR.createFromParcel(Parcel.obtain()); + AudioPlaybackConfiguration audioPlaybackConfiguration = + new AudioPlaybackConfiguration( + playerIdCard, /* piid= */ 1000, appUid, /* pid= */ 1000); + audioPlaybackConfiguration.handleStateEvent(PLAYER_STATE_STARTED, /* deviceId= */1); + configs.add(audioPlaybackConfiguration); + } + return configs; + } + + private List<AudioRecordingConfiguration> createRecordingConfigurations( + ArraySet<Integer> appUids) { + List<AudioRecordingConfiguration> configs = new ArrayList<>(); + for (int appUid : appUids) { + AudioRecordingConfiguration audioRecordingConfiguration = + new AudioRecordingConfiguration( + /* uid= */ appUid, + /* session= */ 1000, + MediaRecorder.AudioSource.MIC, + /* clientFormat= */ null, + /* devFormat= */ null, + /* patchHandle= */ 1000, + "com.android.example"); + configs.add(audioRecordingConfiguration); + } + return configs; + } +} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 48e21225f6d6..fe110e5398ca 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -55,6 +55,7 @@ import static android.net.InetAddresses.parseNumericAddress; import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback; import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_PROFILE_OFF_DEADLINE; import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_TURN_PROFILE_ON_NOTIFICATION; @@ -997,7 +998,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); // Change the parent user's password. - dpm.reportPasswordChanged(UserHandle.USER_SYSTEM); + dpm.reportPasswordChanged(new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD), + UserHandle.USER_SYSTEM); // The managed profile owner should receive this broadcast. final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED); @@ -1036,7 +1038,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); // Change the profile's password. - dpm.reportPasswordChanged(MANAGED_PROFILE_USER_ID); + dpm.reportPasswordChanged(new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD), + MANAGED_PROFILE_USER_ID); // Both the device owner and the managed profile owner should receive this broadcast. final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED); @@ -5531,7 +5534,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { reset(mContext.spiedContext); // This should be ignored, as there is no lock screen. - dpm.reportPasswordChanged(userHandle); + dpm.reportPasswordChanged(new PasswordMetrics(CREDENTIAL_TYPE_NONE), userHandle); // No broadcast should be sent. verify(mContext.spiedContext, times(0)).sendBroadcastAsUser( @@ -5904,7 +5907,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().lockSettingsInternal.getUserPasswordMetrics(userHandle)) .thenReturn(passwordMetrics); - dpm.reportPasswordChanged(userHandle); + dpm.reportPasswordChanged(passwordMetrics, userHandle); verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( MockUtils.checkIntentAction( diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamServiceTest.java new file mode 100644 index 000000000000..74d2e0f231bb --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/dreams/DreamServiceTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.dreams; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.service.dreams.DreamService; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DreamServiceTest { + @Test + public void testMetadataParsing() throws PackageManager.NameNotFoundException { + final String testPackageName = "com.android.frameworks.servicestests"; + final String testDreamClassName = "com.android.server.dreams.TestDreamService"; + final String testSettingsActivity = "com.android.server.dreams/.TestDreamSettingsActivity"; + + final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + final ServiceInfo si = context.getPackageManager().getServiceInfo( + new ComponentName(testPackageName, testDreamClassName), + PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA)); + final DreamService.DreamMetadata metadata = DreamService.getDreamMetadata(context, si); + + assertEquals(0, metadata.settingsActivity.compareTo( + ComponentName.unflattenFromString(testSettingsActivity))); + assertFalse(metadata.showComplications); + } +} diff --git a/services/tests/servicestests/src/com/android/server/dreams/TestDreamService.java b/services/tests/servicestests/src/com/android/server/dreams/TestDreamService.java new file mode 100644 index 000000000000..3c99a9829275 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/dreams/TestDreamService.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.dreams; + +import android.service.dreams.DreamService; + +/** + * Dream service implementation for unit testing. + */ +public class TestDreamService extends DreamService { +} diff --git a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java index f26e0941e008..096c80b13b89 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java @@ -142,6 +142,7 @@ public class IntegrityFileManagerTest { new AppInstallMetadata.Builder() .setPackageName(packageName) .setAppCertificates(Collections.singletonList(packageCert)) + .setAppCertificateLineage(Collections.singletonList(packageCert)) .setVersionCode(version) .setInstallerName("abc") .setInstallerCertificates(Collections.singletonList("abc")) @@ -183,6 +184,8 @@ public class IntegrityFileManagerTest { new AppInstallMetadata.Builder() .setPackageName(installedPackageName) .setAppCertificates(Collections.singletonList(installedAppCertificate)) + .setAppCertificateLineage( + Collections.singletonList(installedAppCertificate)) .setVersionCode(250) .setInstallerName("abc") .setInstallerCertificates(Collections.singletonList("abc")) diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java index 441cd4b47c94..1c860ca31990 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java @@ -183,6 +183,7 @@ public class RuleEvaluationEngineTest { return new AppInstallMetadata.Builder() .setPackageName("abc") .setAppCertificates(Collections.singletonList("abc")) + .setAppCertificateLineage(Collections.singletonList("abc")) .setInstallerCertificates(Collections.singletonList("abc")) .setInstallerName("abc") .setVersionCode(-1) diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java index b271a7766d63..5089f74894d9 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java @@ -49,6 +49,7 @@ public class RuleEvaluatorTest { new AppInstallMetadata.Builder() .setPackageName(PACKAGE_NAME_1) .setAppCertificates(Collections.singletonList(APP_CERTIFICATE)) + .setAppCertificateLineage(Collections.singletonList(APP_CERTIFICATE)) .setVersionCode(2) .build(); diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java index 415e63537aaf..370bd80003bd 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java @@ -54,6 +54,7 @@ public class RuleIndexingControllerTest { new AppInstallMetadata.Builder() .setPackageName("ddd") .setAppCertificates(Collections.singletonList("777")) + .setAppCertificateLineage(Collections.singletonList("777")) .build(); List<RuleIndexRange> resultingIndexes = @@ -76,6 +77,7 @@ public class RuleIndexingControllerTest { new AppInstallMetadata.Builder() .setPackageName("ddd") .setAppCertificates(Arrays.asList("777", "999")) + .setAppCertificateLineage(Arrays.asList("777", "999")) .build(); List<RuleIndexRange> resultingIndexes = @@ -99,6 +101,7 @@ public class RuleIndexingControllerTest { new AppInstallMetadata.Builder() .setPackageName("bbb") .setAppCertificates(Collections.singletonList("999")) + .setAppCertificateLineage(Collections.singletonList("999")) .build(); List<RuleIndexRange> resultingIndexes = @@ -121,6 +124,7 @@ public class RuleIndexingControllerTest { new AppInstallMetadata.Builder() .setPackageName("ccc") .setAppCertificates(Collections.singletonList("444")) + .setAppCertificateLineage(Collections.singletonList("444")) .build(); List<RuleIndexRange> resultingIndexes = @@ -153,6 +157,7 @@ public class RuleIndexingControllerTest { new AppInstallMetadata.Builder() .setPackageName("ccc") .setAppCertificates(Collections.singletonList("444")) + .setAppCertificateLineage(Collections.singletonList("444")) .build(); List<RuleIndexRange> resultingIndexes = diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java index b87877907d4f..9ed2e88bd6a2 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java @@ -875,6 +875,11 @@ public class RuleBinarySerializerTest { } @Override + public boolean isAppCertificateLineageFormula() { + return false; + } + + @Override public boolean isInstallerFormula() { return false; } diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java index ea9e6ff86728..6dccdf51af02 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java @@ -305,6 +305,11 @@ public class RuleIndexingDetailsIdentifierTest { } @Override + public boolean isAppCertificateLineageFormula() { + return false; + } + + @Override public boolean isInstallerFormula() { return false; } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java index 58e1c4378c41..eae9ee7fc55a 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java @@ -257,7 +257,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { flushHandlerTasks(); final PasswordMetrics metric = PasswordMetrics.computeForCredential(pattern); assertEquals(metric, mService.getUserPasswordMetrics(PRIMARY_USER_ID)); - verify(mDevicePolicyManager).reportPasswordChanged(PRIMARY_USER_ID); + verify(mDevicePolicyManager).reportPasswordChanged(metric, PRIMARY_USER_ID); assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( pattern, PRIMARY_USER_ID, 0 /* flags */).getResponseCode()); diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index f865a50978b5..af8ac6f412f5 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -23,11 +23,13 @@ import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRI import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY; import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER; import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE; +import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY; import static android.net.ConnectivityManager.BLOCKED_REASON_NONE; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_WIFI; -import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED; import static android.net.INetd.FIREWALL_RULE_ALLOW; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; @@ -38,8 +40,10 @@ import static android.net.NetworkPolicy.WARNING_DISABLED; import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_FOREGROUND; import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_SYSTEM; import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND; +import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST; import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE; import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM; +import static android.net.NetworkPolicyManager.ALLOWED_REASON_TOP; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND; import static android.net.NetworkPolicyManager.POLICY_NONE; @@ -200,6 +204,7 @@ import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; @@ -1877,55 +1882,99 @@ public class NetworkPolicyManagerServiceTest { } @Test + public void testLowPowerStandbyAllowlist() throws Exception { + callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_TOP, 0); + callOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0); + callOnUidStateChanged(UID_C, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0); + expectHasInternetPermission(UID_A, true); + expectHasInternetPermission(UID_B, true); + expectHasInternetPermission(UID_C, true); + + final NetworkPolicyManagerInternal internal = LocalServices + .getService(NetworkPolicyManagerInternal.class); + + Map<Integer, Integer> firewallUidRules = new ArrayMap<>(); + doAnswer(arg -> { + int[] uids = arg.getArgument(1); + int[] rules = arg.getArgument(2); + assertTrue(uids.length == rules.length); + + for (int i = 0; i < uids.length; ++i) { + firewallUidRules.put(uids[i], rules[i]); + } + return null; + }).when(mNetworkManager).setFirewallUidRules(eq(FIREWALL_CHAIN_LOW_POWER_STANDBY), + any(int[].class), any(int[].class)); + + internal.setLowPowerStandbyAllowlist(new int[] { UID_B }); + internal.setLowPowerStandbyActive(true); + assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_A).intValue()); + assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_B).intValue()); + assertFalse(mService.isUidNetworkingBlocked(UID_A, false)); + assertFalse(mService.isUidNetworkingBlocked(UID_B, false)); + assertTrue(mService.isUidNetworkingBlocked(UID_C, false)); + + internal.setLowPowerStandbyActive(false); + assertFalse(mService.isUidNetworkingBlocked(UID_A, false)); + assertFalse(mService.isUidNetworkingBlocked(UID_B, false)); + assertFalse(mService.isUidNetworkingBlocked(UID_C, false)); + } + + @Test public void testUpdateEffectiveBlockedReasons() { - final SparseArray<Pair<Integer, Integer>> effectiveBlockedReasons = new SparseArray<>(); - effectiveBlockedReasons.put(BLOCKED_REASON_NONE, - Pair.create(BLOCKED_REASON_NONE, ALLOWED_REASON_NONE)); - - effectiveBlockedReasons.put(BLOCKED_REASON_NONE, - Pair.create(BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_SYSTEM)); - effectiveBlockedReasons.put(BLOCKED_REASON_NONE, - Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_REASON_DOZE, - ALLOWED_REASON_SYSTEM)); - effectiveBlockedReasons.put(BLOCKED_REASON_NONE, - Pair.create(BLOCKED_METERED_REASON_DATA_SAVER, - ALLOWED_METERED_REASON_SYSTEM)); - effectiveBlockedReasons.put(BLOCKED_REASON_NONE, - Pair.create(BLOCKED_METERED_REASON_DATA_SAVER - | BLOCKED_METERED_REASON_USER_RESTRICTED, - ALLOWED_METERED_REASON_SYSTEM)); - - effectiveBlockedReasons.put(BLOCKED_METERED_REASON_DATA_SAVER, + final Map<Pair<Integer, Integer>, Integer> effectiveBlockedReasons = new HashMap<>(); + effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_NONE, ALLOWED_REASON_NONE), + BLOCKED_REASON_NONE); + + effectiveBlockedReasons.put( + Pair.create(BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_SYSTEM), + BLOCKED_REASON_NONE); + effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_REASON_DOZE, + ALLOWED_REASON_SYSTEM), BLOCKED_REASON_NONE); + effectiveBlockedReasons.put( + Pair.create(BLOCKED_METERED_REASON_DATA_SAVER, ALLOWED_METERED_REASON_SYSTEM), + BLOCKED_REASON_NONE); + effectiveBlockedReasons.put(Pair.create(BLOCKED_METERED_REASON_DATA_SAVER + | BLOCKED_METERED_REASON_USER_RESTRICTED, + ALLOWED_METERED_REASON_SYSTEM), BLOCKED_REASON_NONE); + + effectiveBlockedReasons.put( Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_METERED_REASON_DATA_SAVER, - ALLOWED_REASON_SYSTEM)); - effectiveBlockedReasons.put(BLOCKED_REASON_APP_STANDBY, + ALLOWED_REASON_SYSTEM), BLOCKED_METERED_REASON_DATA_SAVER); + effectiveBlockedReasons.put( Pair.create(BLOCKED_REASON_APP_STANDBY | BLOCKED_METERED_REASON_USER_RESTRICTED, - ALLOWED_METERED_REASON_SYSTEM)); - - effectiveBlockedReasons.put(BLOCKED_REASON_NONE, - Pair.create(BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_FOREGROUND)); - effectiveBlockedReasons.put(BLOCKED_REASON_NONE, - Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_REASON_DOZE, - ALLOWED_REASON_FOREGROUND)); - effectiveBlockedReasons.put(BLOCKED_REASON_NONE, - Pair.create(BLOCKED_METERED_REASON_DATA_SAVER, ALLOWED_METERED_REASON_FOREGROUND)); - effectiveBlockedReasons.put(BLOCKED_REASON_NONE, - Pair.create(BLOCKED_METERED_REASON_DATA_SAVER - | BLOCKED_METERED_REASON_USER_RESTRICTED, - ALLOWED_METERED_REASON_FOREGROUND)); - effectiveBlockedReasons.put(BLOCKED_METERED_REASON_DATA_SAVER, + ALLOWED_METERED_REASON_SYSTEM), BLOCKED_REASON_APP_STANDBY); + + effectiveBlockedReasons.put( + Pair.create(BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_FOREGROUND), + BLOCKED_REASON_NONE); + effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_REASON_DOZE, + ALLOWED_REASON_FOREGROUND), BLOCKED_REASON_NONE); + effectiveBlockedReasons.put( + Pair.create(BLOCKED_METERED_REASON_DATA_SAVER, ALLOWED_METERED_REASON_FOREGROUND), + BLOCKED_REASON_NONE); + effectiveBlockedReasons.put(Pair.create(BLOCKED_METERED_REASON_DATA_SAVER + | BLOCKED_METERED_REASON_USER_RESTRICTED, + ALLOWED_METERED_REASON_FOREGROUND), BLOCKED_REASON_NONE); + effectiveBlockedReasons.put( Pair.create(BLOCKED_REASON_BATTERY_SAVER | BLOCKED_METERED_REASON_DATA_SAVER, - ALLOWED_REASON_FOREGROUND)); - effectiveBlockedReasons.put(BLOCKED_REASON_BATTERY_SAVER, - Pair.create(BLOCKED_REASON_BATTERY_SAVER - | BLOCKED_METERED_REASON_USER_RESTRICTED, - ALLOWED_METERED_REASON_FOREGROUND)); + ALLOWED_REASON_FOREGROUND), BLOCKED_METERED_REASON_DATA_SAVER); + effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_BATTERY_SAVER + | BLOCKED_METERED_REASON_USER_RESTRICTED, + ALLOWED_METERED_REASON_FOREGROUND), BLOCKED_REASON_BATTERY_SAVER); + + effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_LOW_POWER_STANDBY, + ALLOWED_REASON_FOREGROUND), BLOCKED_REASON_LOW_POWER_STANDBY); + effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_LOW_POWER_STANDBY, + ALLOWED_REASON_TOP), BLOCKED_REASON_NONE); + effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_LOW_POWER_STANDBY, + ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST), BLOCKED_REASON_NONE); // TODO: test more combinations of blocked reasons. - for (int i = 0; i < effectiveBlockedReasons.size(); ++i) { - final int expectedEffectiveBlockedReasons = effectiveBlockedReasons.keyAt(i); - final int blockedReasons = effectiveBlockedReasons.valueAt(i).first; - final int allowedReasons = effectiveBlockedReasons.valueAt(i).second; + for (Map.Entry<Pair<Integer, Integer>, Integer> test : effectiveBlockedReasons.entrySet()) { + final int expectedEffectiveBlockedReasons = test.getValue(); + final int blockedReasons = test.getKey().first; + final int allowedReasons = test.getKey().second; final String errorMsg = "Expected=" + blockedReasonsToString(expectedEffectiveBlockedReasons) + "; blockedReasons=" + blockedReasonsToString(blockedReasons) diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java index 13a8f69358b6..b72b8d2ec6e8 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -30,15 +30,10 @@ import android.annotation.Nullable; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManagerInternal; import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.UserInfo; -import com.android.server.pm.pkg.parsing.ParsingPackage; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedActivityImpl; -import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; -import com.android.server.pm.pkg.component.ParsedProviderImpl; import android.os.Build; import android.os.Process; import android.os.UserHandle; @@ -53,6 +48,13 @@ import com.android.server.om.OverlayReferenceMapper; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedActivityImpl; +import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; +import com.android.server.pm.pkg.component.ParsedProviderImpl; +import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.utils.WatchableTester; import org.junit.Before; @@ -101,6 +103,8 @@ public class AppsFilterTest { AppsFilter.StateProvider mStateProvider; @Mock Executor mMockExecutor; + @Mock + PackageManagerInternal mMockPmInternal; private ArrayMap<String, PackageSetting> mExisting = new ArrayMap<>(); @@ -222,7 +226,7 @@ public class AppsFilterTest { public void testSystemReadyPropogates() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); watcher.register(); appsFilter.onSystemReady(); @@ -234,7 +238,7 @@ public class AppsFilterTest { public void testQueriesAction_FilterMatches() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); watcher.register(); simulateAddBasicAndroid(appsFilter); @@ -257,7 +261,7 @@ public class AppsFilterTest { public void testQueriesProtectedAction_FilterDoesNotMatch() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); watcher.register(); final Signature frameworkSignature = Mockito.mock(Signature.class); @@ -306,7 +310,7 @@ public class AppsFilterTest { public void testQueriesProvider_FilterMatches() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); watcher.register(); simulateAddBasicAndroid(appsFilter); @@ -331,7 +335,7 @@ public class AppsFilterTest { public void testOnUserUpdated_FilterMatches() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -389,7 +393,7 @@ public class AppsFilterTest { public void testQueriesDifferentProvider_Filters() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); watcher.register(); simulateAddBasicAndroid(appsFilter); @@ -414,7 +418,7 @@ public class AppsFilterTest { public void testQueriesProviderWithSemiColon_FilterMatches() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -433,7 +437,7 @@ public class AppsFilterTest { public void testQueriesAction_NoMatchingAction_Filters() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -450,7 +454,7 @@ public class AppsFilterTest { public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -471,7 +475,7 @@ public class AppsFilterTest { public void testNoQueries_Filters() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -488,7 +492,7 @@ public class AppsFilterTest { public void testNoUsesLibrary_Filters() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -514,7 +518,7 @@ public class AppsFilterTest { public void testUsesLibrary_DoesntFilter() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -541,7 +545,7 @@ public class AppsFilterTest { public void testUsesOptionalLibrary_DoesntFilter() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -568,7 +572,7 @@ public class AppsFilterTest { public void testUsesLibrary_ShareUid_DoesntFilter() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -600,7 +604,7 @@ public class AppsFilterTest { public void testForceQueryable_SystemDoesntFilter() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -619,7 +623,7 @@ public class AppsFilterTest { public void testForceQueryable_NonSystemFilters() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -636,7 +640,7 @@ public class AppsFilterTest { public void testForceQueryableByDevice_SystemCaller_DoesntFilter() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{"com.some.package"}, - false, null, mMockExecutor); + false, null, mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -655,7 +659,7 @@ public class AppsFilterTest { public void testSystemSignedTarget_DoesntFilter() throws CertificateException { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); appsFilter.onSystemReady(); final Signature frameworkSignature = Mockito.mock(Signature.class); @@ -684,7 +688,7 @@ public class AppsFilterTest { public void testForceQueryableByDevice_NonSystemCaller_Filters() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{"com.some.package"}, - false, null, mMockExecutor); + false, null, mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -702,7 +706,8 @@ public class AppsFilterTest { public void testSystemQueryable_DoesntFilter() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, - true /* system force queryable */, null, mMockExecutor); + true /* system force queryable */, null, mMockExecutor, + mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -720,7 +725,7 @@ public class AppsFilterTest { public void testQueriesPackage_DoesntFilter() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -739,7 +744,7 @@ public class AppsFilterTest { .thenReturn(false); final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -756,7 +761,7 @@ public class AppsFilterTest { public void testSystemUid_DoesntFilter() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -772,7 +777,7 @@ public class AppsFilterTest { public void testSystemUidSecondaryUser_DoesntFilter() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -789,7 +794,7 @@ public class AppsFilterTest { public void testNonSystemUid_NoCallingSetting_Filters() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -804,7 +809,7 @@ public class AppsFilterTest { public void testNoTargetPackage_filters() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -862,7 +867,7 @@ public class AppsFilterTest { return Collections.emptyMap(); } }, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -918,7 +923,16 @@ public class AppsFilterTest { .setOverlayTargetOverlayableName("overlayableName"); ParsingPackage actorOne = pkg("com.some.package.actor.one"); ParsingPackage actorTwo = pkg("com.some.package.actor.two"); - + ArraySet<PackageStateInternal> actorSharedSettingPackages = new ArraySet<>(); + PackageSetting ps1 = getPackageSettingFromParsingPackage(actorOne, DUMMY_ACTOR_APPID, + null /*settingBuilder*/); + PackageSetting ps2 = getPackageSettingFromParsingPackage(actorTwo, DUMMY_ACTOR_APPID, + null /*settingBuilder*/); + actorSharedSettingPackages.add(ps1); + actorSharedSettingPackages.add(ps2); + when(mMockPmInternal.getSharedUserPackages(any(Integer.class))).thenReturn( + actorSharedSettingPackages + ); final AppsFilter appsFilter = new AppsFilter( mStateProvider, mFeatureConfigMock, @@ -949,20 +963,18 @@ public class AppsFilterTest { return Collections.emptyMap(); } }, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID); SharedUserSetting actorSharedSetting = new SharedUserSetting("actorSharedUser", targetSetting.getFlags(), targetSetting.getPrivateFlags()); + actorSharedSetting.mAppId = 100; /* mimic a valid sharedUserSetting.mAppId */ PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_APPID); - simulateAddPackage(appsFilter, actorOne, DUMMY_ACTOR_APPID, - null /*settingBuilder*/, actorSharedSetting); - simulateAddPackage(appsFilter, actorTwo, DUMMY_ACTOR_APPID, - null /*settingBuilder*/, actorSharedSetting); - + simulateAddPackage(ps1, appsFilter, actorSharedSetting); + simulateAddPackage(ps2, appsFilter, actorSharedSetting); // actorTwo can see both target and overlay assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSharedSetting, @@ -975,7 +987,7 @@ public class AppsFilterTest { public void testInitiatingApp_DoesntFilter() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -993,7 +1005,7 @@ public class AppsFilterTest { public void testUninstalledInitiatingApp_Filters() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -1011,7 +1023,7 @@ public class AppsFilterTest { public void testOriginatingApp_Filters() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); watcher.register(); simulateAddBasicAndroid(appsFilter); @@ -1036,7 +1048,7 @@ public class AppsFilterTest { public void testInstallingApp_DoesntFilter() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); watcher.register(); simulateAddBasicAndroid(appsFilter); @@ -1061,7 +1073,7 @@ public class AppsFilterTest { public void testInstrumentation_DoesntFilter() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); watcher.register(); simulateAddBasicAndroid(appsFilter); @@ -1090,7 +1102,7 @@ public class AppsFilterTest { public void testWhoCanSee() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); watcher.register(); simulateAddBasicAndroid(appsFilter); @@ -1163,7 +1175,7 @@ public class AppsFilterTest { public void testOnChangeReport() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); final WatchableTester watcher = new WatchableTester(appsFilter, "onChange"); watcher.register(); simulateAddBasicAndroid(appsFilter); @@ -1236,7 +1248,7 @@ public class AppsFilterTest { public void testOnChangeReportedFilter() throws Exception { final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null, - mMockExecutor); + mMockExecutor, mMockPmInternal); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); final WatchableTester watcher = new WatchableTester(appsFilter, "onChange filter"); @@ -1292,8 +1304,15 @@ public class AppsFilterTest { private PackageSetting simulateAddPackage(AppsFilter filter, ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action, @Nullable SharedUserSetting sharedUserSetting) { - AndroidPackage newPkg = ((ParsedPackage) newPkgBuilder.hideAsParsed()).hideAsFinal(); + final PackageSetting setting = + getPackageSettingFromParsingPackage(newPkgBuilder, appId, action); + simulateAddPackage(setting, filter, sharedUserSetting); + return setting; + } + private PackageSetting getPackageSettingFromParsingPackage(ParsingPackage newPkgBuilder, + int appId, @Nullable WithSettingBuilder action) { + AndroidPackage newPkg = ((ParsedPackage) newPkgBuilder.hideAsParsed()).hideAsFinal(); final PackageSettingBuilder settingBuilder = new PackageSettingBuilder() .setPackage(newPkg) .setAppId(appId) @@ -1302,13 +1321,17 @@ public class AppsFilterTest { .setPVersionCode(1L); final PackageSetting setting = (action == null ? settingBuilder : action.withBuilder(settingBuilder)).build(); - mExisting.put(newPkg.getPackageName(), setting); + return setting; + } + + private void simulateAddPackage(PackageSetting setting, AppsFilter filter, + @Nullable SharedUserSetting sharedUserSetting) { + mExisting.put(setting.getPackageName(), setting); if (sharedUserSetting != null) { sharedUserSetting.addPackage(setting); - setting.setSharedUser(sharedUserSetting); + setting.setSharedUserAppId(sharedUserSetting.mAppId); } filter.addPackage(setting); - return setting; } private WithSettingBuilder withInstallSource(String initiatingPackageName, diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index ed4dee1792e5..8873f42ff03d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -70,6 +70,7 @@ import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import com.android.server.utils.Watchable; import com.android.server.utils.WatchableTester; import com.android.server.utils.WatchedArrayMap; +import com.android.server.utils.WatchedArraySet; import com.android.server.utils.Watcher; import com.google.common.truth.Truth; @@ -595,13 +596,13 @@ public class PackageManagerSettingsTests { watcher.verifyNoChangeReported("getEnabled"); // Enable/Disable a component - ArraySet<String> components = new ArraySet<String>(); + WatchedArraySet<String> components = new WatchedArraySet<String>(); String component1 = PACKAGE_NAME_1 + "/.Component1"; components.add(component1); ps.setDisabledComponents(components, 0); - ArraySet<String> componentsDisabled = ps.getDisabledComponents(0); + WatchedArraySet<String> componentsDisabled = ps.getDisabledComponents(0); assertThat(componentsDisabled.size(), is(1)); - assertThat(componentsDisabled.toArray()[0], is(component1)); + assertThat(componentsDisabled.untrackedStorage().toArray()[0], is(component1)); boolean hasEnabled = ps.getEnabledComponents(0) != null && ps.getEnabledComponents(1).size() > 0; assertThat(hasEnabled, is(false)); @@ -704,7 +705,7 @@ public class PackageManagerSettingsTests { null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, UUID.randomUUID()); - testPkgSetting01.copyPackageSetting(origPkgSetting01); + testPkgSetting01.copyPackageSetting(origPkgSetting01, true); verifySettingCopy(origPkgSetting01, testPkgSetting01); verifyUserStatesCopy(origPkgSetting01.readUserState(0), testPkgSetting01.readUserState(0)); @@ -722,6 +723,7 @@ public class PackageManagerSettingsTests { Settings.updatePackageSetting( testPkgSetting01, null /*disabledPkg*/, + null /*existingSharedUserSetting*/, null /*sharedUser*/, UPDATED_CODE_PATH /*codePath*/, null /*legacyNativeLibraryPath*/, @@ -757,6 +759,7 @@ public class PackageManagerSettingsTests { Settings.updatePackageSetting( testPkgSetting01, null /*disabledPkg*/, + null /*existingSharedUserSetting*/, null /*sharedUser*/, UPDATED_CODE_PATH /*codePath*/, null /*legacyNativeLibraryPath*/, @@ -792,6 +795,7 @@ public class PackageManagerSettingsTests { Settings.updatePackageSetting( testPkgSetting01, null /*disabledPkg*/, + null /*existingSharedUserSetting*/, testUserSetting01 /*sharedUser*/, UPDATED_CODE_PATH /*codePath*/, null /*legacyNativeLibraryPath*/, @@ -1148,8 +1152,6 @@ public class PackageManagerSettingsTests { assertThat(origPkgSetting.getRealName(), is(testPkgSetting.getRealName())); assertSame(origPkgSetting.getSecondaryCpuAbi(), testPkgSetting.getSecondaryCpuAbi()); assertThat(origPkgSetting.getSecondaryCpuAbi(), is(testPkgSetting.getSecondaryCpuAbi())); - assertSame(origPkgSetting.getSharedUser(), testPkgSetting.getSharedUser()); - assertThat(origPkgSetting.getSharedUser(), is(testPkgSetting.getSharedUser())); assertSame(origPkgSetting.getSignatures(), testPkgSetting.getSignatures()); assertThat(origPkgSetting.getSignatures(), is(testPkgSetting.getSignatures())); assertThat(origPkgSetting.getLastModifiedTime(), is(testPkgSetting.getLastModifiedTime())); diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java index 31bdec147a4f..952200d02de8 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java @@ -895,6 +895,7 @@ public class PackageParserTest { assertEquals(a.networkSecurityConfigRes, that.networkSecurityConfigRes); assertEquals(a.taskAffinity, that.taskAffinity); assertEquals(a.permission, that.permission); + assertEquals(a.getKnownActivityEmbeddingCerts(), that.getKnownActivityEmbeddingCerts()); assertEquals(a.processName, that.processName); assertEquals(a.className, that.className); assertEquals(a.manageSpaceActivityName, that.manageSpaceActivityName); diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java index a0edb85e64e1..4dc9612acda3 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java @@ -39,6 +39,7 @@ import com.android.server.pm.pkg.PackageStateUnserialized; import com.android.server.pm.pkg.PackageUserStateImpl; import com.android.server.pm.pkg.SuspendParams; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -238,9 +239,12 @@ public class PackageUserStateTest { PackageUserStateImpl testUserState2 = new PackageUserStateImpl(null, testUserState1); assertThat(testUserState1.equals(testUserState2), is(true)); - testUserState2.setSuspendParams(paramsMap2); - // Should not be equal since suspendParams maps are different - assertThat(testUserState1.equals(testUserState2), is(false)); + try { + testUserState2.setSuspendParams(paramsMap2); + Assert.fail("Changing sealed snapshot of suspendParams should throw"); + } catch (IllegalStateException e) { + assertThat(e.getMessage().contains("attempt to change a sealed object"), is(true)); + } } @Test diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanRequestBuilder.java b/services/tests/servicestests/src/com/android/server/pm/ScanRequestBuilder.java index 54bfe0199975..9962a3c8b71f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ScanRequestBuilder.java +++ b/services/tests/servicestests/src/com/android/server/pm/ScanRequestBuilder.java @@ -26,6 +26,7 @@ import com.android.server.pm.parsing.pkg.ParsedPackage; class ScanRequestBuilder { private final ParsedPackage mPkg; private AndroidPackage mOldPkg; + private SharedUserSetting mOldSharedUserSetting; private SharedUserSetting mSharedUserSetting; private PackageSetting mPkgSetting; private PackageSetting mDisabledPkgSetting; @@ -52,6 +53,11 @@ class ScanRequestBuilder { return this; } + public ScanRequestBuilder setOldSharedUserSetting(SharedUserSetting oldSharedUserSetting) { + this.mOldSharedUserSetting = oldSharedUserSetting; + return this; + } + public ScanRequestBuilder setPkgSetting(PackageSetting pkgSetting) { this.mPkgSetting = pkgSetting; return this; @@ -110,8 +116,8 @@ class ScanRequestBuilder { ScanRequest build() { return new ScanRequest( - mPkg, mSharedUserSetting, mOldPkg, mPkgSetting, mDisabledPkgSetting, - mOriginalPkgSetting, mRealPkgName, mParseFlags, mScanFlags, mIsPlatformPackage, - mUser, mCpuAbiOverride); + mPkg, mOldSharedUserSetting, mOldPkg, mPkgSetting, mSharedUserSetting, + mDisabledPkgSetting, mOriginalPkgSetting, mRealPkgName, mParseFlags, mScanFlags, + mIsPlatformPackage, mUser, mCpuAbiOverride); } } diff --git a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java index 4ae9613a36d3..00a794446742 100644 --- a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java @@ -47,6 +47,7 @@ import androidx.test.InstrumentationRegistry; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.LocalServices; +import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.testutils.OffsettableClock; import org.junit.After; @@ -80,6 +81,8 @@ public class LowPowerStandbyControllerTest { private IPowerManager mIPowerManagerMock; @Mock private PowerManagerInternal mPowerManagerInternalMock; + @Mock + private NetworkPolicyManagerInternal mNetworkPolicyManagerInternal; @Before public void setUp() throws Exception { @@ -90,6 +93,7 @@ public class LowPowerStandbyControllerTest { PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, null, null); when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock); + addLocalServiceMock(NetworkPolicyManagerInternal.class, mNetworkPolicyManagerInternal); when(mIPowerManagerMock.isInteractive()).thenReturn(true); @@ -121,6 +125,7 @@ public class LowPowerStandbyControllerTest { public void tearDown() throws Exception { LocalServices.removeServiceForTest(PowerManagerInternal.class); LocalServices.removeServiceForTest(LowPowerStandbyControllerInternal.class); + LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class); } @Test @@ -130,6 +135,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isFalse(); verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); + verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean()); } @Test @@ -142,6 +148,7 @@ public class LowPowerStandbyControllerTest { awaitStandbyTimeoutAlarm(); assertThat(mController.isActive()).isTrue(); verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true); + verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(true); } private void awaitStandbyTimeoutAlarm() { @@ -169,6 +176,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isFalse(); verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); + verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean()); } @Test @@ -182,6 +190,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isTrue(); verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true); + verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(true); } @Test @@ -197,6 +206,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isFalse(); verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); + verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean()); } private void verifyStandbyAlarmCancelled() { @@ -221,6 +231,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isFalse(); verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false); + verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(false); } @Test @@ -238,6 +249,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isFalse(); verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false); + verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(false); } @Test @@ -255,6 +267,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isTrue(); verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(false); + verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(false); } @Test @@ -273,6 +286,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isTrue(); verify(mPowerManagerInternalMock, times(2)).setLowPowerStandbyActive(true); + verify(mNetworkPolicyManagerInternal, times(2)).setLowPowerStandbyActive(true); } @Test @@ -285,6 +299,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isFalse(); verify(mAlarmManagerMock, never()).setExact(anyInt(), anyLong(), anyString(), any(), any()); verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); + verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean()); } @Test @@ -350,10 +365,12 @@ public class LowPowerStandbyControllerTest { service.addToAllowlist(10); mTestLooper.dispatchAll(); verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {10}); + verify(mNetworkPolicyManagerInternal).setLowPowerStandbyAllowlist(new int[] {10}); service.removeFromAllowlist(10); mTestLooper.dispatchAll(); verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {}); + verify(mNetworkPolicyManagerInternal).setLowPowerStandbyAllowlist(new int[] {}); } @Test @@ -366,12 +383,14 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isTrue(); verify(mPowerManagerInternalMock).setLowPowerStandbyActive(true); + verify(mNetworkPolicyManagerInternal).setLowPowerStandbyActive(true); mController.forceActive(false); mTestLooper.dispatchAll(); assertThat(mController.isActive()).isFalse(); verify(mPowerManagerInternalMock).setLowPowerStandbyActive(false); + verify(mNetworkPolicyManagerInternal).setLowPowerStandbyActive(false); } private void setLowPowerStandbySupportedConfig(boolean supported) { 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 ef9494aca4a5..dfcab2b849d6 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -102,6 +102,7 @@ import static org.mockito.Mockito.when; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; @@ -241,6 +242,7 @@ import java.util.function.Consumer; @SmallTest @RunWith(AndroidTestingRunner.class) +@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service. @RunWithLooper public class NotificationManagerServiceTest extends UiServiceTestCase { private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"; @@ -1338,7 +1340,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment); NotificationManagerService.PostNotificationRunnable runnable = - mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime()); + mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); @@ -1359,7 +1362,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(IMPORTANCE_NONE); NotificationManagerService.PostNotificationRunnable runnable = - mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime()); + mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); @@ -3921,7 +3925,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false); mService.addEnqueuedNotification(r); NotificationManagerService.PostNotificationRunnable runnable = - mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime()); + mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); @@ -3938,7 +3943,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r = generateNotificationRecord(mTestNotificationChannel, 0, null, false); mService.addEnqueuedNotification(r); NotificationManagerService.PostNotificationRunnable runnable = - mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime()); + mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); @@ -3954,7 +3960,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addEnqueuedNotification(r); NotificationManagerService.PostNotificationRunnable runnable = - mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime()); + mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); @@ -3967,12 +3974,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r.setCriticality(CriticalNotificationExtractor.CRITICAL_LOW); mService.addEnqueuedNotification(r); NotificationManagerService.PostNotificationRunnable runnable = - mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime()); + mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); runnable.run(); r = generateNotificationRecord(mTestNotificationChannel, 1, null, false); r.setCriticality(CriticalNotificationExtractor.CRITICAL); - runnable = mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime()); + runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); mService.addEnqueuedNotification(r); runnable.run(); @@ -4006,6 +4015,63 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testMediaStyleRemote_hasPermission() throws RemoteException { + String deviceName = "device"; + when(mPackageManager.checkPermission( + eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt())) + .thenReturn(PERMISSION_GRANTED); + Notification.MediaStyle style = new Notification.MediaStyle(); + style.setRemotePlaybackInfo(deviceName, 0, null); + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setStyle(style); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, + "testMediaStyleRemoteHasPermission", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + NotificationRecord posted = mService.findNotificationLocked( + PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); + Bundle extras = posted.getNotification().extras; + + assertTrue(extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)); + assertEquals(deviceName, extras.getString(Notification.EXTRA_MEDIA_REMOTE_DEVICE)); + } + + @Test + public void testMediaStyleRemote_noPermission() throws RemoteException { + String deviceName = "device"; + when(mPackageManager.checkPermission( + eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt())) + .thenReturn(PERMISSION_DENIED); + Notification.MediaStyle style = new Notification.MediaStyle(); + style.setRemotePlaybackInfo(deviceName, 0, null); + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setStyle(style); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, + "testMediaStyleRemoteNoPermission", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + NotificationRecord posted = mService.findNotificationLocked( + PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); + + assertFalse(posted.getNotification().extras + .containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)); + } + + @Test public void testGetNotificationCountLocked() { String sampleTagToExclude = null; int sampleIdToExclude = 0; @@ -4416,6 +4482,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManagerService.PostNotificationRunnable runnable = mService.new PostNotificationRunnable(original.getKey(), + original.getSbn().getPackageName(), + original.getUid(), SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); @@ -4438,6 +4506,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManagerService.PostNotificationRunnable runnable = mService.new PostNotificationRunnable(update.getKey(), + update.getSbn().getPackageName(), + update.getUid(), SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); @@ -6533,7 +6603,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertNull(update.getSbn().getNotification().getSmallIcon()); NotificationManagerService.PostNotificationRunnable runnable = - mService.new PostNotificationRunnable(update.getKey(), + mService.new PostNotificationRunnable(update.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java index fec5405c3390..d922f403c3c7 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java @@ -657,7 +657,8 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { when(mPermissionHelper.hasPermission(anyInt())).thenReturn(false); NotificationManagerService.PostNotificationRunnable runnable = - mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime()); + mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); @@ -790,7 +791,8 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { mService.addEnqueuedNotification(r); NotificationManagerService.PostNotificationRunnable runnable = - mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime()); + mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); @@ -806,7 +808,8 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mService.addEnqueuedNotification(r); - runnable = mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime()); + runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); @@ -822,7 +825,8 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mService.addEnqueuedNotification(r); - runnable = mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime()); + runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java index 3b6718207c83..b71323b4c3fc 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java @@ -15,6 +15,7 @@ */ package com.android.server.notification; +import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; @@ -233,8 +234,8 @@ public class PermissionHelperTest extends UiServiceTestCase { public void testSetNotificationPermission_grantReviewRequired() throws Exception { mPermissionHelper.setNotificationPermission("pkg", 10, true, false, true); - verify(mPermManager).grantRuntimePermission( - "pkg", Manifest.permission.POST_NOTIFICATIONS, 10); + verify(mPermManager).revokeRuntimePermission( + "pkg", Manifest.permission.POST_NOTIFICATIONS, 10, "PermissionHelper"); verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS, FLAG_PERMISSION_REVIEW_REQUIRED, FLAG_PERMISSION_REVIEW_REQUIRED, true, 10); } @@ -245,10 +246,43 @@ public class PermissionHelperTest extends UiServiceTestCase { "pkg", 10, true, false); mPermissionHelper.setNotificationPermission(pkgPerm); + verify(mPermManager).revokeRuntimePermission( + "pkg", Manifest.permission.POST_NOTIFICATIONS, 10, "PermissionHelper"); + verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS, + FLAG_PERMISSION_REVIEW_REQUIRED, FLAG_PERMISSION_REVIEW_REQUIRED, true, 10); + } + + @Test + public void testSetNotificationPermission_pkgPerm_notUserSet_grantedByDefaultPermNotSet() + throws Exception { + when(mPermManager.getPermissionFlags(anyString(), + eq(Manifest.permission.POST_NOTIFICATIONS), + anyInt())).thenReturn(FLAG_PERMISSION_GRANTED_BY_DEFAULT); + PermissionHelper.PackagePermission pkgPerm = new PermissionHelper.PackagePermission( + "pkg", 10, true, false); + + mPermissionHelper.setNotificationPermission(pkgPerm); + verify(mPermManager, never()).revokeRuntimePermission( + anyString(), anyString(), anyInt(), anyString()); + verify(mPermManager, never()).updatePermissionFlags( + anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt()); + } + + @Test + public void testSetNotificationPermission_pkgPerm_userSet_grantedByDefaultPermSet() + throws Exception { + when(mPermManager.getPermissionFlags(anyString(), + eq(Manifest.permission.POST_NOTIFICATIONS), + anyInt())).thenReturn(FLAG_PERMISSION_GRANTED_BY_DEFAULT); + PermissionHelper.PackagePermission pkgPerm = new PermissionHelper.PackagePermission( + "pkg", 10, true, true); + + mPermissionHelper.setNotificationPermission(pkgPerm); verify(mPermManager).grantRuntimePermission( "pkg", Manifest.permission.POST_NOTIFICATIONS, 10); verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS, - FLAG_PERMISSION_REVIEW_REQUIRED, FLAG_PERMISSION_REVIEW_REQUIRED, true, 10); + FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_REVIEW_REQUIRED, + FLAG_PERMISSION_USER_SET, true, 10); } @Test @@ -283,6 +317,32 @@ public class PermissionHelperTest extends UiServiceTestCase { } @Test + public void testSetNotificationPermission_SystemFixedPermNotSet() throws Exception { + when(mPermManager.getPermissionFlags(anyString(), + eq(Manifest.permission.POST_NOTIFICATIONS), + anyInt())).thenReturn(FLAG_PERMISSION_SYSTEM_FIXED); + + mPermissionHelper.setNotificationPermission("pkg", 10, false, true); + verify(mPermManager, never()).revokeRuntimePermission( + anyString(), anyString(), anyInt(), anyString()); + verify(mPermManager, never()).updatePermissionFlags( + anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt()); + } + + @Test + public void testSetNotificationPermission_PolicyFixedPermNotSet() throws Exception { + when(mPermManager.getPermissionFlags(anyString(), + eq(Manifest.permission.POST_NOTIFICATIONS), + anyInt())).thenReturn(FLAG_PERMISSION_POLICY_FIXED); + + mPermissionHelper.setNotificationPermission("pkg", 10, false, true); + verify(mPermManager, never()).revokeRuntimePermission( + anyString(), anyString(), anyInt(), anyString()); + verify(mPermManager, never()).updatePermissionFlags( + anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt()); + } + + @Test public void testIsPermissionFixed() throws Exception { when(mPermManager.getPermissionFlags(anyString(), eq(Manifest.permission.POST_NOTIFICATIONS), 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 fc4ab22a8f0e..8f1eed89647c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -789,6 +789,73 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testReadXml_oldXml_migration_NoUid() throws Exception { + when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + + when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID); + String xml = "<ranking version=\"2\">\n" + + "<package name=\"something\" show_badge=\"true\">\n" + + "<channel id=\"idn\" name=\"name\" importance=\"2\"/>\n" + + "</package>\n" + + "</ranking>\n"; + NotificationChannel idn = new NotificationChannel("idn", "name", IMPORTANCE_LOW); + idn.setSound(null, new AudioAttributes.Builder() + .setUsage(USAGE_NOTIFICATION) + .setContentType(CONTENT_TYPE_SONIFICATION) + .setFlags(0) + .build()); + idn.setShowBadge(false); + + loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM); + verify(mPermissionHelper, never()).setNotificationPermission(any()); + + when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(1234); + final ApplicationInfo app = new ApplicationInfo(); + app.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1; + when(mPm.getApplicationInfoAsUser(eq("something"), anyInt(), anyInt())).thenReturn(app); + + mHelper.onPackagesChanged(false, 0, new String[] {"something"}, new int[] {1234}); + + + verify(mPermissionHelper, times(1)).setNotificationPermission(any()); + } + + @Test + public void testReadXml_newXml_noMigration_NoUid() throws Exception { + when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + + when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID); + String xml = "<ranking version=\"3\">\n" + + "<package name=\"something\" show_badge=\"true\">\n" + + "<channel id=\"idn\" name=\"name\" importance=\"2\"/>\n" + + "</package>\n" + + "</ranking>\n"; + NotificationChannel idn = new NotificationChannel("idn", "name", IMPORTANCE_LOW); + idn.setSound(null, new AudioAttributes.Builder() + .setUsage(USAGE_NOTIFICATION) + .setContentType(CONTENT_TYPE_SONIFICATION) + .setFlags(0) + .build()); + idn.setShowBadge(false); + + loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM); + + when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(1234); + final ApplicationInfo app = new ApplicationInfo(); + app.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1; + when(mPm.getApplicationInfoAsUser(eq("something"), anyInt(), anyInt())).thenReturn(app); + + mHelper.onPackagesChanged(false, 0, new String[] {"something"}, new int[] {1234}); + + + verify(mPermissionHelper, never()).setNotificationPermission(any()); + } + + @Test public void testChannelXmlForNonBackup_postMigration() throws Exception { when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 3298d1184cdc..043bc0700ab4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -121,6 +121,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.PersistableBundle; @@ -3358,6 +3359,29 @@ public class ActivityRecordTests extends WindowTestsBase { noProcActivity.mInputDispatchingTimeoutMillis); } + @Test + public void testEnsureActivitiesVisibleAnotherUserTasks() { + // Create an activity with hierarchy: + // RootTask + // - TaskFragment + // - Activity + DisplayContent display = createNewDisplay(); + Task rootTask = createTask(display); + ActivityRecord activity = createActivityRecord(rootTask); + final TaskFragment taskFragment = new TaskFragment(mAtm, new Binder(), + true /* createdByOrganizer */, true /* isEmbedded */); + activity.getTask().addChild(taskFragment, POSITION_TOP); + activity.reparent(taskFragment, POSITION_TOP); + + // Ensure the activity visibility is updated even it is not shown to current user. + activity.mVisibleRequested = true; + doReturn(false).when(activity).showToCurrentUser(); + spyOn(taskFragment); + doReturn(false).when(taskFragment).shouldBeVisible(any()); + display.ensureActivitiesVisible(null, 0, false, false); + assertFalse(activity.mVisibleRequested); + } + private ICompatCameraControlCallback getCompatCameraControlCallback() { return new ICompatCameraControlCallback.Stub() { @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index c58bf3bf1d56..c8e48a48d3fb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -75,6 +75,7 @@ import android.content.pm.ActivityInfo.WindowLayout; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManagerInternal; +import android.content.pm.SigningDetails; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; @@ -84,9 +85,11 @@ import android.platform.test.annotations.Presubmit; import android.service.voice.IVoiceInteractionSession; import android.util.Pair; import android.view.Gravity; +import android.window.TaskFragmentOrganizerToken; import androidx.test.filters.SmallTest; +import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.wm.LaunchParamsController.LaunchParamsModifier; import com.android.server.wm.utils.MockTracker; @@ -94,6 +97,10 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + /** * Tests for the {@link ActivityStarter} class. * @@ -1109,7 +1116,96 @@ public class ActivityStarterTests extends WindowTestsBase { } @Test - public void testStartActivityInner_inTaskFragment() { + public void testStartActivityInner_inTaskFragment_failsByDefault() { + final ActivityStarter starter = prepareStarter(0, false); + final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build(); + final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final TaskFragment taskFragment = new TaskFragment(mAtm, sourceRecord.token, + true /* createdByOrganizer */); + sourceRecord.getTask().addChild(taskFragment, POSITION_TOP); + + starter.startActivityInner( + /* r */targetRecord, + /* sourceRecord */ sourceRecord, + /* voiceSession */null, + /* voiceInteractor */ null, + /* startFlags */ 0, + /* doResume */true, + /* options */null, + /* inTask */null, + /* inTaskFragment */ taskFragment, + /* restrictedBgActivity */false, + /* intentGrants */null); + + assertFalse(taskFragment.hasChild()); + } + + @Test + public void testStartActivityInner_inTaskFragment_allowedForSameUid() { + final ActivityStarter starter = prepareStarter(0, false); + final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build(); + final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final TaskFragment taskFragment = new TaskFragment(mAtm, sourceRecord.token, + true /* createdByOrganizer */); + sourceRecord.getTask().addChild(taskFragment, POSITION_TOP); + + taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class), + targetRecord.getUid(), "test_process_name"); + + starter.startActivityInner( + /* r */targetRecord, + /* sourceRecord */ sourceRecord, + /* voiceSession */null, + /* voiceInteractor */ null, + /* startFlags */ 0, + /* doResume */true, + /* options */null, + /* inTask */null, + /* inTaskFragment */ taskFragment, + /* restrictedBgActivity */false, + /* intentGrants */null); + + assertTrue(taskFragment.hasChild()); + } + + @Test + public void testStartActivityInner_inTaskFragment_allowedTrustedCertUid() { + final ActivityStarter starter = prepareStarter(0, false); + final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build(); + final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final TaskFragment taskFragment = new TaskFragment(mAtm, sourceRecord.token, + true /* createdByOrganizer */); + sourceRecord.getTask().addChild(taskFragment, POSITION_TOP); + + taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class), + 12345, "test_process_name"); + AndroidPackage androidPackage = mock(AndroidPackage.class); + doReturn(androidPackage).when(mMockPackageManager).getPackage(eq(12345)); + + Set<String> certs = new HashSet(Arrays.asList("test_cert1", "test_cert1")); + targetRecord.info.setKnownActivityEmbeddingCerts(certs); + SigningDetails signingDetails = mock(SigningDetails.class); + doReturn(true).when(signingDetails).hasAncestorOrSelfWithDigest(any()); + doReturn(signingDetails).when(androidPackage).getSigningDetails(); + + starter.startActivityInner( + /* r */targetRecord, + /* sourceRecord */ sourceRecord, + /* voiceSession */null, + /* voiceInteractor */ null, + /* startFlags */ 0, + /* doResume */true, + /* options */null, + /* inTask */null, + /* inTaskFragment */ taskFragment, + /* restrictedBgActivity */false, + /* intentGrants */null); + + assertTrue(taskFragment.hasChild()); + } + + @Test + public void testStartActivityInner_inTaskFragment_allowedForUntrustedEmbedding() { final ActivityStarter starter = prepareStarter(0, false); final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build(); final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build(); @@ -1117,6 +1213,8 @@ public class ActivityStarterTests extends WindowTestsBase { true /* createdByOrganizer */); sourceRecord.getTask().addChild(taskFragment, POSITION_TOP); + targetRecord.info.flags |= ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; + starter.startActivityInner( /* r */targetRecord, /* sourceRecord */ sourceRecord, diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index efc9a49023b1..9f7130e45483 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -447,25 +447,6 @@ public class DisplayContentTests extends WindowTestsBase { } /** - * This tests override configuration updates for display content. - */ - @Test - public void testDisplayOverrideConfigUpdate() { - final Configuration currentOverrideConfig = - mDisplayContent.getRequestedOverrideConfiguration(); - - // Create new, slightly changed override configuration and apply it to the display. - final Configuration newOverrideConfig = new Configuration(currentOverrideConfig); - newOverrideConfig.densityDpi += 120; - newOverrideConfig.fontScale += 0.3; - - mWm.setNewDisplayOverrideConfiguration(newOverrideConfig, mDisplayContent); - - // Check that override config is applied. - assertEquals(newOverrideConfig, mDisplayContent.getRequestedOverrideConfiguration()); - } - - /** * This tests global configuration updates when default display config is updated. */ @Test @@ -478,7 +459,8 @@ public class DisplayContentTests extends WindowTestsBase { newOverrideConfig.densityDpi += 120; newOverrideConfig.fontScale += 0.3; - mWm.setNewDisplayOverrideConfiguration(newOverrideConfig, defaultDisplay); + defaultDisplay.updateDisplayOverrideConfigurationLocked(newOverrideConfig, + null /* starting */, false /* deferResume */, null /* result */); // Check that global configuration is updated, as we've updated default display's config. Configuration globalConfig = mWm.mRoot.getConfiguration(); @@ -486,7 +468,8 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(newOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */); // Return back to original values. - mWm.setNewDisplayOverrideConfiguration(currentConfig, defaultDisplay); + defaultDisplay.updateDisplayOverrideConfigurationLocked(currentConfig, + null /* starting */, false /* deferResume */, null /* result */); globalConfig = mWm.mRoot.getConfiguration(); assertEquals(currentConfig.densityDpi, globalConfig.densityDpi); assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index 87f76fab3bf8..e0911909f47e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -16,9 +16,6 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; @@ -632,10 +629,11 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { @Test public void testAttachNavBarInSplitScreenMode() { setupForShouldAttachNavBarDuringTransition(); - final ActivityRecord primary = createActivityRecordWithParentTask(mDefaultDisplay, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD); - final ActivityRecord secondary = createActivityRecordWithParentTask(mDefaultDisplay, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD); + TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm); + final ActivityRecord primary = createActivityRecordWithParentTask( + organizer.createTaskToPrimary(true)); + final ActivityRecord secondary = createActivityRecordWithParentTask( + organizer.createTaskToSecondary(true)); final ActivityRecord homeActivity = createHomeActivity(); homeActivity.setVisibility(true); initializeRecentsAnimationController(mController, homeActivity); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 409572847623..05eedcf21d4f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -24,6 +24,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.os.Process.SYSTEM_UID; import static android.view.View.VISIBLE; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; @@ -1531,7 +1532,11 @@ class WindowTestsBase extends SystemServiceTestsBase { final Rect primaryBounds = new Rect(); final Rect secondaryBounds = new Rect(); - display.getBounds().splitVertically(primaryBounds, secondaryBounds); + if (display.getConfiguration().orientation == ORIENTATION_LANDSCAPE) { + display.getBounds().splitVertically(primaryBounds, secondaryBounds); + } else { + display.getBounds().splitHorizontally(primaryBounds, secondaryBounds); + } mPrimary.setBounds(primaryBounds); mSecondary.setBounds(secondaryBounds); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 8acd3c7c1c36..84bd98347dab 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -45,7 +45,6 @@ import android.os.IBinder; import android.os.IRemoteCallback; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; -import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SharedMemory; @@ -278,8 +277,10 @@ final class HotwordDetectionConnection { mRemoteHotwordDetectionService.unbind(); LocalServices.getService(PermissionManagerServiceInternal.class) .setHotwordDetectionServiceProvider(null); + if (mIdentity != null) { + removeServiceUidForAudioPolicy(mIdentity.getIsolatedUid()); + } mIdentity = null; - updateServiceUidForAudioPolicy(Process.INVALID_UID); mCancellationTaskFuture.cancel(/* may interrupt */ true); if (mAudioFlinger != null) { mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0); @@ -909,17 +910,27 @@ final class HotwordDetectionConnection { LocalServices.getService(PermissionManagerServiceInternal.class) .setHotwordDetectionServiceProvider(() -> uid); mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid); - updateServiceUidForAudioPolicy(uid); + addServiceUidForAudioPolicy(uid); } })); } - private void updateServiceUidForAudioPolicy(int uid) { + private void addServiceUidForAudioPolicy(int uid) { + mScheduledExecutorService.execute(() -> { + AudioManagerInternal audioManager = + LocalServices.getService(AudioManagerInternal.class); + if (audioManager != null) { + audioManager.addAssistantServiceUid(uid); + } + }); + } + + private void removeServiceUidForAudioPolicy(int uid) { mScheduledExecutorService.execute(() -> { - final AudioManagerInternal audioManager = + AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class); if (audioManager != null) { - audioManager.setHotwordDetectionServiceUid(uid); + audioManager.removeAssistantServiceUid(uid); } }); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 1285a84ea752..8cbbe947d32d 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -263,6 +263,25 @@ public class VoiceInteractionManagerService extends SystemService { } @Override + public String getVoiceInteractorPackageName(IBinder callingVoiceInteractor) { + VoiceInteractionManagerServiceImpl impl = + VoiceInteractionManagerService.this.mServiceStub.mImpl; + if (impl == null) { + return null; + } + VoiceInteractionSessionConnection session = + impl.mActiveSession; + if (session == null) { + return null; + } + IVoiceInteractor voiceInteractor = session.mInteractor; + if (voiceInteractor == null || voiceInteractor.asBinder() != callingVoiceInteractor) { + return null; + } + return session.mSessionComponentName.getPackageName(); + } + + @Override public HotwordDetectionServiceIdentity getHotwordDetectionServiceIdentity() { // IMPORTANT: This is called when performing permission checks; do not lock! diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java index 30ed7c287421..8d9252019538 100644 --- a/telephony/java/android/service/euicc/EuiccService.java +++ b/telephony/java/android/service/euicc/EuiccService.java @@ -524,7 +524,10 @@ public abstract class EuiccService extends Service { * defined in {@code RESOLVABLE_ERROR_}. A subclass should override this method. Otherwise, * this method does nothing and returns null by default. * @see android.telephony.euicc.EuiccManager#downloadSubscription + * @deprecated prefer {@link #onDownloadSubscription(int, int, + * DownloadableSubscription, boolean, boolean, Bundle)} */ + @Deprecated public DownloadSubscriptionResult onDownloadSubscription(int slotId, @NonNull DownloadableSubscription subscription, boolean switchAfterDownload, boolean forceDeactivateSim, @Nullable Bundle resolvedBundle) { @@ -534,6 +537,37 @@ public abstract class EuiccService extends Service { /** * Download the given subscription. * + * @param slotIndex Index of the SIM slot to use for the operation. + * @param portIndex Index of the port from the slot. portIndex is used when + * switchAfterDownload is set to {@code true}, otherwise download is port agnostic. + * @param subscription The subscription to download. + * @param switchAfterDownload If true, the subscription should be enabled upon successful + * download. + * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the + * eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM} + * should be returned to allow the user to consent to this operation first. + * @param resolvedBundle The bundle containing information on resolved errors. It can contain + * a string of confirmation code for the key {@link #EXTRA_RESOLUTION_CONFIRMATION_CODE}, + * and a boolean for key {@link #EXTRA_RESOLUTION_ALLOW_POLICY_RULES} indicating whether + * the user allows profile policy rules or not. + * @return a DownloadSubscriptionResult instance including a result code, a resolvable errors + * bit map, and original the card Id. The result code may be one of the predefined + * {@code RESULT_} constants or any implementation-specific code starting with + * {@link #RESULT_FIRST_USER}. The resolvable error bit map can be either 0 or values + * defined in {@code RESOLVABLE_ERROR_}. + * @see android.telephony.euicc.EuiccManager#downloadSubscription + */ + @NonNull + public DownloadSubscriptionResult onDownloadSubscription(int slotIndex, int portIndex, + @NonNull DownloadableSubscription subscription, boolean switchAfterDownload, + boolean forceDeactivateSim, @NonNull Bundle resolvedBundle) { + // stub implementation, LPA needs to implement this + throw new UnsupportedOperationException("LPA must override onDownloadSubscription"); + } + + /** + * Download the given subscription. + * * @param slotId ID of the SIM slot to use for the operation. * @param subscription The subscription to download. * @param switchAfterDownload If true, the subscription should be enabled upon successful @@ -701,7 +735,8 @@ public abstract class EuiccService extends Service { */ private class IEuiccServiceWrapper extends IEuiccService.Stub { @Override - public void downloadSubscription(int slotId, DownloadableSubscription subscription, + public void downloadSubscription(int slotId, int portIndex, + DownloadableSubscription subscription, boolean switchAfterDownload, boolean forceDeactivateSim, Bundle resolvedBundle, IDownloadSubscriptionCallback callback) { mExecutor.execute(new Runnable() { diff --git a/telephony/java/android/service/euicc/IEuiccService.aidl b/telephony/java/android/service/euicc/IEuiccService.aidl index 030e11aee993..6b0397d67015 100644 --- a/telephony/java/android/service/euicc/IEuiccService.aidl +++ b/telephony/java/android/service/euicc/IEuiccService.aidl @@ -35,9 +35,9 @@ import android.os.Bundle; /** @hide */ oneway interface IEuiccService { - void downloadSubscription(int slotId, in DownloadableSubscription subscription, - boolean switchAfterDownload, boolean forceDeactivateSim, in Bundle resolvedBundle, - in IDownloadSubscriptionCallback callback); + void downloadSubscription(int slotId, int portIndex, in DownloadableSubscription subscription, + boolean switchAfterDownload, boolean forceDeactivateSim, in Bundle resolvedBundle, + in IDownloadSubscriptionCallback callback); void getDownloadableSubscriptionMetadata(int slotId, in DownloadableSubscription subscription, boolean forceDeactivateSim, in IGetDownloadableSubscriptionMetadataCallback callback); void getEid(int slotId, in IGetEidCallback callback); diff --git a/telephony/java/android/telephony/CallQuality.java b/telephony/java/android/telephony/CallQuality.java index d77bf672347a..9b12ae52e5cf 100644 --- a/telephony/java/android/telephony/CallQuality.java +++ b/telephony/java/android/telephony/CallQuality.java @@ -86,7 +86,7 @@ public final class CallQuality implements Parcelable { private int mNumDroppedRtpPackets; private long mMinPlayoutDelayMillis; private long mMaxPlayoutDelayMillis; - private int mNumRtpSidPacketsRx; + private int mNumRtpSidPacketsReceived; private int mNumRtpDuplicatePackets; /** @hide **/ @@ -110,7 +110,7 @@ public final class CallQuality implements Parcelable { mNumDroppedRtpPackets = in.readInt(); mMinPlayoutDelayMillis = in.readLong(); mMaxPlayoutDelayMillis = in.readLong(); - mNumRtpSidPacketsRx = in.readInt(); + mNumRtpSidPacketsReceived = in.readInt(); mNumRtpDuplicatePackets = in.readInt(); } @@ -352,8 +352,8 @@ public final class CallQuality implements Parcelable { * Returns the total number of RTP SID (Silence Insertion Descriptor) packets * received by this device for an ongoing call */ - public int getNumRtpSidPacketsRx() { - return mNumRtpSidPacketsRx; + public int getNumRtpSidPacketsReceived() { + return mNumRtpSidPacketsReceived; } /** @@ -417,7 +417,7 @@ public final class CallQuality implements Parcelable { + " numDroppedRtpPackets=" + mNumDroppedRtpPackets + " minPlayoutDelayMillis=" + mMinPlayoutDelayMillis + " maxPlayoutDelayMillis=" + mMaxPlayoutDelayMillis - + " numRtpSidPacketsRx=" + mNumRtpSidPacketsRx + + " numRtpSidPacketsReceived=" + mNumRtpSidPacketsReceived + " numRtpDuplicatePackets=" + mNumRtpDuplicatePackets + "}"; } @@ -444,7 +444,7 @@ public final class CallQuality implements Parcelable { mNumDroppedRtpPackets, mMinPlayoutDelayMillis, mMaxPlayoutDelayMillis, - mNumRtpSidPacketsRx, + mNumRtpSidPacketsReceived, mNumRtpDuplicatePackets); } @@ -479,7 +479,7 @@ public final class CallQuality implements Parcelable { && mNumDroppedRtpPackets == s.mNumDroppedRtpPackets && mMinPlayoutDelayMillis == s.mMinPlayoutDelayMillis && mMaxPlayoutDelayMillis == s.mMaxPlayoutDelayMillis - && mNumRtpSidPacketsRx == s.mNumRtpSidPacketsRx + && mNumRtpSidPacketsReceived == s.mNumRtpSidPacketsReceived && mNumRtpDuplicatePackets == s.mNumRtpDuplicatePackets); } @@ -513,7 +513,7 @@ public final class CallQuality implements Parcelable { dest.writeInt(mNumDroppedRtpPackets); dest.writeLong(mMinPlayoutDelayMillis); dest.writeLong(mMaxPlayoutDelayMillis); - dest.writeInt(mNumRtpSidPacketsRx); + dest.writeInt(mNumRtpSidPacketsReceived); dest.writeInt(mNumRtpDuplicatePackets); } @@ -562,7 +562,7 @@ public final class CallQuality implements Parcelable { private int mNumDroppedRtpPackets; private long mMinPlayoutDelayMillis; private long mMaxPlayoutDelayMillis; - private int mNumRtpSidPacketsRx; + private int mNumRtpSidPacketsReceived; private int mNumRtpDuplicatePackets; /** @@ -595,8 +595,11 @@ public final class CallQuality implements Parcelable { * @param callDuration the call duration in milliseconds * @return The same instance of the builder. */ - public @NonNull Builder setCallDuration(int callDuration) { - mCallDuration = callDuration; + // Newer builder includes guidelines compliant units; existing method does not. + @NonNull + @SuppressWarnings("MissingGetterMatchingBuilder") + public Builder setCallDurationMillis(int callDurationMillis) { + mCallDuration = callDurationMillis; return this; } @@ -675,8 +678,11 @@ public final class CallQuality implements Parcelable { * @param averageRoundTripTime average round trip delay in milliseconds * @return The same instance of the builder. */ - public @NonNull Builder setAverageRoundTripTime(int averageRoundTripTime) { - mAverageRoundTripTime = averageRoundTripTime; + // Newer builder includes guidelines compliant units; existing method does not. + @NonNull + @SuppressWarnings("MissingGetterMatchingBuilder") + public Builder setAverageRoundTripTimeMillis(int averageRoundTripTimeMillis) { + mAverageRoundTripTime = averageRoundTripTimeMillis; return this; } @@ -789,12 +795,12 @@ public final class CallQuality implements Parcelable { * Set the total number of RTP SID (Silence Insertion Descriptor) * packets received by this device for an ongoing call. * - * @param numRtpSidPacketsRx the total number of RTP SID packets received + * @param numRtpSidPacketsReceived the total number of RTP SID packets received * by this device for an ongoing call. * @return The same instance of the builder. */ - public @NonNull Builder setNumRtpSidPacketsRx(int numRtpSidPacketsRx) { - mNumRtpSidPacketsRx = numRtpSidPacketsRx; + public @NonNull Builder setNumRtpSidPacketsReceived(int numRtpSidPacketsReceived) { + mNumRtpSidPacketsReceived = numRtpSidPacketsReceived; return this; } @@ -838,7 +844,7 @@ public final class CallQuality implements Parcelable { callQuality.mNumDroppedRtpPackets = mNumDroppedRtpPackets; callQuality.mMinPlayoutDelayMillis = mMinPlayoutDelayMillis; callQuality.mMaxPlayoutDelayMillis = mMaxPlayoutDelayMillis; - callQuality.mNumRtpSidPacketsRx = mNumRtpSidPacketsRx; + callQuality.mNumRtpSidPacketsReceived = mNumRtpSidPacketsReceived; callQuality.mNumRtpDuplicatePackets = mNumRtpDuplicatePackets; return callQuality; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index a1e37dbabc8c..2c39863c74aa 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -3796,7 +3796,7 @@ public class CarrierConfigManager { /** * SMDP+ server address for downloading opportunistic eSIM profile. * FQDN (Fully Qualified Domain Name) of the SM-DP+ (e.g., smdp.gsma.com) restricted to the - * Alphanumeric mode character set defined in table 5 of ISO/IEC 18004 [15] excluding '$'. + * Alphanumeric mode character set defined in table 5 of ISO/IEC 18004 excluding '$'. */ public static final String KEY_SMDP_SERVER_ADDRESS_STRING = "smdp_server_address_string"; @@ -3840,6 +3840,17 @@ public class CarrierConfigManager { "opportunistic_carrier_ids_int_array"; /** + * Boolean configuration to control auto provisioning eSIM download in + * OpportunisticNetworkService using only WiFi or both WiFi/Data. + * True will download esim only via WiFi. + * False will use both WiFi and Data connection. + * + * @hide + */ + public static final String KEY_OPPORTUNISTIC_ESIM_DOWNLOAD_VIA_WIFI_ONLY_BOOL = + "opportunistic_esim_download_via_wifi_only_bool"; + + /** * Controls RSRP threshold at which OpportunisticNetworkService will decide whether * the opportunistic network is good enough for internet data. */ @@ -5219,7 +5230,7 @@ public class CarrierConfigManager { * <li>{@link #KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY}</li> * <li>{@link #KEY_CAPABILITY_TYPE_UT_INT_ARRAY}</li> * <li>{@link #KEY_CAPABILITY_TYPE_SMS_INT_ARRAY}</li> - * <li>{@link #KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY}</li> + * <li>{@link #KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY}</li> * </ul> * <p> The values are defined in * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech} @@ -5228,39 +5239,68 @@ public class CarrierConfigManager { KEY_PREFIX + "mmtel_requires_provisioning_bundle"; /** - * This MmTelFeature supports Voice calling (IR.92) + * List of different RAT technologies on which Provisioning for Voice calling (IR.92) + * is supported. * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE + * <p>Possible values are, + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} */ public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY = - KEY_PREFIX + "key_capability_type_voice_int_array"; + KEY_PREFIX + "capability_type_voice_int_array"; /** - * This MmTelFeature supports Video (IR.94) + * List of different RAT technologies on which Provisioning for Video Telephony (IR.94) + * is supported. * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO + * <p>Possible values are, + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} */ public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY = - KEY_PREFIX + "key_capability_type_video_int_array"; + KEY_PREFIX + "capability_type_video_int_array"; /** - * This MmTelFeature supports XCAP over Ut for supplementary services. (IR.92) + * List of different RAT technologies on which Provisioning for XCAP over Ut for + * supplementary services. (IR.92) is supported. * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT + * <p>Possible values are, + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} */ public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY = - KEY_PREFIX + "key_capability_type_ut_int_array"; + KEY_PREFIX + "capability_type_ut_int_array"; /** - * This MmTelFeature supports SMS (IR.92) + * List of different RAT technologies on which Provisioning for SMS (IR.92) is supported. * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS + * <p>Possible values are, + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} */ public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY = - KEY_PREFIX + "key_capability_type_sms_int_array"; + KEY_PREFIX + "capability_type_sms_int_array"; /** - * This MmTelFeature supports Call Composer (section 2.4 of RCC.20) + * List of different RAT technologies on which Provisioning for Call Composer + * (section 2.4 of RCC.20) is supported. * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_CALL_COMPOSER + * <p>Possible values are, + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} */ - public static final String KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY = - KEY_PREFIX + "key_capability_type_call_composer_int_array"; + public static final String KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY = + KEY_PREFIX + "capability_type_call_composer_int_array"; /** * A bundle which specifies the RCS capability and registration technology @@ -5283,9 +5323,14 @@ public class CarrierConfigManager { * framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS. * If not set, this RcsFeature should not service capability requests. * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE + * <p>Possible values are, + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} */ public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY = - KEY_PREFIX + "key_capability_type_options_uce_int_array"; + KEY_PREFIX + "capability_type_options_uce_int_array"; /** * This carrier supports User Capability Exchange using a presence server as defined by the @@ -5293,9 +5338,14 @@ public class CarrierConfigManager { * server. If not set, this RcsFeature should not publish capabilities or service capability * requests using presence. * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE + * <p>Possible values are, + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} */ public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY = - KEY_PREFIX + "key_capability_type_presence_uce_int_array"; + KEY_PREFIX + "capability_type_presence_uce_int_array"; private Ims() {} @@ -5337,16 +5387,13 @@ public class CarrierConfigManager { /** * @see #KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE */ - PersistableBundle mmtel_requires_provisioning_int_array = new PersistableBundle(); defaults.putPersistableBundle( - KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE, mmtel_requires_provisioning_int_array); - + KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE, new PersistableBundle()); /** * @see #KEY_RCS_REQUIRES_PROVISIONING_BUNDLE */ - PersistableBundle rcs_requires_provisioning_int_array = new PersistableBundle(); defaults.putPersistableBundle( - KEY_RCS_REQUIRES_PROVISIONING_BUNDLE, rcs_requires_provisioning_int_array); + KEY_RCS_REQUIRES_PROVISIONING_BUNDLE, new PersistableBundle()); defaults.putBoolean(KEY_GRUU_ENABLED_BOOL, true); defaults.putBoolean(KEY_SIP_OVER_IPSEC_ENABLED_BOOL, true); @@ -8790,6 +8837,7 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_ESIM_MAX_DOWNLOAD_RETRY_ATTEMPTS_INT, 5); sDefaults.putInt(KEY_ESIM_DOWNLOAD_RETRY_BACKOFF_TIMER_SEC_INT, 60); sDefaults.putIntArray(KEY_OPPORTUNISTIC_CARRIER_IDS_INT_ARRAY, new int[] {0}); + sDefaults.putBoolean(KEY_OPPORTUNISTIC_ESIM_DOWNLOAD_VIA_WIFI_ONLY_BOOL, false); /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_GOOD */ sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT, -108); /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_MODERATE */ diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 81bcf75a5214..613b0a667612 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -7118,7 +7118,14 @@ public class TelephonyManager { */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) public boolean iccCloseLogicalChannel(int channel) { - return iccCloseLogicalChannel(getSubId(), channel); + try { + return iccCloseLogicalChannel(getSubId(), channel); + } catch (IllegalStateException ex) { + Rlog.e(TAG, "iccCloseLogicalChannel IllegalStateException", ex); + } catch (IllegalArgumentException ex) { + Rlog.e(TAG, "iccCloseLogicalChannel IllegalArgumentException", ex); + } + return false; } /** diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java index f614988d1950..0a2bb3d16f24 100644 --- a/telephony/java/android/telephony/euicc/EuiccCardManager.java +++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java @@ -118,6 +118,9 @@ public class EuiccCardManager { /** Resets the default SM-DP+ address. */ public static final int RESET_OPTION_RESET_DEFAULT_SMDP_ADDRESS = 1 << 2; + /** Result code when the requested profile is not found */ + public static final int RESULT_PROFILE_NOT_FOUND = 1; + /** Result code of execution with no error. */ public static final int RESULT_OK = 0; @@ -130,9 +133,6 @@ public class EuiccCardManager { /** Result code indicating the caller is not the active LPA. */ public static final int RESULT_CALLER_NOT_ALLOWED = -3; - /** Result code when the requested profile is not found */ - public static final int RESULT_PROFILE_NOT_FOUND = -4; - /** * Callback to receive the result of an eUICC card API. * @@ -223,7 +223,9 @@ public class EuiccCardManager { } /** - * Requests the enabled profile for a given port on an eUicc. + * Requests the enabled profile for a given port on an eUicc. Callback with result code + * {@link RESULT_PROFILE_NOT_FOUND} and {@code NULL} EuiccProfile if there is no enabled + * profile on the target port. * * @param cardId The Id of the eUICC. * @param portIndex The portIndex to use. The port may be active or inactive. As long as the diff --git a/test-base/Android.bp b/test-base/Android.bp index 8be732452228..527159a78ebf 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -72,11 +72,16 @@ java_sdk_library { // Build the android.test.base_static library // ========================================== -// This is only intended for inclusion in the android.test.runner-minus-junit, -// robolectric_android-all-stub and repackaged.android.test.* libraries. +// This is only intended for use by the android.test.runner-minus-junit +// library. +// // Must not be used elsewhere. +// java_library_static { name: "android.test.base_static", + visibility: [ + "//frameworks/base/test-runner", + ], installable: false, srcs: [":android-test-base-sources"], @@ -91,28 +96,10 @@ java_library_static { sdk_version: "current", } -// Build the repackaged.android.test.base library -// ============================================== -// This contains repackaged versions of the classes from -// android.test.base. -java_library_static { - name: "repackaged.android.test.base", - - sdk_version: "current", - static_libs: ["android.test.base_static"], - - jarjar_rules: "jarjar-rules.txt", - // Pin java_version until jarjar is certified to support later versions. http://b/72703434 - java_version: "1.8", -} - // Build the android.test.base-minus-junit library // =============================================== // This contains the android.test classes from android.test.base plus -// the com.android.internal.util.Predicate[s] classes. This is only -// intended for inclusion in android.test.legacy and in -// android.test.base-hiddenapi-annotations to avoid a dependency cycle and must -// not be used elsewhere. +// the com.android.internal.util.Predicate[s] classes. java_library_static { name: "android.test.base-minus-junit", diff --git a/test-base/jarjar-rules.txt b/test-base/jarjar-rules.txt deleted file mode 100644 index fd8555c8931c..000000000000 --- a/test-base/jarjar-rules.txt +++ /dev/null @@ -1,3 +0,0 @@ -rule junit.** repackaged.junit.@1 -rule android.test.** repackaged.android.test.@1 -rule com.android.internal.util.** repackaged.com.android.internal.util.@1 diff --git a/test-runner/Android.bp b/test-runner/Android.bp index 2a19af9f8cd2..13a5dac9eb38 100644 --- a/test-runner/Android.bp +++ b/test-runner/Android.bp @@ -79,32 +79,6 @@ java_library { ], } -// Build the repackaged.android.test.runner library -// ================================================ -java_library_static { - name: "repackaged.android.test.runner", - - srcs: [":android-test-runner-sources"], - exclude_srcs: [ - "src/android/test/ActivityUnitTestCase.java", - "src/android/test/ApplicationTestCase.java", - "src/android/test/IsolatedContext.java", - "src/android/test/ProviderTestCase.java", - "src/android/test/ProviderTestCase2.java", - "src/android/test/RenamingDelegatingContext.java", - "src/android/test/ServiceTestCase.java", - ], - - sdk_version: "current", - libs: [ - "android.test.base_static", - ], - - jarjar_rules: "jarjar-rules.txt", - // Pin java_version until jarjar is certified to support later versions. http://b/72703434 - java_version: "1.8", -} - // Make the current.txt available for use by the cts/tests/signature tests. // ======================================================================== filegroup { diff --git a/test-runner/jarjar-rules.txt b/test-runner/jarjar-rules.txt deleted file mode 120000 index f6f79139d511..000000000000 --- a/test-runner/jarjar-rules.txt +++ /dev/null @@ -1 +0,0 @@ -../test-base/jarjar-rules.txt
\ No newline at end of file diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/Android.bp b/tests/Camera2Tests/SmartCamera/SimpleCamera/Android.bp new file mode 100644 index 000000000000..4fff969359c8 --- /dev/null +++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/Android.bp @@ -0,0 +1,34 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + default_applicable_licenses: [ + "frameworks_base_license", + ], +} + +android_test { + name: "SmartCamera", + optimize: { + enabled: false, + }, + // comment it out for now since we need use some hidden APIs + sdk_version: "current", + static_libs: ["android-ex-camera2"], + srcs: [ + "src/**/*.java", + ], + jni_libs: ["libsmartcamera_jni"], +} diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/Android.mk b/tests/Camera2Tests/SmartCamera/SimpleCamera/Android.mk deleted file mode 100644 index 6003628ffb0d..000000000000 --- a/tests/Camera2Tests/SmartCamera/SimpleCamera/Android.mk +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (C) 2013 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. - -ifneq ($(TARGET_BUILD_JAVA_SUPPORT_LEVEL),) - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_PROGUARD_ENABLED := disabled - -# comment it out for now since we need use some hidden APIs -LOCAL_SDK_VERSION := current - -LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2 - -LOCAL_SRC_FILES := \ - $(call all-java-files-under, src) \ - $(call all-renderscript-files-under, src) - -LOCAL_PACKAGE_NAME := SmartCamera -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../NOTICE -LOCAL_JNI_SHARED_LIBRARIES := libsmartcamera_jni - -include $(BUILD_PACKAGE) - -# Include packages in subdirectories -include $(call all-makefiles-under,$(LOCAL_PATH)) - -endif diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/Android.bp b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/Android.bp new file mode 100644 index 000000000000..5edb1de9586e --- /dev/null +++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/Android.bp @@ -0,0 +1,36 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + // See: http://go/android-license-faq + default_applicable_licenses: [ + "frameworks_base_license", + ], +} + +android_test { + name: "SmartCamera-tests", + platform_apis: true, + srcs: ["src/**/*.java"], + libs: ["android.test.base"], + static_libs: [ + "guava", + "junit", + ], + optimize: { + enabled: false, + }, + instrumentation_for: "SmartCamera", +} diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/Android.mk b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/Android.mk deleted file mode 100644 index c23d593d4f86..000000000000 --- a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/Android.mk +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (C) 2013 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. -# - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -# LOCAL_SDK_VERSION := current -LOCAL_PRIVATE_PLATFORM_APIS := true - -LOCAL_PACKAGE_NAME := SmartCamera-tests -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE - -LOCAL_SRC_FILES += $(call all-java-files-under, src) - -LOCAL_JAVA_LIBRARIES := android.test.base -LOCAL_STATIC_JAVA_LIBRARIES := guava junit - -LOCAL_PROGUARD_ENABLED := disabled - -LOCAL_INSTRUMENTATION_FOR := SmartCamera - -include $(BUILD_PACKAGE) diff --git a/tests/CanvasCompare/Android.bp b/tests/CanvasCompare/Android.bp new file mode 100644 index 000000000000..98831154ddc2 --- /dev/null +++ b/tests/CanvasCompare/Android.bp @@ -0,0 +1,63 @@ +// +// Copyright (C) 2012 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + // See: http://go/android-license-faq + default_applicable_licenses: [ + "frameworks_base_license", + ], +} + +android_test { + name: "CanvasCompare", + srcs: [ + "src/**/*.java", + ":CanvasCompare-rscript{CanvasCompare.srcjar}", + ], + resource_zips: [ + ":CanvasCompare-rscript{CanvasCompare.res.zip}", + ], + platform_apis: true, + libs: [ + "android.test.runner", + "android.test.base", + ], + static_libs: ["junit"], +} + +genrule { + name: "CanvasCompare-rscript", + srcs: [ + "src/**/*.rscript", + ":rs_script_api", + ":rs_clang_headers", + ], + tools: [ + "llvm-rs-cc", + "soong_zip", + ], + out: [ + "CanvasCompare.srcjar", + "CanvasCompare.res.zip", + ], + cmd: "for f in $(locations src/**/*.rscript); do " + + " $(location llvm-rs-cc) -o $(genDir)/res/raw -p $(genDir)/src " + + " -I $$(dirname $$(echo $(locations :rs_script_api) | awk '{ print $$1 }')) " + + " -I $$(dirname $$(echo $(locations :rs_clang_headers) | awk '{ print $$1 }')) $${f}; " + + "done && " + + "$(location soong_zip) -srcjar -o $(location CanvasCompare.srcjar) -C $(genDir)/src -D $(genDir)/src &&" + + "$(location soong_zip) -o $(location CanvasCompare.res.zip) -C $(genDir)/res -D $(genDir)/res", +} diff --git a/tests/CanvasCompare/Android.mk b/tests/CanvasCompare/Android.mk deleted file mode 100644 index b82ae65b4356..000000000000 --- a/tests/CanvasCompare/Android.mk +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright (C) 2012 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. -# - -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src) - -LOCAL_PACKAGE_NAME := CanvasCompare -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE -LOCAL_PRIVATE_PLATFORM_APIS := true - -LOCAL_MODULE_TAGS := tests - -LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base -LOCAL_STATIC_JAVA_LIBRARIES := junit - -include $(BUILD_PACKAGE) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index 294a220d6d4d..a425ee0ed969 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt @@ -142,7 +142,7 @@ class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio @JvmStatic fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt index 519bd5627b28..a0892612f4e4 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt @@ -157,7 +157,7 @@ class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt index ca73503164e6..8d60466eff95 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt @@ -18,7 +18,6 @@ package com.android.server.wm.flicker.close import android.app.Instrumentation import android.platform.test.annotations.Presubmit -import androidx.test.filters.FlakyTest import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter @@ -194,22 +193,4 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) open fun launcherLayerReplacesApp() { testSpec.replacesLayer(testApp.component, LAUNCHER_COMPONENT) } - - @FlakyTest - @Test - fun runPresubmitAssertion() { - flickerRule.checkPresubmitAssertions() - } - - @FlakyTest - @Test - fun runPostsubmitAssertion() { - flickerRule.checkPostsubmitAssertions() - } - - @FlakyTest - @Test - fun runFlakyAssertion() { - flickerRule.checkFlakyAssertions() - } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt index c87d8e1b98e1..dd5f33fb8669 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt @@ -170,7 +170,7 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5, + .getConfigNonRotationTests(repetitions = 3, // b/190352379 (IME doesn't show on app launch in 90 degrees) supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index 815ea778555b..5606965a6d40 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -178,7 +178,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5, + .getConfigNonRotationTests(repetitions = 3, // b/190352379 (IME doesn't show on app launch in 90 degrees) supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt index 24b1598f899c..e7a1c50821b7 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt @@ -160,7 +160,7 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt index e5d82a11c389..b454f0155b3e 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt @@ -164,7 +164,7 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt index 005c4f59de50..964390930d54 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt @@ -140,7 +140,7 @@ class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt index 87f8ef28cfda..8fcb4b7c03f1 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt @@ -152,7 +152,7 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt index 5e06f11fb6b8..5f0176e76e36 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt @@ -238,7 +238,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedRotations = listOf(Surface.ROTATION_0) ) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt index 195af589d77c..b5e13be7dca0 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt @@ -152,7 +152,7 @@ class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) { @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index 3a2eba1d851b..797919b03726 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -130,7 +130,7 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) override fun appLayerBecomesVisible() = super.appLayerBecomesVisible_warmStart() /** {@inheritDoc} */ - @Presubmit + @FlakyTest(bugId = 218624176) @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible_warmStart() @@ -149,6 +149,22 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) super.appWindowReplacesLauncherAsTopWindow() } + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + assumeFalse(isShellTransitionsEnabled) + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + } + + /** {@inheritDoc} */ + @FlakyTest(bugId = 218470989) + @Test + fun visibleWindowsShownMoreThanOneConsecutiveEntry_shellTransit() { + assumeTrue(isShellTransitionsEnabled) + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + } + companion object { /** * Creates the test configurations. @@ -160,7 +176,7 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt index 6365e7b9e967..f75c50eab077 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt @@ -160,7 +160,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedNavigationModes = listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY), supportedRotations = listOf(Surface.ROTATION_0) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt index 882e128fe181..04fdda425edf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt @@ -244,7 +244,7 @@ class TaskTransitionTest(val testSpec: FlickerTestParameter) { @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt index 0a64939cb4ca..5301e0275b53 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt @@ -318,7 +318,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY ), diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt index 42941c2a914c..ce6a3837ad01 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt @@ -357,7 +357,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY ), diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt index 8f2803e97986..1a762bfd97c5 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt @@ -333,7 +333,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY ), diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index 3f0de7f3cd7d..f603f6e7ed9d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -25,11 +25,13 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Rule import org.junit.Test @@ -94,24 +96,6 @@ class ChangeAppRotationTest( } } - @FlakyTest - @Test - fun runPresubmitAssertion() { - flickerRule.checkPresubmitAssertions() - } - - @FlakyTest - @Test - fun runPostsubmitAssertion() { - flickerRule.checkPostsubmitAssertions() - } - - @FlakyTest - @Test - fun runFlakyAssertion() { - flickerRule.checkFlakyAssertions() - } - /** * Windows maybe recreated when rotated. Checks that the focus does not change or if it does, * focus returns to [testApp] @@ -131,6 +115,7 @@ class ChangeAppRotationTest( @Presubmit @Test fun rotationLayerAppearsAndVanishes() { + Assume.assumeFalse(isShellTransitionsEnabled) testSpec.assertLayers { this.isVisible(testApp.component) .then() @@ -141,6 +126,13 @@ class ChangeAppRotationTest( } } + @FlakyTest(bugId = 218484127) + @Test + fun rotationLayerAppearsAndVanishes_shellTransit() { + Assume.assumeTrue(isShellTransitionsEnabled) + rotationLayerAppearsAndVanishes() + } + /** * Checks that the status bar window is visible and above the app windows in all WM * trace entries @@ -191,7 +183,7 @@ class ChangeAppRotationTest( @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigRotationTests(repetitions = 5) + .getConfigRotationTests(repetitions = 3) } } -}
\ No newline at end of file +} diff --git a/tests/HandwritingIme/Android.bp b/tests/HandwritingIme/Android.bp new file mode 100644 index 000000000000..1f552bf4dc6d --- /dev/null +++ b/tests/HandwritingIme/Android.bp @@ -0,0 +1,35 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "HandwritingIme", + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + certificate: "platform", + platform_apis: true, + static_libs: [ + "androidx.core_core", + "androidx.appcompat_appcompat", + "com.google.android.material_material", + ], +} diff --git a/tests/HandwritingIme/AndroidManifest.xml b/tests/HandwritingIme/AndroidManifest.xml new file mode 100644 index 000000000000..1445d95c2879 --- /dev/null +++ b/tests/HandwritingIme/AndroidManifest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (018C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.android.test.handwritingime"> + + <application android:label="Handwriting IME"> + <service android:name=".HandwritingIme" + android:process=":HandwritingIme" + android:label="Handwriting IME" + android:permission="android.permission.BIND_INPUT_METHOD" + android:exported="true"> + <intent-filter> + <action android:name="android.view.InputMethod"/> + </intent-filter> + <meta-data android:name="android.view.im" + android:resource="@xml/ime"/> + </service> + + </application> +</manifest> diff --git a/tests/HandwritingIme/res/xml/ime.xml b/tests/HandwritingIme/res/xml/ime.xml new file mode 100644 index 000000000000..2e84a0389429 --- /dev/null +++ b/tests/HandwritingIme/res/xml/ime.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- Configuration info for an input method --> +<input-method xmlns:android="http://schemas.android.com/apk/res/android" + android:supportsStylusHandwriting="true"/>
\ No newline at end of file diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java new file mode 100644 index 000000000000..18f962398fb8 --- /dev/null +++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.test.handwritingime; + +import android.annotation.Nullable; +import android.inputmethodservice.InputMethodService; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.FrameLayout; +import android.widget.Toast; + +import java.util.Random; + +public class HandwritingIme extends InputMethodService { + + public static final int HEIGHT_DP = 100; + + private Window mInkWindow; + private InkView mInk; + + static final String TAG = "HandwritingIme"; + + interface HandwritingFinisher { + void finish(); + } + + interface StylusListener { + void onStylusEvent(MotionEvent me); + } + + final class StylusConsumer implements StylusListener { + @Override + public void onStylusEvent(MotionEvent me) { + HandwritingIme.this.onStylusEvent(me); + } + } + + final class HandwritingFinisherImpl implements HandwritingFinisher { + + HandwritingFinisherImpl() {} + + @Override + public void finish() { + finishStylusHandwriting(); + Log.d(TAG, "HandwritingIme called finishStylusHandwriting() "); + } + } + + private void onStylusEvent(@Nullable MotionEvent event) { + // TODO Hookup recognizer here + if (event.getAction() == MotionEvent.ACTION_UP) { + sendKeyChar((char) (56 + new Random().nextInt(66))); + } + } + + @Override + public View onCreateInputView() { + Log.d(TAG, "onCreateInputView"); + final ViewGroup view = new FrameLayout(this); + final View inner = new View(this); + final float density = getResources().getDisplayMetrics().density; + final int height = (int) (HEIGHT_DP * density); + view.setPadding(0, 0, 0, 0); + view.addView(inner, new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, height)); + inner.setBackgroundColor(0xff0110fe); // blue + + return view; + } + + public void onPrepareStylusHandwriting() { + Log.d(TAG, "onPrepareStylusHandwriting "); + if (mInk == null) { + mInk = new InkView(this, new HandwritingFinisherImpl(), new StylusConsumer()); + } + } + + @Override + public boolean onStartStylusHandwriting() { + Log.d(TAG, "onStartStylusHandwriting "); + Toast.makeText(this, "START HW", Toast.LENGTH_SHORT).show(); + mInkWindow = getStylusHandwritingWindow(); + mInkWindow.setContentView(mInk, mInk.getLayoutParams()); + return true; + } + + @Override + public void onFinishStylusHandwriting() { + Log.d(TAG, "onFinishStylusHandwriting "); + Toast.makeText(this, "Finish HW", Toast.LENGTH_SHORT).show(); + // Free-up + ((ViewGroup) mInk.getParent()).removeView(mInk); + mInk = null; + } +} diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java new file mode 100644 index 000000000000..4ffdc9211f1d --- /dev/null +++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.test.handwritingime; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Insets; +import android.graphics.Paint; +import android.graphics.Path; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; + +class InkView extends View { + private static final long FINISH_TIMEOUT = 2500; + private final HandwritingIme.HandwritingFinisher mHwCanceller; + private final HandwritingIme.StylusConsumer mConsumer; + private Paint mPaint; + private Path mPath; + private float mX, mY; + private static final float STYLUS_MOVE_TOLERANCE = 1; + private Runnable mFinishRunnable; + + InkView(Context context, HandwritingIme.HandwritingFinisher hwController, + HandwritingIme.StylusConsumer consumer) { + super(context); + mHwCanceller = hwController; + mConsumer = consumer; + + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setDither(true); + mPaint.setColor(Color.GREEN); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeJoin(Paint.Join.ROUND); + mPaint.setStrokeCap(Paint.Cap.ROUND); + mPaint.setStrokeWidth(14); + + mPath = new Path(); + + WindowManager wm = context.getSystemService(WindowManager.class); + WindowMetrics metrics = wm.getCurrentWindowMetrics(); + Insets insets = metrics.getWindowInsets() + .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()); + setLayoutParams(new ViewGroup.LayoutParams( + metrics.getBounds().width() - insets.left - insets.right, + metrics.getBounds().height() - insets.top - insets.bottom)); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + canvas.drawPath(mPath, mPaint); + canvas.drawARGB(20, 255, 50, 50); + } + + private void stylusStart(float x, float y) { + mPath.moveTo(x, y); + mX = x; + mY = y; + } + + private void stylusMove(float x, float y) { + float dx = Math.abs(x - mX); + float dy = Math.abs(y - mY); + if (mPath.isEmpty()) { + stylusStart(x, y); + } + if (dx >= STYLUS_MOVE_TOLERANCE || dy >= STYLUS_MOVE_TOLERANCE) { + mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); + mX = x; + mY = y; + } + } + + private void stylusFinish() { + mPath.lineTo(mX, mY); + // TODO: support offscreen? e.g. mCanvas.drawPath(mPath, mPaint); + mPath.reset(); + mX = 0; + mY = 0; + + } + + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) { + mConsumer.onStylusEvent(event); + android.util.Log.w(HandwritingIme.TAG, "INK touch onStylusEvent " + event); + float x = event.getX(); + float y = event.getY(); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + cancelTimer(); + stylusStart(x, y); + invalidate(); + break; + case MotionEvent.ACTION_MOVE: + stylusMove(x, y); + invalidate(); + break; + + case MotionEvent.ACTION_UP: + scheduleTimer(); + break; + + } + return true; + } + return false; + } + + private void cancelTimer() { + if (mFinishRunnable != null) { + if (getHandler() != null) { + getHandler().removeCallbacks(mFinishRunnable); + } + mFinishRunnable = null; + } + if (getHandler() != null) { + getHandler().removeCallbacksAndMessages(null); + } + } + + private void scheduleTimer() { + cancelTimer(); + if (getHandler() != null) { + postDelayed(getFinishRunnable(), FINISH_TIMEOUT); + } + } + + private Runnable getFinishRunnable() { + mFinishRunnable = () -> { + android.util.Log.e(HandwritingIme.TAG, "Hw view timer finishHandwriting "); + mHwCanceller.finish(); + stylusFinish(); + mPath.reset(); + invalidate(); + }; + + return mFinishRunnable; + } + +} diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java index 824f91e1e826..15a6afc5ff7c 100644 --- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java +++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java @@ -62,6 +62,7 @@ public final class FrameworksTestsFilter extends SelectTest { "android.view.PendingInsetsControllerTest", "android.window.", // all tests under the package. "android.app.activity.ActivityThreadTest", + "android.app.activity.RegisterComponentCallbacksTest" }; public FrameworksTestsFilter(Bundle testArgs) { diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp index 1ec83a360048..b18bdff7263f 100644 --- a/tools/streaming_proto/Android.bp +++ b/tools/streaming_proto/Android.bp @@ -69,7 +69,6 @@ java_library { "test/**/*.proto", ], proto: { - plugin: "javastream", + type: "stream", }, - static_libs: ["libprotobuf-java-lite"], } diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java index d3eb8e03c7aa..960447504d15 100644 --- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java +++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java @@ -1269,18 +1269,19 @@ public class WifiNl80211Manager { * support the NL80211_CMD_REG_CHANGED (otherwise it will find out on its own). The wificond * updates in internal state in response to this Country Code update. * - * @return true on success, false otherwise. + * @param newCountryCode new country code. An ISO-3166-alpha2 country code which is 2-Character + * alphanumeric. */ - public boolean notifyCountryCodeChanged() { + public void notifyCountryCodeChanged(@Nullable String newCountryCode) { + if (mWificond == null) { + new RemoteException("Wificond service doesn't exist!").rethrowFromSystemServer(); + } try { - if (mWificond != null) { - mWificond.notifyCountryCodeChanged(); - return true; - } - } catch (RemoteException e1) { - Log.e(TAG, "Failed to notify country code changed due to remote exception"); + mWificond.notifyCountryCodeChanged(); + Log.i(TAG, "Receive country code change to " + newCountryCode); + } catch (RemoteException re) { + re.rethrowFromSystemServer(); } - return false; } /** diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java index 4032a7b0c75c..a750696628f9 100644 --- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java +++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java @@ -1143,17 +1143,17 @@ public class WifiNl80211ManagerTest { @Test public void testNotifyCountryCodeChanged() throws Exception { doNothing().when(mWificond).notifyCountryCodeChanged(); - assertTrue(mWificondControl.notifyCountryCodeChanged()); + mWificondControl.notifyCountryCodeChanged(TEST_COUNTRY_CODE); verify(mWificond).notifyCountryCodeChanged(); } /** * Tests notifyCountryCodeChanged with RemoteException */ - @Test + @Test(expected = RuntimeException.class) public void testNotifyCountryCodeChangedRemoteException() throws Exception { doThrow(new RemoteException()).when(mWificond).notifyCountryCodeChanged(); - assertFalse(mWificondControl.notifyCountryCodeChanged()); + mWificondControl.notifyCountryCodeChanged(TEST_COUNTRY_CODE); verify(mWificond).notifyCountryCodeChanged(); } |