diff options
297 files changed, 9862 insertions, 2333 deletions
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfRunPreconditionBase.java b/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfRunPreconditionBase.java index 8d2ac0276592..330a19e03c39 100644 --- a/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfRunPreconditionBase.java +++ b/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfRunPreconditionBase.java @@ -89,7 +89,7 @@ public class WindowPerfRunPreconditionBase extends RunListener { navOverlay = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY; break; } - executeShellCommand("cmd overlay enable-exclusive " + navOverlay); + executeShellCommand("cmd overlay enable-exclusive --category " + navOverlay); }); /** It only executes once before all tests. */ diff --git a/apex/jobscheduler/framework/Android.bp b/apex/jobscheduler/framework/Android.bp index 6650e677544b..fd35537cd7b7 100644 --- a/apex/jobscheduler/framework/Android.bp +++ b/apex/jobscheduler/framework/Android.bp @@ -22,6 +22,7 @@ java_library { ], }, libs: [ + "app-compat-annotations", "framework-minus-apex", "unsupportedappusage", ], diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 7851087bac19..7c7b21001c3b 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -16,11 +16,14 @@ package android.app; +import android.Manifest; import android.annotation.IntDef; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.compat.annotation.ChangeId; +import android.compat.annotation.Disabled; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; @@ -183,6 +186,25 @@ public class AlarmManager { @UnsupportedAppUsage public static final int FLAG_IDLE_UNTIL = 1<<4; + /** + * Flag for alarms: Used to provide backwards compatibility for apps with targetSdkVersion less + * than {@link Build.VERSION_CODES#S} + * @hide + */ + public static final int FLAG_ALLOW_WHILE_IDLE_COMPAT = 1 << 5; + + /** + * For apps targeting {@link Build.VERSION_CODES#S} or above, APIs + * {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} and + * {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} will require holding a new + * permission {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM} + * + * @hide + */ + @ChangeId + @Disabled // TODO (b/171306433): Enable starting S. + public static final long REQUIRE_EXACT_ALARM_PERMISSION = 171306433L; + @UnsupportedAppUsage private final IAlarmManager mService; private final Context mContext; @@ -588,6 +610,11 @@ public class AlarmManager { * This method is like {@link #setExact(int, long, PendingIntent)}, but implies * {@link #RTC_WAKEUP}. * + * <p> + * Starting from API {@link Build.VERSION_CODES#S}, using this method requires the + * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} permission. Alarms scheduled via this API + * will be allowed to start a foreground service even if the app is in the background. + * * @param info * @param operation Action to perform when the alarm goes off; * typically comes from {@link PendingIntent#getBroadcast @@ -603,6 +630,7 @@ public class AlarmManager { * @see android.content.Context#registerReceiver * @see android.content.Intent#filterEquals */ + @RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM) public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) { setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation, null, null, null, null, info); @@ -876,6 +904,12 @@ public class AlarmManager { * device is idle it may take even more liberties with scheduling in order to optimize * for battery life.</p> * + * <p> + * Starting from API {@link Build.VERSION_CODES#S}, using this method requires the + * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} permission, unless the app is exempt from + * battery restrictions. Alarms scheduled via this API will be allowed to start a foreground + * service even if the app is in the background. + * * @param type type of alarm. * @param triggerAtMillis time in milliseconds that the alarm should go * off, using the appropriate clock (depending on the alarm type). @@ -895,6 +929,7 @@ public class AlarmManager { * @see #RTC * @see #RTC_WAKEUP */ + @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true) public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis, PendingIntent operation) { setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation, @@ -1018,6 +1053,18 @@ public class AlarmManager { } /** + * Called to check if the caller has permission to use alarms set via {@link } + * @return + */ + public boolean canScheduleExactAlarms() { + try { + return mService.canScheduleExactAlarms(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Gets information about the next alarm clock currently scheduled. * * The alarm clocks considered are those scheduled by any application diff --git a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl index 2c51935dc446..2f21ce395df5 100644 --- a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl +++ b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl @@ -41,4 +41,5 @@ interface IAlarmManager { @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) AlarmManager.AlarmClockInfo getNextAlarmClock(int userId); long currentNetworkTimeMillis(); + boolean canScheduleExactAlarms(); } diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java index 657c368d0aee..3bc7b307c334 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java @@ -26,6 +26,7 @@ import static com.android.server.alarm.AlarmManagerService.clampPositive; import android.app.AlarmManager; import android.app.IAlarmListener; import android.app.PendingIntent; +import android.os.Bundle; import android.os.WorkSource; import android.util.IndentingPrintWriter; import android.util.TimeUtils; @@ -92,10 +93,12 @@ class Alarm { private long mWhenElapsed; private long mMaxWhenElapsed; public AlarmManagerService.PriorityClass priorityClass; + /** Broadcast options to use when delivering this alarm */ + public Bundle mIdleOptions; Alarm(int type, long when, long requestedWhenElapsed, long windowLength, long interval, PendingIntent op, IAlarmListener rec, String listenerTag, WorkSource ws, int flags, - AlarmManager.AlarmClockInfo info, int uid, String pkgName) { + AlarmManager.AlarmClockInfo info, int uid, String pkgName, Bundle idleOptions) { this.type = type; origWhen = when; wakeup = type == AlarmManager.ELAPSED_REALTIME_WAKEUP @@ -115,6 +118,7 @@ class Alarm { alarmClock = info; this.uid = uid; packageName = pkgName; + mIdleOptions = idleOptions; sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName; creatorUid = (operation != null) ? operation.getCreatorUid() : this.uid; } @@ -303,6 +307,10 @@ class Alarm { ipw.print("listener="); ipw.println(listener.asBinder()); } + if (mIdleOptions != null) { + ipw.print("idle-options="); + ipw.println(mIdleOptions.toString()); + } } public void dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed) { 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 f6a1b8af9e49..559a43491c7f 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -20,11 +20,15 @@ import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.AlarmManager.ELAPSED_REALTIME; import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE; +import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; import static android.app.AlarmManager.FLAG_IDLE_UNTIL; +import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE; +import static android.app.AlarmManager.INTERVAL_HOUR; import static android.app.AlarmManager.RTC; import static android.app.AlarmManager.RTC_WAKEUP; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED; import static android.os.UserHandle.USER_SYSTEM; import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX; @@ -32,6 +36,7 @@ import static com.android.server.alarm.Alarm.BATTERY_SAVER_POLICY_INDEX; import static com.android.server.alarm.Alarm.DEVICE_IDLE_POLICY_INDEX; import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX; +import android.Manifest; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.Activity; @@ -43,12 +48,14 @@ import android.app.IAlarmCompleteListener; import android.app.IAlarmListener; import android.app.IAlarmManager; import android.app.PendingIntent; +import android.app.compat.CompatChanges; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.PermissionChecker; import android.content.pm.PackageManagerInternal; import android.net.Uri; import android.os.BatteryManager; @@ -65,6 +72,7 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; +import android.os.ServiceManager; import android.os.ShellCallback; import android.os.ShellCommand; import android.os.SystemClock; @@ -95,6 +103,8 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.IAppOpsCallback; +import com.android.internal.app.IAppOpsService; import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.LocalLog; @@ -177,6 +187,7 @@ public class AlarmManagerService extends SystemService { final LocalLog mLog = new LocalLog(TAG); AppOpsManager mAppOps; + IAppOpsService mAppOpsService; DeviceIdleInternal mLocalDeviceIdleController; private UsageStatsManagerInternal mUsageStatsManagerInternal; private ActivityManagerInternal mActivityManagerInternal; @@ -253,10 +264,8 @@ public class AlarmManagerService extends SystemService { "REORDER_ALARMS_FOR_STANDBY", }); - /** - * Broadcast options to use for FLAG_ALLOW_WHILE_IDLE. - */ - Bundle mIdleOptions; + BroadcastOptions mOptsWithFgs = BroadcastOptions.makeBasic(); + BroadcastOptions mOptsWithoutFgs = BroadcastOptions.makeBasic(); // TODO(b/172085676): Move inside alarm store. private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser = @@ -410,6 +419,10 @@ public class AlarmManagerService extends SystemService { @VisibleForTesting static final String KEY_ALLOW_WHILE_IDLE_QUOTA = "allow_while_idle_quota"; + @VisibleForTesting + static final String KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA = "allow_while_idle_compat_quota"; + private static final String KEY_ALLOW_WHILE_IDLE_WINDOW = "allow_while_idle_window"; + private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; private static final long DEFAULT_MAX_INTERVAL = 365 * DateUtils.DAY_IN_MILLIS; @@ -433,8 +446,14 @@ public class AlarmManagerService extends SystemService { private static final boolean DEFAULT_LAZY_BATCHING = true; private static final boolean DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE = true; - private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 7; - public static final long ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour. + /** + * Default quota for pre-S apps. Enough to accommodate the existing policy of an alarm + * every ALLOW_WHILE_IDLE_LONG_DELAY, which was 9 minutes. + */ + private static final int DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA = 7; + private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 72; + + private static final long DEFAULT_ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour. // Minimum futurity of a new alarm public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; @@ -463,6 +482,19 @@ public class AlarmManagerService extends SystemService { public int ALLOW_WHILE_IDLE_QUOTA = DEFAULT_ALLOW_WHILE_IDLE_QUOTA; + /** + * Used to provide backwards compatibility to pre-S apps with a quota equivalent to the + * earlier delay throttling mechanism. + */ + public int ALLOW_WHILE_IDLE_COMPAT_QUOTA = DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA; + + /** + * The window used for enforcing {@link #ALLOW_WHILE_IDLE_QUOTA} and + * {@link #ALLOW_WHILE_IDLE_COMPAT_QUOTA}. Can be configured, but only recommended for + * testing. + */ + public long ALLOW_WHILE_IDLE_WINDOW = DEFAULT_ALLOW_WHILE_IDLE_WINDOW; + private long mLastAllowWhileIdleWhitelistDuration = -1; Constants() { @@ -480,9 +512,11 @@ public class AlarmManagerService extends SystemService { public void updateAllowWhileIdleWhitelistDurationLocked() { if (mLastAllowWhileIdleWhitelistDuration != ALLOW_WHILE_IDLE_WHITELIST_DURATION) { mLastAllowWhileIdleWhitelistDuration = ALLOW_WHILE_IDLE_WHITELIST_DURATION; - BroadcastOptions opts = BroadcastOptions.makeBasic(); - opts.setTemporaryAppWhitelistDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION); - mIdleOptions = opts.toBundle(); + + mOptsWithFgs.setTemporaryAppWhitelistDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION); + mOptsWithoutFgs.setTemporaryAppWhitelistDuration( + TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, + ALLOW_WHILE_IDLE_WHITELIST_DURATION); } } @@ -516,6 +550,27 @@ public class AlarmManagerService extends SystemService { ALLOW_WHILE_IDLE_QUOTA = 1; } break; + case KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA: + ALLOW_WHILE_IDLE_COMPAT_QUOTA = properties.getInt( + KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, + DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA); + if (ALLOW_WHILE_IDLE_COMPAT_QUOTA <= 0) { + Slog.w(TAG, "Cannot have quota lower than 1."); + ALLOW_WHILE_IDLE_COMPAT_QUOTA = 1; + } + break; + case KEY_ALLOW_WHILE_IDLE_WINDOW: + ALLOW_WHILE_IDLE_WINDOW = properties.getLong( + KEY_ALLOW_WHILE_IDLE_WINDOW, DEFAULT_ALLOW_WHILE_IDLE_WINDOW); + if (ALLOW_WHILE_IDLE_WINDOW > DEFAULT_ALLOW_WHILE_IDLE_WINDOW) { + Slog.w(TAG, "Cannot have allow_while_idle_window > " + + DEFAULT_ALLOW_WHILE_IDLE_WINDOW); + ALLOW_WHILE_IDLE_WINDOW = DEFAULT_ALLOW_WHILE_IDLE_WINDOW; + } else if (ALLOW_WHILE_IDLE_WINDOW < DEFAULT_ALLOW_WHILE_IDLE_WINDOW) { + Slog.w(TAG, "Using a non-default allow_while_idle_window = " + + ALLOW_WHILE_IDLE_WINDOW); + } + break; case KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION: ALLOW_WHILE_IDLE_WHITELIST_DURATION = properties.getLong( KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, @@ -643,10 +698,14 @@ public class AlarmManagerService extends SystemService { TimeUtils.formatDuration(LISTENER_TIMEOUT, pw); pw.println(); - pw.print("allow_while_idle_window="); + pw.print(KEY_ALLOW_WHILE_IDLE_WINDOW); + pw.print("="); TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WINDOW, pw); pw.println(); + pw.print(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, ALLOW_WHILE_IDLE_COMPAT_QUOTA); + pw.println(); + pw.print(KEY_ALLOW_WHILE_IDLE_QUOTA, ALLOW_WHILE_IDLE_QUOTA); pw.println(); @@ -830,7 +889,15 @@ public class AlarmManagerService extends SystemService { if (futurity < MIN_FUZZABLE_INTERVAL) { futurity = 0; } - return clampPositive(triggerAtTime + (long) (.75 * futurity)); + long maxElapsed = triggerAtTime + (long) (0.75 * futurity); + // For non-repeating alarms, window is capped at a maximum of one hour from the requested + // delivery time. This allows for inexact-while-idle alarms to be slightly more reliable. + // In practice, the delivery window should generally be much smaller than that + // when the device is not idling. + if (interval == 0) { + maxElapsed = Math.min(maxElapsed, triggerAtTime + INTERVAL_HOUR); + } + return clampPositive(maxElapsed); } // The RTC clock has moved arbitrarily, so we need to recalculate all the RTC alarm deliveries. @@ -998,7 +1065,7 @@ public class AlarmManagerService extends SystemService { setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed, nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null, null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid, - alarm.packageName); + alarm.packageName, null); // Kernel alarms will be rescheduled as needed in setImplLocked } } @@ -1241,7 +1308,8 @@ public class AlarmManagerService extends SystemService { mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater); mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW); - mAllowWhileIdleHistory = new AppWakeupHistory(Constants.ALLOW_WHILE_IDLE_WINDOW); + mAllowWhileIdleHistory = new AppWakeupHistory( + Constants.DEFAULT_ALLOW_WHILE_IDLE_WINDOW); mNextWakeup = mNextNonWakeup = 0; @@ -1327,6 +1395,28 @@ public class AlarmManagerService extends SystemService { synchronized (mLock) { mConstants.start(); mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); + mAppOpsService = mInjector.getAppOpsService(); + try { + mAppOpsService.startWatchingMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, null, + new IAppOpsCallback.Stub() { + @Override + public void opChanged(int op, int uid, String packageName) + throws RemoteException { + if (op != AppOpsManager.OP_SCHEDULE_EXACT_ALARM) { + return; + } + final int mode = mAppOpsService.checkOperation(op, uid, + packageName); + if (mode != AppOpsManager.MODE_ALLOWED + && mode != AppOpsManager.MODE_DEFAULT) { + mHandler.obtainMessage(AlarmHandler.REMOVE_EXACT_ALARMS, + uid, 0, packageName).sendToTarget(); + } + } + }); + } catch (RemoteException e) { + } + mLocalDeviceIdleController = LocalServices.getService(DeviceIdleInternal.class); mUsageStatsManagerInternal = @@ -1428,7 +1518,7 @@ public class AlarmManagerService extends SystemService { void setImpl(int type, long triggerAtTime, long windowLength, long interval, PendingIntent operation, IAlarmListener directReceiver, String listenerTag, int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock, - int callingUid, String callingPackage) { + int callingUid, String callingPackage, Bundle idleOptions) { if ((operation == null && directReceiver == null) || (operation != null && directReceiver != null)) { Slog.w(TAG, "Alarms must either supply a PendingIntent or an AlarmReceiver"); @@ -1519,17 +1609,18 @@ public class AlarmManagerService extends SystemService { } setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, interval, operation, directReceiver, listenerTag, flags, workSource, alarmClock, callingUid, - callingPackage); + callingPackage, idleOptions); } } private void setImplLocked(int type, long when, long whenElapsed, long windowLength, long interval, PendingIntent operation, IAlarmListener directReceiver, String listenerTag, int flags, WorkSource workSource, - AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage) { + AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage, + Bundle idleOptions) { final Alarm a = new Alarm(type, when, whenElapsed, windowLength, interval, operation, directReceiver, listenerTag, workSource, flags, alarmClock, - callingUid, callingPackage); + callingUid, callingPackage, idleOptions); if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, callingPackage)) { Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a + " -- package not allowed to start"); @@ -1605,19 +1696,21 @@ public class AlarmManagerService extends SystemService { return false; } - if (!(mAppStateTracker != null && mAppStateTracker.areAlarmsRestrictedByBatterySaver( - alarm.creatorUid, alarm.sourcePackage))) { + if (mAppStateTracker == null || !mAppStateTracker.areAlarmsRestrictedByBatterySaver( + alarm.creatorUid, alarm.sourcePackage)) { return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, nowElapsed); } final long batterySaverPolicyElapsed; - if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) { + if ((alarm.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) { // Unrestricted. batterySaverPolicyElapsed = nowElapsed; - } else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) { + } else if (isAllowedWhileIdleRestricted(alarm)) { // Allowed but limited. final int userId = UserHandle.getUserId(alarm.creatorUid); - final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA; + final int quota = ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0) + ? mConstants.ALLOW_WHILE_IDLE_QUOTA + : mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA; final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow( alarm.sourcePackage, userId); if (dispatchesInWindow < quota) { @@ -1625,7 +1718,7 @@ public class AlarmManagerService extends SystemService { batterySaverPolicyElapsed = nowElapsed; } else { batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage( - alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW; + alarm.sourcePackage, userId, quota) + mConstants.ALLOW_WHILE_IDLE_WINDOW; } } else { // Not allowed. @@ -1635,6 +1728,16 @@ public class AlarmManagerService extends SystemService { } /** + * Returns {@code true} if the given alarm has the flag + * {@link AlarmManager#FLAG_ALLOW_WHILE_IDLE} or + * {@link AlarmManager#FLAG_ALLOW_WHILE_IDLE_COMPAT} + * + */ + private static boolean isAllowedWhileIdleRestricted(Alarm a) { + return (a.flags & (FLAG_ALLOW_WHILE_IDLE | FLAG_ALLOW_WHILE_IDLE_COMPAT)) != 0; + } + + /** * Adjusts the delivery time of the alarm based on device_idle (doze) rules. * * @param alarm The alarm to adjust @@ -1647,14 +1750,15 @@ public class AlarmManagerService extends SystemService { } final long deviceIdlePolicyTime; - if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED - | AlarmManager.FLAG_WAKE_FROM_IDLE)) != 0) { + if ((alarm.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_WAKE_FROM_IDLE)) != 0) { // Unrestricted. deviceIdlePolicyTime = nowElapsed; - } else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) { + } else if (isAllowedWhileIdleRestricted(alarm)) { // Allowed but limited. final int userId = UserHandle.getUserId(alarm.creatorUid); - final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA; + final int quota = ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0) + ? mConstants.ALLOW_WHILE_IDLE_QUOTA + : mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA; final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow( alarm.sourcePackage, userId); if (dispatchesInWindow < quota) { @@ -1662,7 +1766,7 @@ public class AlarmManagerService extends SystemService { deviceIdlePolicyTime = nowElapsed; } else { final long whenInQuota = mAllowWhileIdleHistory.getNthLastWakeupForPackage( - alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW; + alarm.sourcePackage, userId, quota) + mConstants.ALLOW_WHILE_IDLE_WINDOW; deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed()); } } else { @@ -1749,7 +1853,7 @@ public class AlarmManagerService extends SystemService { } else if (mPendingIdleUntil != null) { adjustDeliveryTimeBasedOnDeviceIdle(a); } - if ((a.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) { + if ((a.flags & FLAG_WAKE_FROM_IDLE) != 0) { if (mNextWakeFromIdle == null || mNextWakeFromIdle.getWhenElapsed() > a.getWhenElapsed()) { mNextWakeFromIdle = a; @@ -1810,6 +1914,14 @@ public class AlarmManagerService extends SystemService { } /** + * Returns true if the given uid is on the system or user's power save exclusion list. + */ + boolean isWhitelisted(int uid) { + return (mLocalDeviceIdleController == null || mLocalDeviceIdleController.isAppOnWhitelist( + UserHandle.getAppId(uid))); + } + + /** * Public-facing binder interface */ private final IBinder mService = new IAlarmManager.Stub() { @@ -1824,6 +1936,54 @@ public class AlarmManagerService extends SystemService { // wakelock time spent in alarm delivery mAppOps.checkPackage(callingUid, callingPackage); + final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0; + + Bundle idleOptions = null; + if (alarmClock != null || allowWhileIdle) { + // make sure the caller is allowed to use the requested kind of alarm, and also + // decide what broadcast options to use. + final boolean needsPermission; + boolean lowQuota; + if (CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, + callingPackage, UserHandle.getUserHandleForUid(callingUid))) { + if (windowLength != AlarmManager.WINDOW_EXACT) { + needsPermission = false; + lowQuota = true; + idleOptions = isWhitelisted(callingUid) ? mOptsWithFgs.toBundle() + : mOptsWithoutFgs.toBundle(); + } else if (alarmClock != null) { + needsPermission = true; + lowQuota = false; + idleOptions = mOptsWithFgs.toBundle(); + } else { + needsPermission = true; + lowQuota = false; + idleOptions = mOptsWithFgs.toBundle(); + } + } else { + needsPermission = false; + lowQuota = allowWhileIdle; + idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null; + } + if (needsPermission && !canScheduleExactAlarms()) { + if (alarmClock == null && isWhitelisted(callingUid)) { + // If the app is on the full system allow-list (not except-idle), we still + // allow the alarms, but with a lower quota to keep pre-S compatibility. + lowQuota = true; + } else { + final String errorMessage = "Caller needs to hold " + + Manifest.permission.SCHEDULE_EXACT_ALARM + " to set " + + ((allowWhileIdle) ? "exact, allow-while-idle" : "alarm-clock") + + " alarms."; + throw new SecurityException(errorMessage); + } + } + if (lowQuota) { + flags &= ~FLAG_ALLOW_WHILE_IDLE; + flags |= FLAG_ALLOW_WHILE_IDLE_COMPAT; + } + } + // Repeating alarms must use PendingIntent, not direct listener if (interval != 0) { if (directReceiver != null) { @@ -1840,8 +2000,7 @@ public class AlarmManagerService extends SystemService { // No incoming callers can request either WAKE_FROM_IDLE or // ALLOW_WHILE_IDLE_UNRESTRICTED -- we will apply those later as appropriate. - flags &= ~(AlarmManager.FLAG_WAKE_FROM_IDLE - | AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED); + flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED); // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm // manager when to come out of idle mode, which is only for DeviceIdleController. @@ -1857,22 +2016,32 @@ public class AlarmManagerService extends SystemService { // If this alarm is for an alarm clock, then it must be standalone and we will // use it to wake early from idle if needed. if (alarmClock != null) { - flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE; + flags |= FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE; // If the caller is a core system component or on the user's whitelist, and not calling // to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED. // This means we will allow these alarms to go off as normal even while idle, with no // timing restrictions. - } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID + } else if (workSource == null && (UserHandle.isCore(callingUid) || UserHandle.isSameApp(callingUid, mSystemUiUid) || ((mAppStateTracker != null) && mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) { - flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; - flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE; + flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; + flags &= ~FLAG_ALLOW_WHILE_IDLE; + flags &= ~FLAG_ALLOW_WHILE_IDLE_COMPAT; + idleOptions = null; } setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver, - listenerTag, flags, workSource, alarmClock, callingUid, callingPackage); + listenerTag, flags, workSource, alarmClock, callingUid, callingPackage, + idleOptions); + } + + @Override + public boolean canScheduleExactAlarms() { + return PermissionChecker.checkCallingOrSelfPermissionForPreflight(getContext(), + Manifest.permission.SCHEDULE_EXACT_ALARM) + == PermissionChecker.PERMISSION_GRANTED; } @Override @@ -2755,6 +2924,77 @@ public class AlarmManagerService extends SystemService { } } + /** + * Called when an app loses {@link Manifest.permission#SCHEDULE_EXACT_ALARM} to remove alarms + * that the app is no longer eligible to use. + * TODO (b/179541791): Revisit and write tests once UX is final. + */ + void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) { + if (UserHandle.isCore(uid) || uid == mSystemUiUid) { + return; + } + if (isWhitelisted(uid)) { + return; + } + if (!CompatChanges.isChangeEnabled( + AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, + packageName, UserHandle.getUserHandleForUid(uid))) { + return; + } + + final Predicate<Alarm> whichAlarms = + a -> (a.uid == uid && a.packageName.equals(packageName) + && ((a.flags & FLAG_ALLOW_WHILE_IDLE) != 0 || a.alarmClock != null)); + final ArrayList<Alarm> removed = mAlarmStore.remove(whichAlarms); + final boolean didRemove = !removed.isEmpty(); + if (didRemove) { + decrementAlarmCount(uid, removed.size()); + } + + for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) { + final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i); + for (int j = alarmsForUid.size() - 1; j >= 0; j--) { + final Alarm alarm = alarmsForUid.get(j); + if (whichAlarms.test(alarm)) { + // Don't set didRemove, since this doesn't impact the scheduled alarms. + alarmsForUid.remove(j); + decrementAlarmCount(alarm.uid, 1); + } + } + if (alarmsForUid.size() == 0) { + mPendingBackgroundAlarms.removeAt(i); + } + } + for (int i = mPendingNonWakeupAlarms.size() - 1; i >= 0; i--) { + final Alarm a = mPendingNonWakeupAlarms.get(i); + if (whichAlarms.test(a)) { + // Don't set didRemove, since this doesn't impact the scheduled alarms. + mPendingNonWakeupAlarms.remove(i); + decrementAlarmCount(a.uid, 1); + } + } + + if (didRemove) { + if (mNextWakeFromIdle != null && whichAlarms.test(mNextWakeFromIdle)) { + mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm(); + if (mPendingIdleUntil != null) { + final boolean idleUntilUpdated = mAlarmStore.updateAlarmDeliveries(alarm -> { + if (alarm != mPendingIdleUntil) { + return false; + } + return adjustIdleUntilTime(alarm); + }); + if (idleUntilUpdated) { + mAlarmStore.updateAlarmDeliveries( + alarm -> adjustDeliveryTimeBasedOnDeviceIdle(alarm)); + } + } + } + rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); + } + } + void removeLocked(PendingIntent operation, IAlarmListener directReceiver) { if (operation == null && directReceiver == null) { if (localLOGV) { @@ -3082,7 +3322,7 @@ public class AlarmManagerService extends SystemService { } } - private boolean isExemptFromBatterySaver(Alarm alarm) { + private static boolean isExemptFromBatterySaver(Alarm alarm) { if (alarm.alarmClock != null) { return true; } @@ -3142,7 +3382,7 @@ public class AlarmManagerService extends SystemService { alarm.count = 1; triggerList.add(alarm); - if ((alarm.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) { + if ((alarm.flags & FLAG_WAKE_FROM_IDLE) != 0) { EventLogTags.writeDeviceIdleWakeFromIdle(mPendingIdleUntil != null ? 1 : 0, alarm.statsTag); } @@ -3180,7 +3420,7 @@ public class AlarmManagerService extends SystemService { setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed, nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null, null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid, - alarm.packageName); + alarm.packageName, null); } if (alarm.wakeup) { @@ -3257,7 +3497,6 @@ public class AlarmManagerService extends SystemService { mLastAlarmDeliveryTime = nowELAPSED; for (int i = 0; i < triggerList.size(); i++) { Alarm alarm = triggerList.get(i); - final boolean allowWhileIdle = (alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0; if (alarm.wakeup) { Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch wakeup alarm to " + alarm.packageName); @@ -3273,7 +3512,7 @@ public class AlarmManagerService extends SystemService { mActivityManagerInternal.noteAlarmStart(alarm.operation, alarm.workSource, alarm.uid, alarm.statsTag); } - mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle); + mDeliveryTracker.deliverLocked(alarm, nowELAPSED); } catch (RuntimeException e) { Slog.w(TAG, "Failure sending alarm.", e); } @@ -3282,9 +3521,10 @@ public class AlarmManagerService extends SystemService { } } - private boolean isExemptFromAppStandby(Alarm a) { + @VisibleForTesting + static boolean isExemptFromAppStandby(Alarm a) { return a.alarmClock != null || UserHandle.isCore(a.creatorUid) - || (a.flags & FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED) != 0; + || (a.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_ALLOW_WHILE_IDLE)) != 0; } @VisibleForTesting @@ -3368,6 +3608,11 @@ public class AlarmManagerService extends SystemService { MATCH_SYSTEM_ONLY, USER_SYSTEM); } + IAppOpsService getAppOpsService() { + return IAppOpsService.Stub.asInterface( + ServiceManager.getService(Context.APP_OPS_SERVICE)); + } + ClockReceiver getClockReceiver(AlarmManagerService service) { return service.new ClockReceiver(); } @@ -3566,6 +3811,7 @@ public class AlarmManagerService extends SystemService { public static final int APP_STANDBY_BUCKET_CHANGED = 5; public static final int CHARGING_STATUS_CHANGED = 6; public static final int REMOVE_FOR_CANCELED = 7; + public static final int REMOVE_EXACT_ALARMS = 8; AlarmHandler() { super(Looper.myLooper()); @@ -3645,6 +3891,14 @@ public class AlarmManagerService extends SystemService { } break; + case REMOVE_EXACT_ALARMS: + final int uid = msg.arg1; + final String packageName = (String) msg.obj; + synchronized (mLock) { + removeExactAlarmsOnPermissionRevokedLocked(uid, packageName); + } + break; + default: // nope, just ignore it break; @@ -3720,7 +3974,7 @@ public class AlarmManagerService extends SystemService { setImpl(ELAPSED_REALTIME, mInjector.getElapsedRealtime() + tickEventDelay, 0, 0, null, mTimeTickTrigger, TIME_TICK_TAG, flags, workSource, null, - Process.myUid(), "android"); + Process.myUid(), "android", null); // Finally, remember when we set the tick alarm synchronized (mLock) { @@ -3740,7 +3994,7 @@ public class AlarmManagerService extends SystemService { final WorkSource workSource = null; // Let system take blame for date change events. setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, null, null, AlarmManager.FLAG_STANDALONE, workSource, null, - Process.myUid(), "android"); + Process.myUid(), "android", null); } } @@ -4112,7 +4366,7 @@ public class AlarmManagerService extends SystemService { * Deliver an alarm and set up the post-delivery handling appropriately */ @GuardedBy("mLock") - public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) { + public void deliverLocked(Alarm alarm, long nowELAPSED) { final long workSourceToken = ThreadLocalWorkSource.setUid( getAlarmAttributionUid(alarm)); try { @@ -4122,10 +4376,8 @@ public class AlarmManagerService extends SystemService { try { alarm.operation.send(getContext(), 0, - mBackgroundIntent.putExtra( - Intent.EXTRA_ALARM_COUNT, alarm.count), - mDeliveryTracker, mHandler, null, - allowWhileIdle ? mIdleOptions : null); + mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count), + mDeliveryTracker, mHandler, null, alarm.mIdleOptions); } catch (PendingIntent.CanceledException e) { if (alarm.repeatInterval > 0) { // This IntentSender is no longer valid, but this @@ -4194,7 +4446,7 @@ public class AlarmManagerService extends SystemService { if (inflight.isBroadcast()) { notifyBroadcastAlarmPendingLocked(alarm.uid); } - if (allowWhileIdle) { + if (isAllowedWhileIdleRestricted(alarm)) { final boolean doze = (mPendingIdleUntil != null); final boolean batterySaver = (mAppStateTracker != null && mAppStateTracker.isForceAllAppsStandbyEnabled()); @@ -4204,8 +4456,7 @@ public class AlarmManagerService extends SystemService { mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage, UserHandle.getUserId(alarm.creatorUid), nowELAPSED); mAlarmStore.updateAlarmDeliveries(a -> { - if (a.creatorUid != alarm.creatorUid - || (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) { + if (a.creatorUid != alarm.creatorUid || !isAllowedWhileIdleRestricted(a)) { return false; } return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a)) diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index 3cefe65e45f9..164781a250b7 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -741,17 +741,26 @@ class JobConcurrencyManager { pw.increaseIndent(); try { - pw.print("Configuration:"); + pw.println("Configuration:"); pw.increaseIndent(); pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println(); + pw.println(); CONFIG_LIMITS_SCREEN_ON.normal.dump(pw); + pw.println(); CONFIG_LIMITS_SCREEN_ON.moderate.dump(pw); + pw.println(); CONFIG_LIMITS_SCREEN_ON.low.dump(pw); + pw.println(); CONFIG_LIMITS_SCREEN_ON.critical.dump(pw); + pw.println(); CONFIG_LIMITS_SCREEN_OFF.normal.dump(pw); + pw.println(); CONFIG_LIMITS_SCREEN_OFF.moderate.dump(pw); + pw.println(); CONFIG_LIMITS_SCREEN_OFF.low.dump(pw); + pw.println(); CONFIG_LIMITS_SCREEN_OFF.critical.dump(pw); + pw.println(); pw.decreaseIndent(); pw.print("Screen state: current "); @@ -770,18 +779,17 @@ class JobConcurrencyManager { pw.println(); - pw.println("Current max jobs:"); - pw.println(" "); + pw.print("Current work counts: "); pw.println(mWorkCountTracker); pw.println(); pw.print("mLastMemoryTrimLevel: "); - pw.print(mLastMemoryTrimLevel); + pw.println(mLastMemoryTrimLevel); pw.println(); pw.print("User Grace Period: "); - pw.print(mGracePeriodObserver.mGracePeriodExpiration); + pw.println(mGracePeriodObserver.mGracePeriodExpiration); pw.println(); mStatLogger.dump(pw); 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 96f3bcc58e8b..fdbc0864a59d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -3040,7 +3040,6 @@ public class JobSchedulerService extends com.android.server.SystemService pw.println(); for (int i = mJobRestrictions.size() - 1; i >= 0; i--) { - pw.print(" "); mJobRestrictions.get(i).dumpConstants(pw); pw.println(); } @@ -3067,7 +3066,6 @@ public class JobSchedulerService extends com.android.server.SystemService job.dump(pw, " ", true, nowElapsed); - pw.print(" Restricted due to:"); final boolean isRestricted = checkIfRestricted(job) != null; if (isRestricted) { @@ -3161,39 +3159,28 @@ public class JobSchedulerService extends com.android.server.SystemService } pw.println(); pw.println("Active jobs:"); + pw.increaseIndent(); for (int i=0; i<mActiveServices.size(); i++) { JobServiceContext jsc = mActiveServices.get(i); - pw.print(" Slot #"); pw.print(i); pw.print(": "); + pw.print("Slot #"); pw.print(i); pw.print(": "); + jsc.dumpLocked(pw, nowElapsed); + final JobStatus job = jsc.getRunningJobLocked(); - if (job == null) { - if (jsc.mStoppedReason != null) { - pw.print("inactive since "); - TimeUtils.formatDuration(jsc.mStoppedTime, nowElapsed, pw); - pw.print(", stopped because: "); - pw.println(jsc.mStoppedReason); - } else { - pw.println("inactive"); - } - continue; - } else { - pw.println(job.toShortString()); - pw.print(" Running for: "); - TimeUtils.formatDuration(nowElapsed - jsc.getExecutionStartTimeElapsed(), pw); - pw.print(", timeout at: "); - TimeUtils.formatDuration(jsc.getTimeoutElapsed() - nowElapsed, pw); - pw.println(); - job.dump(pw, " ", false, nowElapsed); - int priority = evaluateJobPriorityLocked(job); - pw.print(" Evaluated priority: "); - pw.println(JobInfo.getPriorityString(priority)); + if (job != null) { + pw.increaseIndent(); + job.dump(pw, " ", false, nowElapsed); + pw.print("Evaluated priority: "); + pw.println(JobInfo.getPriorityString(job.lastEvaluatedPriority)); - pw.print(" Active at "); + pw.print("Active at "); TimeUtils.formatDuration(job.madeActive - nowUptime, pw); pw.print(", pending for "); TimeUtils.formatDuration(job.madeActive - job.madePending, pw); pw.println(); + pw.decreaseIndent(); } } + pw.decreaseIndent(); if (filterUid == -1) { pw.println(); pw.print("mReadyToRock="); pw.println(mReadyToRock); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index da6f9fe0ace7..0aca2461b41c 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -43,6 +43,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.WorkSource; import android.util.EventLog; +import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.TimeUtils; @@ -901,8 +902,30 @@ public final class JobServiceContext implements ServiceConnection { mTimeoutElapsed = sElapsedRealtimeClock.millis() + timeoutMillis; } - private void removeOpTimeOutLocked() { mCallbackHandler.removeMessages(MSG_TIMEOUT); } + + void dumpLocked(IndentingPrintWriter pw, final long nowElapsed) { + if (mRunningJob == null) { + if (mStoppedReason != null) { + pw.print("inactive since "); + TimeUtils.formatDuration(mStoppedTime, nowElapsed, pw); + pw.print(", stopped because: "); + pw.println(mStoppedReason); + } else { + pw.println("inactive"); + } + } else { + pw.println(mRunningJob.toShortString()); + + pw.increaseIndent(); + pw.print("Running for: "); + TimeUtils.formatDuration(nowElapsed - mExecutionStartTimeElapsed, pw); + pw.print(", timeout at: "); + TimeUtils.formatDuration(mTimeoutElapsed - nowElapsed, pw); + pw.println(); + pw.decreaseIndent(); + } + } } diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp index 3bad889f1715..5d64579f080e 100644 --- a/cmds/idmap2/Android.bp +++ b/cmds/idmap2/Android.bp @@ -26,10 +26,24 @@ cc_defaults { tidy_checks_as_errors: [ "modernize-*", "-modernize-avoid-c-arrays", + "-modernize-pass-by-value", + "-modernize-replace-disallow-copy-and-assign-macro", + "-modernize-use-equals-default", + "-modernize-use-nodiscard", + "-modernize-use-override", "-modernize-use-trailing-return-type", + "-modernize-use-using", "android-*", "misc-*", + "-misc-non-private-member-variables-in-classes", "readability-*", + "-readability-braces-around-statements", + "-readability-const-return-type", + "-readability-convert-member-functions-to-static", + "-readability-else-after-return", + "-readability-named-parameter", + "-readability-redundant-access-specifiers", + "-readability-uppercase-literal-suffix", ], tidy_flags: [ "-system-headers", diff --git a/core/api/current.txt b/core/api/current.txt index 0e64a9d14fae..a21b6938c189 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -145,6 +145,7 @@ package android { field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE"; field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY"; field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES"; + field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM"; field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE"; field public static final String SEND_SMS = "android.permission.SEND_SMS"; field public static final String SET_ALARM = "com.android.alarm.permission.SET_ALARM"; @@ -1713,30 +1714,42 @@ package android { field @Deprecated public static final int secondary_text_dark_nodisable = 17170438; // 0x1060006 field @Deprecated public static final int secondary_text_light = 17170439; // 0x1060007 field @Deprecated public static final int secondary_text_light_nodisable = 17170440; // 0x1060008 - field public static final int system_accent_0 = 17170473; // 0x1060029 - field public static final int system_accent_100 = 17170475; // 0x106002b - field public static final int system_accent_1000 = 17170484; // 0x1060034 - field public static final int system_accent_200 = 17170476; // 0x106002c - field public static final int system_accent_300 = 17170477; // 0x106002d - field public static final int system_accent_400 = 17170478; // 0x106002e - field public static final int system_accent_50 = 17170474; // 0x106002a - field public static final int system_accent_500 = 17170479; // 0x106002f - field public static final int system_accent_600 = 17170480; // 0x1060030 - field public static final int system_accent_700 = 17170481; // 0x1060031 - field public static final int system_accent_800 = 17170482; // 0x1060032 - field public static final int system_accent_900 = 17170483; // 0x1060033 - field public static final int system_main_0 = 17170461; // 0x106001d - field public static final int system_main_100 = 17170463; // 0x106001f - field public static final int system_main_1000 = 17170472; // 0x1060028 - field public static final int system_main_200 = 17170464; // 0x1060020 - field public static final int system_main_300 = 17170465; // 0x1060021 - field public static final int system_main_400 = 17170466; // 0x1060022 - field public static final int system_main_50 = 17170462; // 0x106001e - field public static final int system_main_500 = 17170467; // 0x1060023 - field public static final int system_main_600 = 17170468; // 0x1060024 - field public static final int system_main_700 = 17170469; // 0x1060025 - field public static final int system_main_800 = 17170470; // 0x1060026 - field public static final int system_main_900 = 17170471; // 0x1060027 + field public static final int system_neutral_0 = 17170485; // 0x1060035 + field public static final int system_neutral_100 = 17170487; // 0x1060037 + field public static final int system_neutral_1000 = 17170496; // 0x1060040 + field public static final int system_neutral_200 = 17170488; // 0x1060038 + field public static final int system_neutral_300 = 17170489; // 0x1060039 + field public static final int system_neutral_400 = 17170490; // 0x106003a + field public static final int system_neutral_50 = 17170486; // 0x1060036 + field public static final int system_neutral_500 = 17170491; // 0x106003b + field public static final int system_neutral_600 = 17170492; // 0x106003c + field public static final int system_neutral_700 = 17170493; // 0x106003d + field public static final int system_neutral_800 = 17170494; // 0x106003e + field public static final int system_neutral_900 = 17170495; // 0x106003f + field public static final int system_primary_0 = 17170461; // 0x106001d + field public static final int system_primary_100 = 17170463; // 0x106001f + field public static final int system_primary_1000 = 17170472; // 0x1060028 + field public static final int system_primary_200 = 17170464; // 0x1060020 + field public static final int system_primary_300 = 17170465; // 0x1060021 + field public static final int system_primary_400 = 17170466; // 0x1060022 + field public static final int system_primary_50 = 17170462; // 0x106001e + field public static final int system_primary_500 = 17170467; // 0x1060023 + field public static final int system_primary_600 = 17170468; // 0x1060024 + field public static final int system_primary_700 = 17170469; // 0x1060025 + field public static final int system_primary_800 = 17170470; // 0x1060026 + field public static final int system_primary_900 = 17170471; // 0x1060027 + field public static final int system_secondary_0 = 17170473; // 0x1060029 + field public static final int system_secondary_100 = 17170475; // 0x106002b + field public static final int system_secondary_1000 = 17170484; // 0x1060034 + field public static final int system_secondary_200 = 17170476; // 0x106002c + field public static final int system_secondary_300 = 17170477; // 0x106002d + field public static final int system_secondary_400 = 17170478; // 0x106002e + field public static final int system_secondary_50 = 17170474; // 0x106002a + field public static final int system_secondary_500 = 17170479; // 0x106002f + field public static final int system_secondary_600 = 17170480; // 0x1060030 + field public static final int system_secondary_700 = 17170481; // 0x1060031 + field public static final int system_secondary_800 = 17170482; // 0x1060032 + field public static final int system_secondary_900 = 17170483; // 0x1060033 field public static final int tab_indicator_text = 17170441; // 0x1060009 field @Deprecated public static final int tertiary_text_dark = 17170448; // 0x1060010 field @Deprecated public static final int tertiary_text_light = 17170449; // 0x1060011 @@ -4316,16 +4329,17 @@ package android.app { } public class AlarmManager { + method public boolean canScheduleExactAlarms(); method public void cancel(android.app.PendingIntent); method public void cancel(android.app.AlarmManager.OnAlarmListener); method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock(); method public void set(int, long, android.app.PendingIntent); method public void set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler); - method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent); + method @RequiresPermission(android.Manifest.permission.SCHEDULE_EXACT_ALARM) public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent); method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent); method public void setExact(int, long, android.app.PendingIntent); method public void setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler); - method public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent); + method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent); method public void setInexactRepeating(int, long, long, android.app.PendingIntent); method public void setRepeating(int, long, long, android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.SET_TIME) public void setTime(long); @@ -7092,6 +7106,7 @@ package android.app.admin { method public boolean isManagedProfile(@NonNull android.content.ComponentName); method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName); method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName); + method public boolean isNetworkSlicingEnabled(); method public boolean isOrganizationOwnedDeviceWithManagedProfile(); method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName); method public boolean isPackageSuspended(@NonNull android.content.ComponentName, String) throws android.content.pm.PackageManager.NameNotFoundException; @@ -7104,6 +7119,7 @@ package android.app.admin { method public boolean isUniqueDeviceAttestationSupported(); method public boolean isUsbDataSignalingEnabled(); method public boolean isUsingUnifiedPassword(@NonNull android.content.ComponentName); + method @NonNull public java.util.List<android.os.UserHandle> listForegroundAffiliatedUsers(); method public void lockNow(); method public void lockNow(int); method public int logoutUser(@NonNull android.content.ComponentName); @@ -7163,6 +7179,7 @@ package android.app.admin { method public void setMaximumTimeToLock(@NonNull android.content.ComponentName, long); method @NonNull public java.util.List<java.lang.String> setMeteredDataDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>); method public void setNetworkLoggingEnabled(@Nullable android.content.ComponentName, boolean); + method public void setNetworkSlicingEnabled(boolean); method @Deprecated public void setOrganizationColor(@NonNull android.content.ComponentName, int); method public void setOrganizationId(@NonNull String); method public void setOrganizationName(@NonNull android.content.ComponentName, @Nullable CharSequence); @@ -12206,6 +12223,7 @@ package android.content.pm { method public void setAutoRevokePermissionsMode(boolean); method public void setInstallLocation(int); method public void setInstallReason(int); + method public void setInstallScenario(int); method public void setMultiPackage(); method public void setOriginatingUid(int); method public void setOriginatingUri(@Nullable android.net.Uri); @@ -12428,6 +12446,7 @@ package android.content.pm { field public static final String FEATURE_INPUT_METHODS = "android.software.input_methods"; field public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels"; field public static final String FEATURE_IRIS = "android.hardware.biometrics.iris"; + field public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY = "android.hardware.keystore.app_attest_key"; field public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY = "android.hardware.keystore.limited_use_key"; field public static final String FEATURE_KEYSTORE_SINGLE_USE_KEY = "android.hardware.keystore.single_use_key"; field public static final String FEATURE_LEANBACK = "android.software.leanback"; @@ -12534,6 +12553,10 @@ package android.content.pm { field public static final int INSTALL_REASON_POLICY = 1; // 0x1 field public static final int INSTALL_REASON_UNKNOWN = 0; // 0x0 field public static final int INSTALL_REASON_USER = 4; // 0x4 + field public static final int INSTALL_SCENARIO_BULK = 2; // 0x2 + field public static final int INSTALL_SCENARIO_BULK_SECONDARY = 3; // 0x3 + field public static final int INSTALL_SCENARIO_DEFAULT = 0; // 0x0 + field public static final int INSTALL_SCENARIO_FAST = 1; // 0x1 field public static final int MATCH_ALL = 131072; // 0x20000 field public static final int MATCH_APEX = 1073741824; // 0x40000000 field public static final int MATCH_DEFAULT_ONLY = 65536; // 0x10000 @@ -17686,6 +17709,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> SCALER_AVAILABLE_MAX_DIGITAL_ZOOM; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SCALER_AVAILABLE_ROTATE_AND_CROP_MODES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SCALER_CROPPING_TYPE; + 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_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP; @@ -20993,7 +21017,7 @@ package android.media { method @NonNull public String getDiagnosticInfo(); } - public final class MediaCodec { + public final class MediaCodec implements android.media.metrics.PlaybackComponent { method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, @Nullable android.media.MediaCrypto, int); method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, int, @Nullable android.media.MediaDescrambler); method @NonNull public static android.media.MediaCodec createByCodecName(@NonNull String) throws java.io.IOException; @@ -21019,6 +21043,7 @@ package android.media { method @NonNull public android.media.MediaFormat getOutputFormat(int); method @NonNull public android.media.MediaCodec.OutputFrame getOutputFrame(int); method @Nullable public android.media.Image getOutputImage(int); + method public String getPlaybackId(); method @NonNull public android.media.MediaCodec.QueueRequest getQueueRequest(int); method @Nullable public static android.media.Image mapHardwareBuffer(@NonNull android.hardware.HardwareBuffer); method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException; @@ -21034,6 +21059,7 @@ package android.media { method public void setOnFrameRenderedListener(@Nullable android.media.MediaCodec.OnFrameRenderedListener, @Nullable android.os.Handler); method public void setOutputSurface(@NonNull android.view.Surface); method public void setParameters(@Nullable android.os.Bundle); + method public void setPlaybackId(@NonNull String); method public void setVideoScalingMode(int); method public void signalEndOfInputStream(); method public void start(); @@ -21634,6 +21660,7 @@ package android.media { method @android.media.MediaDrm.HdcpLevel public int getConnectedHdcpLevel(); method public android.media.MediaDrm.CryptoSession getCryptoSession(@NonNull byte[], @NonNull String, @NonNull String); method @NonNull public android.media.MediaDrm.KeyRequest getKeyRequest(@NonNull byte[], @Nullable byte[], @Nullable String, int, @Nullable java.util.HashMap<java.lang.String,java.lang.String>) throws android.media.NotProvisionedException; + method @NonNull public java.util.List<android.media.MediaDrm.LogMessage> getLogMessages(); method @android.media.MediaDrm.HdcpLevel public int getMaxHdcpLevel(); method public static int getMaxSecurityLevel(); method public int getMaxSessionCount(); @@ -21742,6 +21769,12 @@ package android.media { field public static final int STATUS_USABLE_IN_FUTURE = 5; // 0x5 } + public static class MediaDrm.LogMessage { + field @NonNull public final String message; + field public final int priority; + field public final long timestampMillis; + } + public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException { method @NonNull public String getDiagnosticInfo(); } @@ -24100,6 +24133,34 @@ package android.media.metrics { field public static final long INVALID_TIMESTAMP = -1L; // 0xffffffffffffffffL } + public final class NetworkEvent extends android.media.metrics.Event implements android.os.Parcelable { + method public int describeContents(); + method public int getNetworkType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.NetworkEvent> CREATOR; + field public static final int NETWORK_TYPE_2G = 4; // 0x4 + field public static final int NETWORK_TYPE_3G = 5; // 0x5 + field public static final int NETWORK_TYPE_4G = 6; // 0x6 + field public static final int NETWORK_TYPE_5G_NSA = 7; // 0x7 + field public static final int NETWORK_TYPE_5G_SA = 8; // 0x8 + field public static final int NETWORK_TYPE_ETHERNET = 3; // 0x3 + field public static final int NETWORK_TYPE_NONE = 0; // 0x0 + field public static final int NETWORK_TYPE_OTHER = 1; // 0x1 + field public static final int NETWORK_TYPE_WIFI = 2; // 0x2 + } + + public static final class NetworkEvent.Builder { + ctor public NetworkEvent.Builder(); + method @NonNull public android.media.metrics.NetworkEvent build(); + method @NonNull public android.media.metrics.NetworkEvent.Builder setNetworkType(int); + method @NonNull public android.media.metrics.NetworkEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long); + } + + public interface PlaybackComponent { + method @NonNull public String getPlaybackId(); + method public void setPlaybackId(@NonNull String); + } + public final class PlaybackErrorEvent extends android.media.metrics.Event implements android.os.Parcelable { method public int describeContents(); method public int getErrorCode(); @@ -24120,11 +24181,78 @@ package android.media.metrics { method @NonNull public android.media.metrics.PlaybackErrorEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long); } + public final class PlaybackMetrics implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getAudioUnderrunCount(); + method public int getContentType(); + method public int getDrmType(); + method @NonNull public long[] getExperimentIds(); + method @IntRange(from=0xffffffff) public long getLocalBytesRead(); + method @IntRange(from=0xffffffff) public long getMediaDurationMillis(); + method @IntRange(from=0xffffffff) public long getNetworkBytesRead(); + method @IntRange(from=0xffffffff) public long getNetworkTransferDurationMillis(); + method public int getPlaybackType(); + method @Nullable public String getPlayerName(); + method @Nullable public String getPlayerVersion(); + method public int getStreamSource(); + method public int getStreamType(); + method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getVideoFramesDropped(); + method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getVideoFramesPlayed(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int CONTENT_TYPE_AD = 1; // 0x1 + field public static final int CONTENT_TYPE_MAIN = 0; // 0x0 + field public static final int CONTENT_TYPE_OTHER = 2; // 0x2 + field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.PlaybackMetrics> CREATOR; + field public static final int DRM_TYPE_CLEARKEY = 6; // 0x6 + field public static final int DRM_TYPE_NONE = 0; // 0x0 + field public static final int DRM_TYPE_OTHER = 1; // 0x1 + field public static final int DRM_TYPE_PLAY_READY = 2; // 0x2 + field public static final int DRM_TYPE_WIDEVINE_L1 = 3; // 0x3 + field public static final int DRM_TYPE_WIDEVINE_L3 = 4; // 0x4 + field public static final int DRM_TYPE_WV_L3_FALLBACK = 5; // 0x5 + field public static final int PLAYBACK_TYPE_LIVE = 1; // 0x1 + field public static final int PLAYBACK_TYPE_OTHER = 2; // 0x2 + field public static final int PLAYBACK_TYPE_VOD = 0; // 0x0 + field public static final int STREAM_SOURCE_DEVICE = 2; // 0x2 + field public static final int STREAM_SOURCE_MIXED = 3; // 0x3 + field public static final int STREAM_SOURCE_NETWORK = 1; // 0x1 + field public static final int STREAM_SOURCE_UNKNOWN = 0; // 0x0 + field public static final int STREAM_TYPE_DASH = 3; // 0x3 + field public static final int STREAM_TYPE_HLS = 4; // 0x4 + field public static final int STREAM_TYPE_OTHER = 1; // 0x1 + field public static final int STREAM_TYPE_PROGRESSIVE = 2; // 0x2 + field public static final int STREAM_TYPE_SS = 5; // 0x5 + field public static final int STREAM_TYPE_UNKNOWN = 0; // 0x0 + } + + public static final class PlaybackMetrics.Builder { + ctor public PlaybackMetrics.Builder(); + method @NonNull public android.media.metrics.PlaybackMetrics.Builder addExperimentId(long); + method @NonNull public android.media.metrics.PlaybackMetrics build(); + method @NonNull public android.media.metrics.PlaybackMetrics.Builder setAudioUnderrunCount(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int); + method @NonNull public android.media.metrics.PlaybackMetrics.Builder setContentType(int); + method @NonNull public android.media.metrics.PlaybackMetrics.Builder setDrmType(int); + method @NonNull public android.media.metrics.PlaybackMetrics.Builder setLocalBytesRead(@IntRange(from=0xffffffff) long); + method @NonNull public android.media.metrics.PlaybackMetrics.Builder setMediaDurationMillis(@IntRange(from=0xffffffff) long); + method @NonNull public android.media.metrics.PlaybackMetrics.Builder setNetworkBytesRead(@IntRange(from=0xffffffff) long); + method @NonNull public android.media.metrics.PlaybackMetrics.Builder setNetworkTransferDurationMillis(@IntRange(from=0xffffffff) long); + method @NonNull public android.media.metrics.PlaybackMetrics.Builder setPlaybackType(int); + method @NonNull public android.media.metrics.PlaybackMetrics.Builder setPlayerName(@NonNull String); + method @NonNull public android.media.metrics.PlaybackMetrics.Builder setPlayerVersion(@NonNull String); + method @NonNull public android.media.metrics.PlaybackMetrics.Builder setStreamSource(int); + method @NonNull public android.media.metrics.PlaybackMetrics.Builder setStreamType(int); + method @NonNull public android.media.metrics.PlaybackMetrics.Builder setVideoFramesDropped(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int); + method @NonNull public android.media.metrics.PlaybackMetrics.Builder setVideoFramesPlayed(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int); + } + public final class PlaybackSession implements java.lang.AutoCloseable { method public void close(); method @NonNull public String getId(); + method public void reportNetworkEvent(@NonNull android.media.metrics.NetworkEvent); method public void reportPlaybackErrorEvent(@NonNull android.media.metrics.PlaybackErrorEvent); + method public void reportPlaybackMetrics(@NonNull android.media.metrics.PlaybackMetrics); method public void reportPlaybackStateEvent(@NonNull android.media.metrics.PlaybackStateEvent); + method public void reportTrackChangeEvent(@NonNull android.media.metrics.TrackChangeEvent); } public final class PlaybackStateEvent extends android.media.metrics.Event implements android.os.Parcelable { @@ -24156,6 +24284,54 @@ package android.media.metrics { method @NonNull public android.media.metrics.PlaybackStateEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long); } + public final class TrackChangeEvent extends android.media.metrics.Event implements android.os.Parcelable { + ctor public TrackChangeEvent(int, int, @Nullable String, @Nullable String, @Nullable String, int, long, int, @Nullable String, @Nullable String, int, int, int, int); + method public int describeContents(); + method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getBitrate(); + method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getChannelCount(); + method @Nullable public String getCodecName(); + method @Nullable public String getContainerMimeType(); + method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getHeight(); + method @Nullable public String getLanguage(); + method @Nullable public String getLanguageRegion(); + method @Nullable public String getSampleMimeType(); + method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getSampleRate(); + method public int getTrackChangeReason(); + method public int getTrackState(); + method public int getTrackType(); + method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getWidth(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.TrackChangeEvent> CREATOR; + field public static final int TRACK_CHANGE_REASON_ADAPTIVE = 4; // 0x4 + field public static final int TRACK_CHANGE_REASON_INITIAL = 2; // 0x2 + field public static final int TRACK_CHANGE_REASON_MANUAL = 3; // 0x3 + field public static final int TRACK_CHANGE_REASON_OTHER = 1; // 0x1 + field public static final int TRACK_CHANGE_REASON_UNKNOWN = 0; // 0x0 + field public static final int TRACK_STATE_OFF = 0; // 0x0 + field public static final int TRACK_STATE_ON = 1; // 0x1 + field public static final int TRACK_TYPE_AUDIO = 0; // 0x0 + field public static final int TRACK_TYPE_TEXT = 2; // 0x2 + field public static final int TRACK_TYPE_VIDEO = 1; // 0x1 + } + + public static final class TrackChangeEvent.Builder { + ctor public TrackChangeEvent.Builder(int); + method @NonNull public android.media.metrics.TrackChangeEvent build(); + method @NonNull public android.media.metrics.TrackChangeEvent.Builder setBitrate(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int); + method @NonNull public android.media.metrics.TrackChangeEvent.Builder setChannelCount(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int); + method @NonNull public android.media.metrics.TrackChangeEvent.Builder setCodecName(@NonNull String); + method @NonNull public android.media.metrics.TrackChangeEvent.Builder setContainerMimeType(@NonNull String); + method @NonNull public android.media.metrics.TrackChangeEvent.Builder setHeight(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int); + method @NonNull public android.media.metrics.TrackChangeEvent.Builder setLanguage(@NonNull String); + method @NonNull public android.media.metrics.TrackChangeEvent.Builder setLanguageRegion(@NonNull String); + method @NonNull public android.media.metrics.TrackChangeEvent.Builder setSampleMimeType(@NonNull String); + method @NonNull public android.media.metrics.TrackChangeEvent.Builder setSampleRate(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int); + method @NonNull public android.media.metrics.TrackChangeEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long); + method @NonNull public android.media.metrics.TrackChangeEvent.Builder setTrackChangeReason(int); + method @NonNull public android.media.metrics.TrackChangeEvent.Builder setTrackState(int); + method @NonNull public android.media.metrics.TrackChangeEvent.Builder setWidth(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int); + } + } package android.media.midi { @@ -26794,6 +26970,46 @@ package android.net.sip { } +package android.net.vcn { + + public final class VcnConfig implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnConfig> CREATOR; + } + + public static final class VcnConfig.Builder { + ctor public VcnConfig.Builder(@NonNull android.content.Context); + method @NonNull public android.net.vcn.VcnConfig.Builder addGatewayConnectionConfig(@NonNull android.net.vcn.VcnGatewayConnectionConfig); + method @NonNull public android.net.vcn.VcnConfig build(); + } + + public final class VcnGatewayConnectionConfig { + method @NonNull public int[] getExposedCapabilities(); + method @IntRange(from=android.net.vcn.VcnGatewayConnectionConfig.MIN_MTU_V6) public int getMaxMtu(); + method @NonNull public int[] getRequiredUnderlyingCapabilities(); + method @NonNull public long[] getRetryInterval(); + } + + public static final class VcnGatewayConnectionConfig.Builder { + ctor public VcnGatewayConnectionConfig.Builder(); + method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int); + method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addRequiredUnderlyingCapability(int); + method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build(); + method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int); + method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeRequiredUnderlyingCapability(int); + method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=android.net.vcn.VcnGatewayConnectionConfig.MIN_MTU_V6) int); + method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryInterval(@NonNull long[]); + } + + public class VcnManager { + method @RequiresPermission("carrier privileges") public void clearVcnConfig(@NonNull android.os.ParcelUuid) throws java.io.IOException; + method @RequiresPermission("carrier privileges") public void setVcnConfig(@NonNull android.os.ParcelUuid, @NonNull android.net.vcn.VcnConfig) throws java.io.IOException; + } + +} + package android.nfc { public class FormatException extends java.lang.Exception { @@ -31586,6 +31802,7 @@ package android.os { method public boolean isQuietModeEnabled(android.os.UserHandle); method public boolean isSystemUser(); method public boolean isUserAGoat(); + method public boolean isUserForeground(); method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunning(android.os.UserHandle); method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunningOrStopping(android.os.UserHandle); method public boolean isUserUnlocked(); @@ -37037,6 +37254,7 @@ package android.security.keystore { public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec { method @Nullable public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec(); + method @Nullable public String getAttestKeyAlias(); method public byte[] getAttestationChallenge(); method @NonNull public String[] getBlockModes(); method @NonNull public java.util.Date getCertificateNotAfter(); @@ -37071,6 +37289,7 @@ package android.security.keystore { ctor public KeyGenParameterSpec.Builder(@NonNull String, int); method @NonNull public android.security.keystore.KeyGenParameterSpec build(); method public android.security.keystore.KeyGenParameterSpec.Builder setAlgorithmParameterSpec(@NonNull java.security.spec.AlgorithmParameterSpec); + method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestKeyAlias(@Nullable String); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestationChallenge(byte[]); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setBlockModes(java.lang.String...); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotAfter(@NonNull java.util.Date); @@ -37168,6 +37387,7 @@ package android.security.keystore { field public static final int ORIGIN_SECURELY_IMPORTED = 8; // 0x8 field public static final int ORIGIN_UNKNOWN = 4; // 0x4 field public static final int PURPOSE_AGREE_KEY = 64; // 0x40 + field public static final int PURPOSE_ATTEST_KEY = 128; // 0x80 field public static final int PURPOSE_DECRYPT = 2; // 0x2 field public static final int PURPOSE_ENCRYPT = 1; // 0x1 field public static final int PURPOSE_SIGN = 4; // 0x4 @@ -38806,7 +39026,9 @@ package android.speech { field public static final int ERROR_NO_MATCH = 7; // 0x7 field public static final int ERROR_RECOGNIZER_BUSY = 8; // 0x8 field public static final int ERROR_SERVER = 4; // 0x4 + field public static final int ERROR_SERVER_DISCONNECTED = 11; // 0xb field public static final int ERROR_SPEECH_TIMEOUT = 6; // 0x6 + field public static final int ERROR_TOO_MANY_REQUESTS = 10; // 0xa field public static final String RESULTS_RECOGNITION = "results_recognition"; } @@ -39207,16 +39429,22 @@ package android.telecom { } public static class CallScreeningService.CallResponse { + method public int getCallComposerAttachmentsToShow(); method public boolean getDisallowCall(); method public boolean getRejectCall(); method public boolean getSilenceCall(); method public boolean getSkipCallLog(); method public boolean getSkipNotification(); + field public static final int CALL_COMPOSER_ATTACHMENT_LOCATION = 2; // 0x2 + field public static final int CALL_COMPOSER_ATTACHMENT_PICTURE = 1; // 0x1 + field public static final int CALL_COMPOSER_ATTACHMENT_PRIORITY = 8; // 0x8 + field public static final int CALL_COMPOSER_ATTACHMENT_SUBJECT = 4; // 0x4 } public static class CallScreeningService.CallResponse.Builder { ctor public CallScreeningService.CallResponse.Builder(); method public android.telecom.CallScreeningService.CallResponse build(); + method @NonNull public android.telecom.CallScreeningService.CallResponse.Builder setCallComposerAttachmentsToShow(int); method public android.telecom.CallScreeningService.CallResponse.Builder setDisallowCall(boolean); method public android.telecom.CallScreeningService.CallResponse.Builder setRejectCall(boolean); method @NonNull public android.telecom.CallScreeningService.CallResponse.Builder setSilenceCall(boolean); @@ -40366,6 +40594,7 @@ package android.telephony { field public static final String KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY = "gsm_roaming_networks_string_array"; field public static final String KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL = "has_in_call_noise_suppression_bool"; field public static final String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool"; + field public static final String KEY_HIDE_ENABLE_2G = "hide_enable_2g_bool"; field public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool"; field public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool"; field public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 84e6f22b384e..deff7b38ea2a 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -14,6 +14,7 @@ package android { field public static final String ACCESS_MTP = "android.permission.ACCESS_MTP"; field public static final String ACCESS_NETWORK_CONDITIONS = "android.permission.ACCESS_NETWORK_CONDITIONS"; field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS"; + field public static final String ACCESS_RCS_USER_CAPABILITY_EXCHANGE = "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"; field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES"; field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS"; field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER"; @@ -195,6 +196,7 @@ package android { field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG"; field public static final String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE"; field public static final String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS"; + field public static final String READ_NETWORK_DEVICE_CONFIG = "android.permission.READ_NETWORK_DEVICE_CONFIG"; field public static final String READ_NETWORK_USAGE_HISTORY = "android.permission.READ_NETWORK_USAGE_HISTORY"; field public static final String READ_OEM_UNLOCK_STATE = "android.permission.READ_OEM_UNLOCK_STATE"; field public static final String READ_PEOPLE_DATA = "android.permission.READ_PEOPLE_DATA"; @@ -345,6 +347,7 @@ package android { field public static final int config_helpPackageNameValue = 17039388; // 0x104001c field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028 field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029 + field public static final int config_systemContacts = 17039403; // 0x104002b field public static final int config_systemGallery = 17039399; // 0x1040027 field public static final int config_systemShell = 17039402; // 0x104002a } @@ -892,6 +895,7 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedKiosk(); + method public boolean isNetworkSlicingEnabledForUser(@NonNull android.os.UserHandle); method public boolean isSecondaryLockscreenEnabled(@NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUnattendedManagedKiosk(); method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long); @@ -8959,6 +8963,16 @@ package android.os.storage { package android.permission { + public final class AdminPermissionControlParams implements android.os.Parcelable { + method public boolean canAdminGrantSensorsPermissions(); + method public int describeContents(); + method public int getGrantState(); + method @NonNull public String getGranteePackageName(); + method @NonNull public String getPermission(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.permission.AdminPermissionControlParams> CREATOR; + } + public final class PermissionControllerManager { method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>); @@ -8990,7 +9004,8 @@ package android.permission { method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>); - method @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>); + method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>); + method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); method @BinderThread public void onUpdateUserSensitivePermissionFlags(int, @NonNull java.util.concurrent.Executor, @NonNull Runnable); method @BinderThread public void onUpdateUserSensitivePermissionFlags(int, @NonNull Runnable); @@ -10756,7 +10771,7 @@ package android.telecom { method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle(); method @Nullable public final String getTelecomCallId(); method @Deprecated public void onAudioStateChanged(android.telecom.AudioState); - method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean); + method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean, @Nullable android.telecom.CallScreeningService.CallResponse, boolean); method public final void resetConnectionTime(); method public void setCallDirection(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectTimeMillis(@IntRange(from=0) long); @@ -10933,7 +10948,7 @@ package android.telecom { } public final class RemoteConnection { - method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean); + method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean, @Nullable android.telecom.CallScreeningService.CallResponse, boolean); method @Deprecated public void setAudioState(android.telecom.AudioState); } @@ -12012,6 +12027,7 @@ package android.telephony { field public static final String ACTION_SIM_CARD_STATE_CHANGED = "android.telephony.action.SIM_CARD_STATE_CHANGED"; field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED"; field public static final int ALLOWED_NETWORK_TYPES_REASON_CARRIER = 2; // 0x2 + field public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3; // 0x3 field public static final int ALLOWED_NETWORK_TYPES_REASON_POWER = 1; // 0x1 field public static final int ALLOWED_NETWORK_TYPES_REASON_USER = 0; // 0x0 field public static final int CALL_WAITING_STATUS_DISABLED = 2; // 0x2 @@ -13446,8 +13462,8 @@ package android.telephony.ims { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void addOnPublishStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getUcePublishState() throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeOnPublishStateChangedListener(@NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException; - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestAvailability(@NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException; - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void requestCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException; + method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestAvailability(@NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException; + method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException; field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2 field public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 1; // 0x1 @@ -13726,10 +13742,16 @@ package android.telephony.ims.feature { package android.telephony.ims.stub { public interface CapabilityExchangeEventListener { + method public void onRemoteCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback) throws android.telephony.ims.ImsException; method public void onRequestPublishCapabilities(int) throws android.telephony.ims.ImsException; method public void onUnpublish() throws android.telephony.ims.ImsException; } + public static interface CapabilityExchangeEventListener.OptionsRequestCallback { + method public default void onRespondToCapabilityRequest(@NonNull android.telephony.ims.RcsContactUceCapability, boolean); + method public void onRespondToCapabilityRequestWithError(@IntRange(from=100, to=699) int, @NonNull String); + } + public interface DelegateConnectionMessageCallback { method public void onMessageReceived(@NonNull android.telephony.ims.SipMessage); method public void onMessageSendFailure(@NonNull String, int); @@ -13916,6 +13938,7 @@ package android.telephony.ims.stub { public class RcsCapabilityExchangeImplBase { ctor public RcsCapabilityExchangeImplBase(@NonNull java.util.concurrent.Executor); method public void publishCapabilities(@NonNull String, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.PublishResponseCallback); + method public void sendOptionsCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback); method public void subscribeForCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.SubscribeResponseCallback); field public static final int COMMAND_CODE_FETCH_ERROR = 3; // 0x3 field public static final int COMMAND_CODE_GENERIC_FAILURE = 1; // 0x1 @@ -13930,6 +13953,11 @@ package android.telephony.ims.stub { field public static final int COMMAND_CODE_SERVICE_UNKNOWN = 0; // 0x0 } + public static interface RcsCapabilityExchangeImplBase.OptionsResponseCallback { + method public void onCommandError(int) throws android.telephony.ims.ImsException; + method public void onNetworkResponse(int, @NonNull String, @NonNull java.util.List<java.lang.String>) throws android.telephony.ims.ImsException; + } + public static interface RcsCapabilityExchangeImplBase.PublishResponseCallback { method public void onCommandError(int) throws android.telephony.ims.ImsException; method public void onNetworkResponse(@IntRange(from=100, to=699) int, @NonNull String) throws android.telephony.ims.ImsException; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index c03f660c21b8..ff96f92da2f5 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1232,11 +1232,6 @@ package android.media { method public void forceResourceLost(); } - public final class MediaCodec implements android.media.metrics.PlaybackComponent { - method public String getPlaybackId(); - method public void setPlaybackId(@NonNull String); - } - public static final class MediaCodecInfo.VideoCapabilities.PerformancePoint { ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(int, int, int, int, @NonNull android.util.Size); ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(@NonNull android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint, @NonNull android.util.Size); @@ -1311,15 +1306,6 @@ package android.media.audiopolicy { } -package android.media.metrics { - - public interface PlaybackComponent { - method @NonNull public String getPlaybackId(); - method public void setPlaybackId(@NonNull String); - } - -} - package android.media.tv { public final class TvInputManager { diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index b85b1861aa02..1906ee4d85d2 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -795,7 +795,8 @@ public class AppOpsManager { // when adding one of these: // - increment _NUM_OP // - define an OPSTR_* constant (marked as @SystemApi) - // - add rows to sOpToSwitch, sOpToString, sOpNames, sOpToPerms, sOpDefault + // - add rows to sOpToSwitch, sOpToString, sOpNames, sOpPerms, sOpDefaultMode, sOpDisableReset, + // sOpRestrictions, sOpAllowSystemRestrictionBypass // - add descriptive strings to Settings/res/values/arrays.xml // - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app) @@ -1174,13 +1175,19 @@ public class AppOpsManager { * * @hide */ - // TODO: Add as AppProtoEnums - public static final int OP_RECORD_AUDIO_OUTPUT = 106; + public static final int OP_RECORD_AUDIO_OUTPUT = AppProtoEnums.APP_OP_RECORD_AUDIO_OUTPUT; + + /** + * App can schedule exact alarm to perform timing based background work + * + * @hide + */ + public static final int OP_SCHEDULE_EXACT_ALARM = AppProtoEnums.APP_OP_SCHEDULE_EXACT_ALARM; /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 107; + public static final int _NUM_OP = 108; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1553,6 +1560,13 @@ public class AppOpsManager { */ public static final String OPSTR_RECORD_AUDIO_OUTPUT = "android:record_audio_output"; + /** + * App can schedule exact alarm to perform timing based background work. + * + * @hide + */ + public static final String OPSTR_SCHEDULE_EXACT_ALARM = "android:schedule_exact_alarm"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -1633,6 +1647,7 @@ public class AppOpsManager { OP_LOADER_USAGE_STATS, OP_MANAGE_ONGOING_CALLS, OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, + OP_SCHEDULE_EXACT_ALARM, }; /** @@ -1751,6 +1766,7 @@ public class AppOpsManager { OP_MANAGE_CREDENTIALS, // MANAGE_CREDENTIALS OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER OP_RECORD_AUDIO_OUTPUT, // RECORD_AUDIO_OUTPUT + OP_SCHEDULE_EXACT_ALARM, // SCHEDULE_EXACT_ALARM }; /** @@ -1864,6 +1880,7 @@ public class AppOpsManager { OPSTR_MANAGE_CREDENTIALS, OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, OPSTR_RECORD_AUDIO_OUTPUT, + OPSTR_SCHEDULE_EXACT_ALARM, }; /** @@ -1978,6 +1995,7 @@ public class AppOpsManager { "MANAGE_CREDENTIALS", "USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER", "RECORD_AUDIO_OUTPUT", + "SCHEDULE_EXACT_ALARM", }; /** @@ -2093,6 +2111,7 @@ public class AppOpsManager { null, // no permission for OP_MANAGE_CREDENTIALS Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, null, // no permission for OP_RECORD_AUDIO_OUTPUT + Manifest.permission.SCHEDULE_EXACT_ALARM, }; /** @@ -2208,6 +2227,7 @@ public class AppOpsManager { null, // MANAGE_CREDENTIALS null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER null, // RECORD_AUDIO_OUTPUT + null, // SCHEDULE_EXACT_ALARM }; /** @@ -2322,6 +2342,7 @@ public class AppOpsManager { null, // MANAGE_CREDENTIALS null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER null, // RECORD_AUDIO_OUTPUT + null, // SCHEDULE_EXACT_ALARM }; /** @@ -2435,6 +2456,7 @@ public class AppOpsManager { AppOpsManager.MODE_DEFAULT, // MANAGE_CREDENTIALS AppOpsManager.MODE_DEFAULT, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_OUTPUT + AppOpsManager.MODE_DEFAULT, // SCHEDULE_EXACT_ALARM }; /** @@ -2552,6 +2574,7 @@ public class AppOpsManager { false, // MANAGE_CREDENTIALS true, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER false, // RECORD_AUDIO_OUTPUT + false, // SCHEDULE_EXACT_ALARM }; /** diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java index 7410a1ca04c8..dfc105a2811a 100644 --- a/core/java/android/app/ApplicationExitInfo.java +++ b/core/java/android/app/ApplicationExitInfo.java @@ -428,6 +428,13 @@ public final class ApplicationExitInfo implements Parcelable { */ private IAppTraceRetriever mAppTraceRetriever; + /** + * ParcelFileDescriptor pointing to a native tombstone. + * + * @see #getTraceInputStream + */ + private IParcelFileDescriptorRetriever mNativeTombstoneRetriever; + /** @hide */ @IntDef(prefix = { "REASON_" }, value = { REASON_UNKNOWN, @@ -603,22 +610,38 @@ public final class ApplicationExitInfo implements Parcelable { * prior to the death of the process; typically it'll be available when * the reason is {@link #REASON_ANR}, though if the process gets an ANR * but recovers, and dies for another reason later, this trace will be included - * in the record of {@link ApplicationExitInfo} still. + * in the record of {@link ApplicationExitInfo} still. Beginning with API 31, + * tombstone traces will be returned for + * {@link #REASON_CRASH_NATIVE}, with an InputStream containing a protobuf with + * <a href="https://android.googlesource.com/platform/system/core/+/refs/heads/master/debuggerd/proto/tombstone.proto">this schema</a>. + * Note thatbecause these traces are kept in a separate global circular buffer, crashes may be + * overwritten by newer crashes (including from other applications), so this may still return + * null. * * @return The input stream to the traces that was taken by the system * prior to the death of the process. */ public @Nullable InputStream getTraceInputStream() throws IOException { - if (mAppTraceRetriever == null) { + if (mAppTraceRetriever == null && mNativeTombstoneRetriever == null) { return null; } + try { - final ParcelFileDescriptor fd = mAppTraceRetriever.getTraceFileDescriptor( - mPackageName, mPackageUid, mPid); - if (fd == null) { - return null; + if (mNativeTombstoneRetriever != null) { + final ParcelFileDescriptor pfd = mNativeTombstoneRetriever.getPfd(); + if (pfd == null) { + return null; + } + + return new ParcelFileDescriptor.AutoCloseInputStream(pfd); + } else { + final ParcelFileDescriptor fd = mAppTraceRetriever.getTraceFileDescriptor( + mPackageName, mPackageUid, mPid); + if (fd == null) { + return null; + } + return new GZIPInputStream(new ParcelFileDescriptor.AutoCloseInputStream(fd)); } - return new GZIPInputStream(new ParcelFileDescriptor.AutoCloseInputStream(fd)); } catch (RemoteException e) { return null; } @@ -849,6 +872,15 @@ public final class ApplicationExitInfo implements Parcelable { mAppTraceRetriever = retriever; } + /** + * @see mNativeTombstoneRetriever + * + * @hide + */ + public void setNativeTombstoneRetriever(final IParcelFileDescriptorRetriever retriever) { + mNativeTombstoneRetriever = retriever; + } + @Override public int describeContents() { return 0; @@ -878,6 +910,12 @@ public final class ApplicationExitInfo implements Parcelable { } else { dest.writeInt(0); } + if (mNativeTombstoneRetriever != null) { + dest.writeInt(1); + dest.writeStrongBinder(mNativeTombstoneRetriever.asBinder()); + } else { + dest.writeInt(0); + } } /** @hide */ @@ -906,6 +944,7 @@ public final class ApplicationExitInfo implements Parcelable { mState = other.mState; mTraceFile = other.mTraceFile; mAppTraceRetriever = other.mAppTraceRetriever; + mNativeTombstoneRetriever = other.mNativeTombstoneRetriever; } private ApplicationExitInfo(@NonNull Parcel in) { @@ -928,6 +967,10 @@ public final class ApplicationExitInfo implements Parcelable { if (in.readInt() == 1) { mAppTraceRetriever = IAppTraceRetriever.Stub.asInterface(in.readStrongBinder()); } + if (in.readInt() == 1) { + mNativeTombstoneRetriever = IParcelFileDescriptorRetriever.Stub.asInterface( + in.readStrongBinder()); + } } public @NonNull static final Creator<ApplicationExitInfo> CREATOR = @@ -986,6 +1029,7 @@ public final class ApplicationExitInfo implements Parcelable { sb.append(" state=").append(ArrayUtils.isEmpty(mState) ? "empty" : Integer.toString(mState.length) + " bytes"); sb.append(" trace=").append(mTraceFile); + return sb.toString(); } diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 96751dba04f4..033cffe38069 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -52,6 +52,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private boolean mSharedElementTransitionStarted; private Activity mActivity; + private boolean mIsTaskRoot; private boolean mHasStopped; private boolean mIsCanceled; private ObjectAnimator mBackgroundAnimator; @@ -252,7 +253,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { cancel(); break; case MSG_ALLOW_RETURN_TRANSITION: - if (!mIsCanceled) { + if (!mIsCanceled && !mIsTaskRoot) { mPendingExitNames = mAllSharedElementNames; } break; @@ -343,6 +344,9 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { if (mActivity == null || decorView == null) { return; } + + mIsTaskRoot = mActivity.isTaskRoot(); + if (!isCrossTask()) { mActivity.overridePendingTransition(0, 0); } diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index f7097fab6b9e..cd84e5671753 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -551,7 +551,7 @@ public class ExitTransitionCoordinator extends ActivityTransitionCoordinator { @Override public boolean isReturnTransitionAllowed() { - return !mActivity.isTopOfTask(); + return true; } @Override diff --git a/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl b/core/java/android/app/IParcelFileDescriptorRetriever.aidl index 545622a007f3..7e808e74bd5d 100644 --- a/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl +++ b/core/java/android/app/IParcelFileDescriptorRetriever.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,19 +14,18 @@ * limitations under the License. */ -package android.speech.tts; -import android.speech.tts.ITextToSpeechSession; +package android.app; + +import android.os.ParcelFileDescriptor; /** - * Callback interface for a session created by {@link ITextToSpeechManager} API. + * An interface used to lazily provide a ParcelFileDescriptor to apps. * * @hide */ -oneway interface ITextToSpeechSessionCallback { - - void onConnected(in ITextToSpeechSession session, in IBinder serviceBinder); - - void onDisconnected(); - - void onError(in String errorInfo); -}
\ No newline at end of file +interface IParcelFileDescriptorRetriever { + /** + * Retrieve the ParcelFileDescriptor. + */ + ParcelFileDescriptor getPfd(); +} diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 28242b08ca65..59e5144113c9 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -9851,6 +9851,84 @@ public class DevicePolicyManager { } /** + * Sets whether 5g slicing is enabled on the work profile. + * + * Slicing allows operators to virtually divide their networks in portions and use different + * portions for specific use cases; for example, a corporation can have a deal/agreement with + * a carrier that all of its employees’ devices use data on a slice dedicated for enterprise + * use. + * + * By default, 5g slicing is enabled on the work profile on supported carriers and devices. + * Admins can explicitly disable it with this API. + * + * <p>This method can only be called by the profile owner of a managed profile. + * + * @param enabled whether 5g Slice should be enabled. + * @throws SecurityException if the caller is not the profile owner. + **/ + public void setNetworkSlicingEnabled(boolean enabled) { + throwIfParentInstance("setNetworkSlicingEnabled"); + if (mService != null) { + try { + mService.setNetworkSlicingEnabled(enabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Indicates whether 5g slicing is enabled. + * + * <p>This method can be called by the profile owner of a managed profile. + * + * @return whether 5g Slice is enabled. + * @throws SecurityException if the caller is not the profile owner. + */ + public boolean isNetworkSlicingEnabled() { + throwIfParentInstance("isNetworkSlicingEnabled"); + if (mService == null) { + return false; + } + try { + return mService.isNetworkSlicingEnabled(myUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Indicates whether 5g slicing is enabled for specific user. + * + * This method can be called with permission + * {@link android.Manifest.permission#READ_NETWORK_DEVICE_CONFIG} by the profile owner of + * a managed profile. And the caller must hold the + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission if query for + * other users. + * + * @param userHandle indicates the user to query the state + * @return indicates whether 5g Slice is enabled. + * @throws SecurityException if the caller is not granted the permission + * {@link android.Manifest.permission#READ_NETWORK_DEVICE_CONFIG} + * and not profile owner of a managed profile, and not granted the permission + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} if query for + * other users. + * @hide + */ + @SystemApi + public boolean isNetworkSlicingEnabledForUser(@NonNull UserHandle userHandle) { + throwIfParentInstance("isNetworkSlicingEnabledForUser"); + if (mService == null) { + return false; + } + try { + return mService.isNetworkSlicingEnabled(userHandle.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * This method is mostly deprecated. * Most of the settings that still have an effect have dedicated setter methods or user * restrictions. See individual settings for details. @@ -13357,6 +13435,7 @@ public class DevicePolicyManager { } } } + /** * Returns true if the caller is running on a device where the admin can grant * permissions related to device sensors. @@ -13459,4 +13538,22 @@ public class DevicePolicyManager { } return false; } + + /** + * Gets the list of {@link #isAffiliatedUser() affiliated} users running on foreground. + * + * @return list of {@link #isAffiliatedUser() affiliated} users running on foreground. + * + * @throws SecurityException if the calling application is not a device owner + */ + @NonNull + public List<UserHandle> listForegroundAffiliatedUsers() { + if (mService == null) return Collections.emptyList(); + + try { + return mService.listForegroundAffiliatedUsers(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 94388cfd41b9..8a87b16b760b 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -267,6 +267,9 @@ interface IDevicePolicyManager { void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled); boolean isSecondaryLockscreenEnabled(in UserHandle userHandle); + void setNetworkSlicingEnabled(in boolean enabled); + boolean isNetworkSlicingEnabled(int userHandle); + void setLockTaskPackages(in ComponentName who, in String[] packages); String[] getLockTaskPackages(in ComponentName who); boolean isLockTaskPermitted(in String pkg); @@ -507,4 +510,6 @@ interface IDevicePolicyManager { boolean isUsbDataSignalingEnabled(String callerPackage); boolean isUsbDataSignalingEnabledForUser(int userId); boolean canUsbDataSignalingBeDisabled(); + + List<UserHandle> listForegroundAffiliatedUsers(); } diff --git a/core/java/android/app/people/IConversationListener.aidl b/core/java/android/app/people/IConversationListener.aidl new file mode 100644 index 000000000000..7cbd66dd6617 --- /dev/null +++ b/core/java/android/app/people/IConversationListener.aidl @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.people; + +import android.app.people.ConversationChannel; +import android.content.pm.ParceledListSlice; +import android.os.UserHandle; + +import java.util.List; + +/** + * Interface for PeopleManager#ConversationListener. + * + * @hide + */ +oneway interface IConversationListener +{ + void onConversationUpdate(in ConversationChannel conversation); +}
\ No newline at end of file diff --git a/core/java/android/app/people/IPeopleManager.aidl b/core/java/android/app/people/IPeopleManager.aidl index d000f3b0b01d..496ca82bb61f 100644 --- a/core/java/android/app/people/IPeopleManager.aidl +++ b/core/java/android/app/people/IPeopleManager.aidl @@ -18,6 +18,7 @@ package android.app.people; import android.app.people.ConversationStatus; import android.app.people.ConversationChannel; +import android.app.people.IConversationListener; import android.content.pm.ParceledListSlice; import android.net.Uri; import android.os.IBinder; @@ -62,4 +63,6 @@ interface IPeopleManager { void clearStatus(in String packageName, int userId, in String conversationId, in String statusId); void clearStatuses(in String packageName, int userId, in String conversationId); ParceledListSlice getStatuses(in String packageName, int userId, in String conversationId); + void registerConversationListener(in String packageName, int userId, in String shortcutId, in IConversationListener callback); + void unregisterConversationListener(in IConversationListener callback); } diff --git a/core/java/android/app/people/PeopleManager.java b/core/java/android/app/people/PeopleManager.java index d348edb6dfbf..108437eb0649 100644 --- a/core/java/android/app/people/PeopleManager.java +++ b/core/java/android/app/people/PeopleManager.java @@ -16,6 +16,8 @@ package android.app.people; +import static java.util.Objects.requireNonNull; + import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; @@ -25,12 +27,18 @@ import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.os.RemoteException; import android.os.ServiceManager; +import android.util.Pair; +import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.Executor; /** * This class allows interaction with conversation and people data. @@ -40,11 +48,18 @@ public final class PeopleManager { private static final String LOG_TAG = PeopleManager.class.getSimpleName(); + /** + * @hide + */ + @VisibleForTesting + public Map<ConversationListener, Pair<Executor, IConversationListener>> + mConversationListeners = new HashMap<>(); + @NonNull - private final Context mContext; + private Context mContext; @NonNull - private final IPeopleManager mService; + private IPeopleManager mService; /** * @hide @@ -56,6 +71,15 @@ public final class PeopleManager { } /** + * @hide + */ + @VisibleForTesting + public PeopleManager(@NonNull Context context, IPeopleManager service) { + mContext = context; + mService = service; + } + + /** * Returns whether a shortcut has a conversation associated. * * <p>Requires android.permission.READ_PEOPLE_DATA permission. @@ -66,9 +90,8 @@ public final class PeopleManager { * clients. * * @param packageName name of the package the conversation is part of - * @param shortcutId the shortcut id backing the conversation + * @param shortcutId the shortcut id backing the conversation * @return whether the {@shortcutId} is backed by a Conversation. - * * @hide */ @SystemApi @@ -94,8 +117,7 @@ public final class PeopleManager { * * @param conversationId the {@link ShortcutInfo#getId() id} of the shortcut backing the * conversation that has an active status - * @param status the current status for the given conversation - * + * @param status the current status for the given conversation * @return whether the role is available in the system */ public void addOrUpdateStatus(@NonNull String conversationId, @@ -115,8 +137,8 @@ public final class PeopleManager { * * @param conversationId the {@link ShortcutInfo#getId() id} of the shortcut backing the * conversation that has an active status - * @param statusId the {@link ConversationStatus#getId() id} of a published status for the given - * conversation + * @param statusId the {@link ConversationStatus#getId() id} of a published status for the + * given conversation */ public void clearStatus(@NonNull String conversationId, @NonNull String statusId) { Preconditions.checkStringNotEmpty(conversationId); @@ -155,7 +177,7 @@ public final class PeopleManager { try { final ParceledListSlice<ConversationStatus> parceledList = mService.getStatuses( - mContext.getPackageName(), mContext.getUserId(), conversationId); + mContext.getPackageName(), mContext.getUserId(), conversationId); if (parceledList != null) { return parceledList.getList(); } @@ -164,4 +186,103 @@ public final class PeopleManager { } return new ArrayList<>(); } + + /** + * Listeners for conversation changes. + * + * @hide + */ + public interface ConversationListener { + /** + * Triggers when the conversation registered for a listener has been updated. + * + * @param conversation The conversation with modified data + * @see IPeopleManager#registerConversationListener(String, int, String, + * android.app.people.ConversationListener) + * + * <p>Only system root and SysUI have access to register the listener. + */ + default void onConversationUpdate(@NonNull ConversationChannel conversation) { + } + } + + /** + * Register a listener to watch for changes to the conversation identified by {@code + * packageName}, {@code userId}, and {@code shortcutId}. + * + * @param packageName The package name to match and filter the conversation to send updates for. + * @param userId The user ID to match and filter the conversation to send updates for. + * @param shortcutId The shortcut ID to match and filter the conversation to send updates for. + * @param listener The listener to register to receive conversation updates. + * @param executor {@link Executor} to handle the listeners. To dispatch listeners to the + * main thread of your application, you can use + * {@link android.content.Context#getMainExecutor()}. + * @hide + */ + public void registerConversationListener(String packageName, int userId, String shortcutId, + ConversationListener listener, Executor executor) { + requireNonNull(listener, "Listener cannot be null"); + requireNonNull(packageName, "Package name cannot be null"); + requireNonNull(shortcutId, "Shortcut ID cannot be null"); + synchronized (mConversationListeners) { + IConversationListener proxy = (IConversationListener) new ConversationListenerProxy( + executor, listener); + try { + mService.registerConversationListener( + packageName, userId, shortcutId, proxy); + mConversationListeners.put(listener, + new Pair<>(executor, proxy)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Unregisters the listener previously registered to watch conversation changes. + * + * @param listener The listener to register to receive conversation updates. + * @hide + */ + public void unregisterConversationListener( + ConversationListener listener) { + requireNonNull(listener, "Listener cannot be null"); + + synchronized (mConversationListeners) { + if (mConversationListeners.containsKey(listener)) { + IConversationListener proxy = mConversationListeners.remove(listener).second; + try { + mService.unregisterConversationListener(proxy); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + + /** + * Listener proxy class for {@link ConversationListener} + * + * @hide + */ + private static class ConversationListenerProxy extends + IConversationListener.Stub { + private final Executor mExecutor; + private final ConversationListener mListener; + + ConversationListenerProxy(Executor executor, ConversationListener listener) { + mExecutor = executor; + mListener = listener; + } + + @Override + public void onConversationUpdate(@NonNull ConversationChannel conversation) { + if (mListener == null || mExecutor == null) { + // Binder is dead. + Slog.e(LOG_TAG, "Binder is dead"); + return; + } + mExecutor.execute(() -> mListener.onConversationUpdate(conversation)); + } + } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 10b00f245d79..51669432e377 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4583,14 +4583,6 @@ public abstract class Context { public static final String AUTOFILL_MANAGER_SERVICE = "autofill"; /** - * Official published name of the (internal) text to speech manager service. - * - * @hide - * @see #getSystemService(String) - */ - public static final String TEXT_TO_SPEECH_MANAGER_SERVICE = "texttospeech"; - - /** * Official published name of the content capture service. * * @hide diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index f9980bc63d92..567501cba70b 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2065,6 +2065,13 @@ public class PackageInstaller { this.forceQueryableOverride = true; } + /** + * Sets the install scenario for this session, which describes the expected user journey. + */ + public void setInstallScenario(@InstallScenario int installScenario) { + this.installScenario = installScenario; + } + /** {@hide} */ public void dump(IndentingPrintWriter pw) { pw.printPair("mode", mode); @@ -2224,7 +2231,7 @@ public class PackageInstaller { /** {@hide} */ public @InstallReason int installReason; /** {@hide} */ - public @InstallReason int installScenario; + public @InstallScenario int installScenario; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public long sizeBytes; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index a3c3500f742f..fdb00c690ffe 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1356,15 +1356,11 @@ public abstract class PackageManager { /** * A value to indicate the lack of CUJ information, disabling all installation scenario logic. - * - * @hide */ public static final int INSTALL_SCENARIO_DEFAULT = 0; /** * Installation scenario providing the fastest “install button to launch" experience possible. - * - * @hide */ public static final int INSTALL_SCENARIO_FAST = 1; @@ -1381,8 +1377,6 @@ public abstract class PackageManager { * less optimized applications. The device state (e.g. memory usage or battery status) should * not be considered when making this decision as those factors are taken into account by the * Package Manager when acting on the installation scenario. - * - * @hide */ public static final int INSTALL_SCENARIO_BULK = 2; @@ -1393,8 +1387,6 @@ public abstract class PackageManager { * operation that are marked BULK_SECONDARY, the faster the entire bulk operation will be. * * See the comments for INSTALL_SCENARIO_BULK for more information. - * - * @hide */ public static final int INSTALL_SCENARIO_BULK_SECONDARY = 3; @@ -3677,6 +3669,15 @@ public abstract class PackageManager { public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY = "android.hardware.keystore.limited_use_key"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has + * a Keystore implementation that can create application-specific attestation keys. + * See {@link android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias}. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY = + "android.hardware.keystore.app_attest_key"; + /** @hide */ public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true; diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 5f5697a4ed0b..5b28e0035b09 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -237,7 +237,8 @@ public class BiometricManager { public BiometricTestSession createTestSession(int sensorId) { try { return new BiometricTestSession(mContext, sensorId, - mService.createTestSession(sensorId, mContext.getOpPackageName())); + (context, sensorId1, callback) -> mService + .createTestSession(sensorId1, callback, context.getOpPackageName())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -390,7 +391,6 @@ public class BiometricManager { * in Keystore land as SIDs, and are used during key generation. * @hide */ - @RequiresPermission(USE_BIOMETRIC_INTERNAL) public long[] getAuthenticatorIds() { if (mService != null) { try { diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java index 1c3560882f1b..ff1a17e07c11 100644 --- a/core/java/android/hardware/biometrics/BiometricTestSession.java +++ b/core/java/android/hardware/biometrics/BiometricTestSession.java @@ -19,6 +19,7 @@ package android.hardware.biometrics; import static android.Manifest.permission.TEST_BIOMETRIC; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.TestApi; import android.content.Context; @@ -27,6 +28,9 @@ import android.os.RemoteException; import android.util.ArraySet; import android.util.Log; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + /** * Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and * {@link android.hardware.fingerprint.FingerprintManager}. @@ -36,22 +40,58 @@ import android.util.Log; public class BiometricTestSession implements AutoCloseable { private static final String TAG = "BiometricTestSession"; + /** + * @hide + */ + public interface TestSessionProvider { + @NonNull + ITestSession createTestSession(@NonNull Context context, int sensorId, + @NonNull ITestSessionCallback callback) throws RemoteException; + } + private final Context mContext; private final int mSensorId; private final ITestSession mTestSession; // Keep track of users that were tested, which need to be cleaned up when finishing. - private final ArraySet<Integer> mTestedUsers; + @NonNull private final ArraySet<Integer> mTestedUsers; + + // Track the users currently cleaning up, and provide a latch that gets notified when all + // users have finished cleaning up. This is an imperfect system, as there can technically be + // multiple cleanups per user. Theoretically we should track the cleanup's BaseClientMonitor's + // unique ID, but it's complicated to plumb it through. This should be fine for now. + @Nullable private CountDownLatch mCloseLatch; + @NonNull private final ArraySet<Integer> mUsersCleaningUp; + + private final ITestSessionCallback mCallback = new ITestSessionCallback.Stub() { + @Override + public void onCleanupStarted(int userId) { + Log.d(TAG, "onCleanupStarted, sensor: " + mSensorId + ", userId: " + userId); + } + + @Override + public void onCleanupFinished(int userId) { + Log.d(TAG, "onCleanupFinished, sensor: " + mSensorId + + ", userId: " + userId + + ", remaining users: " + mUsersCleaningUp.size()); + mUsersCleaningUp.remove(userId); + + if (mUsersCleaningUp.isEmpty() && mCloseLatch != null) { + mCloseLatch.countDown(); + } + } + }; /** * @hide */ public BiometricTestSession(@NonNull Context context, int sensorId, - @NonNull ITestSession testSession) { + @NonNull TestSessionProvider testSessionProvider) throws RemoteException { mContext = context; mSensorId = sensorId; - mTestSession = testSession; + mTestSession = testSessionProvider.createTestSession(context, sensorId, mCallback); mTestedUsers = new ArraySet<>(); + mUsersCleaningUp = new ArraySet<>(); setTestHalEnabled(true); } @@ -176,6 +216,11 @@ public class BiometricTestSession implements AutoCloseable { @RequiresPermission(TEST_BIOMETRIC) public void cleanupInternalState(int userId) { try { + if (mUsersCleaningUp.contains(userId)) { + Log.w(TAG, "Cleanup already in progress for user: " + userId); + } + + mUsersCleaningUp.add(userId); mTestSession.cleanupInternalState(userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -185,12 +230,24 @@ public class BiometricTestSession implements AutoCloseable { @Override @RequiresPermission(TEST_BIOMETRIC) public void close() { - // Disable the test HAL first, so that enumerate is run on the real HAL, which should have - // no enrollments. Test-only framework enrollments will be deleted. - setTestHalEnabled(false); - - for (int user : mTestedUsers) { - cleanupInternalState(user); + // Cleanup can be performed using the test HAL, since it always responds to enumerate with + // zero enrollments. + if (!mTestedUsers.isEmpty()) { + mCloseLatch = new CountDownLatch(1); + for (int user : mTestedUsers) { + cleanupInternalState(user); + } + + try { + Log.d(TAG, "Awaiting latch..."); + mCloseLatch.await(10, TimeUnit.SECONDS); + Log.d(TAG, "Finished awaiting"); + } catch (InterruptedException e) { + Log.e(TAG, "Latch interrupted", e); + } } + + // Disable the test HAL after the sensor becomes idle. + setTestHalEnabled(false); } } diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl index 0dfd5dbf300e..d8c9dbc849a9 100644 --- a/core/java/android/hardware/biometrics/IAuthService.aidl +++ b/core/java/android/hardware/biometrics/IAuthService.aidl @@ -20,6 +20,7 @@ import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorPropertiesInternal; @@ -32,7 +33,7 @@ import android.hardware.biometrics.SensorPropertiesInternal; */ interface IAuthService { // Creates a test session with the specified sensorId - ITestSession createTestSession(int sensorId, String opPackageName); + ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName); // Retrieve static sensor properties for all biometric sensors List<SensorPropertiesInternal> getSensorProperties(String opPackageName); diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl index c854ac9847d8..7639c5dd4d16 100644 --- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl +++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl @@ -20,6 +20,7 @@ import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.face.IFaceServiceReceiver; import android.hardware.face.Face; @@ -32,7 +33,7 @@ import android.hardware.face.Face; interface IBiometricAuthenticator { // Creates a test session - ITestSession createTestSession(String opPackageName); + ITestSession createTestSession(ITestSessionCallback callback, String opPackageName); // Retrieve static sensor properties SensorPropertiesInternal getSensorProperties(String opPackageName); diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index a14a910a9e50..24331863a05f 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -21,6 +21,7 @@ import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorPropertiesInternal; @@ -30,7 +31,7 @@ import android.hardware.biometrics.SensorPropertiesInternal; */ interface IBiometricService { // Creates a test session with the specified sensorId - ITestSession createTestSession(int sensorId, String opPackageName); + ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName); // Retrieve static sensor properties for all biometric sensors List<SensorPropertiesInternal> getSensorProperties(String opPackageName); diff --git a/core/java/android/hardware/biometrics/ITestSession.aidl b/core/java/android/hardware/biometrics/ITestSession.aidl index fa7a62c53531..f8395a119c0b 100644 --- a/core/java/android/hardware/biometrics/ITestSession.aidl +++ b/core/java/android/hardware/biometrics/ITestSession.aidl @@ -18,7 +18,7 @@ package android.hardware.biometrics; import android.hardware.biometrics.SensorPropertiesInternal; /** - * A test service for FingerprintManager and BiometricPrompt. + * A test service for FingerprintManager and BiometricManager. * @hide */ interface ITestSession { diff --git a/core/java/android/speech/tts/ITextToSpeechManager.aidl b/core/java/android/hardware/biometrics/ITestSessionCallback.aidl index e6b63dff1553..3d9517f29548 100644 --- a/core/java/android/speech/tts/ITextToSpeechManager.aidl +++ b/core/java/android/hardware/biometrics/ITestSessionCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -13,17 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package android.speech.tts; - -import android.speech.tts.ITextToSpeechSessionCallback; +package android.hardware.biometrics; /** - * TextToSpeechManagerService interface. Allows opening {@link TextToSpeech} session with the - * specified provider proxied by the system service. - * + * ITestSession callback for FingerprintManager and BiometricManager. * @hide */ -oneway interface ITextToSpeechManager { - void createSession(in String engine, in ITextToSpeechSessionCallback managerCallback); +interface ITestSessionCallback { + void onCleanupStarted(int userId); + void onCleanupFinished(int userId); } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 0f595b700013..16ab900dee06 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -2908,6 +2908,29 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<int[]>("android.scaler.availableRotateAndCropModes", int[].class); /** + * <p>Default YUV/PRIVATE size to use for requesting secure image buffers.</p> + * <p>This entry lists the default size supported in the secure camera mode. This entry is + * optional on devices support the SECURE_IMAGE_DATA capability. This entry will be null + * if the camera device does not list SECURE_IMAGE_DATA capability.</p> + * <p>When the key is present, only a PRIVATE/YUV output of the specified size is guaranteed + * to be supported by the camera HAL in the secure camera mode. Any other format or + * resolutions might not be supported. Use + * {@link CameraDevice#isSessionConfigurationSupported } + * API to query if a secure session configuration is supported if the device supports this + * API.</p> + * <p>If this key returns null on a device with SECURE_IMAGE_DATA capability, the application + * can assume all output sizes listed in the + * {@link android.hardware.camera2.params.StreamConfigurationMap } + * are supported.</p> + * <p><b>Units</b>: Pixels</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + public static final Key<android.util.Size> SCALER_DEFAULT_SECURE_IMAGE_SIZE = + new Key<android.util.Size>("android.scaler.defaultSecureImageSize", android.util.Size.class); + + /** * <p>The area of the image sensor which corresponds to active pixels after any geometric * distortion correction has been applied.</p> * <p>This is the rectangle representing the size of the active region of the sensor (i.e. diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 886a8c1fdae5..a9bcdeff7e47 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -574,12 +574,23 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan mService.remove(mToken, face.getBiometricId(), userId, mServiceReceiver, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.w(TAG, "Remote exception in remove: ", e); - if (callback != null) { - callback.onRemovalError(face, FACE_ERROR_HW_UNAVAILABLE, - getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE, - 0 /* vendorCode */)); - } + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Removes all face templates for the given user. + * @hide + */ + @RequiresPermission(MANAGE_BIOMETRIC) + public void removeAll(int userId, @NonNull RemovalCallback callback) { + if (mService != null) { + try { + mRemovalCallback = callback; + mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } } diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index a3e7e2d2c5cb..6e7c701ef5ff 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -19,6 +19,7 @@ import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.face.IFaceServiceReceiver; import android.hardware.face.Face; import android.hardware.face.FaceSensorPropertiesInternal; @@ -32,7 +33,7 @@ import android.view.Surface; interface IFaceService { // Creates a test session with the specified sensorId - ITestSession createTestSession(int sensorId, String opPackageName); + ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName); // Requests a proto dump of the specified sensor byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer); @@ -83,10 +84,13 @@ interface IFaceService { // Cancel enrollment in progress void cancelEnrollment(IBinder token); - // Any errors resulting from this call will be returned to the listener + // Removes the specified face enrollment for the specified userId. void remove(IBinder token, int faceId, int userId, IFaceServiceReceiver receiver, String opPackageName); + // Removes all face enrollments for the specified userId. + void removeAll(IBinder token, int userId, IFaceServiceReceiver receiver, String opPackageName); + // Get the enrolled face for user. List<Face> getEnrolledFaces(int sensorId, int userId, String opPackageName); diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index a614ebfe1793..9d086cf203e2 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -154,7 +154,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing public BiometricTestSession createTestSession(int sensorId) { try { return new BiometricTestSession(mContext, sensorId, - mService.createTestSession(sensorId, mContext.getOpPackageName())); + (context, sensorId1, callback) -> mService + .createTestSession(sensorId1, callback, context.getOpPackageName())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -739,11 +740,22 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing mService.remove(mToken, fp.getBiometricId(), userId, mServiceReceiver, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.w(TAG, "Remote exception in remove: ", e); - if (callback != null) { - callback.onRemovalError(fp, FINGERPRINT_ERROR_HW_UNAVAILABLE, - getErrorString(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE, - 0 /* vendorCode */)); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes all face templates for the given user. + * @hide + */ + @RequiresPermission(MANAGE_FINGERPRINT) + public void removeAll(int userId, @NonNull RemovalCallback callback) { + if (mService != null) { + try { + mRemovalCallback = callback; + mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 8888247e2823..054c0d0f6513 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -19,6 +19,7 @@ import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.fingerprint.IFingerprintClientActiveCallback; import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.hardware.fingerprint.IUdfpsOverlayController; @@ -33,7 +34,7 @@ import java.util.List; interface IFingerprintService { // Creates a test session with the specified sensorId - ITestSession createTestSession(int sensorId, String opPackageName); + ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName); // Requests a proto dump of the specified sensor byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer); @@ -87,6 +88,9 @@ interface IFingerprintService { void remove(IBinder token, int fingerId, int userId, IFingerprintServiceReceiver receiver, String opPackageName); + // Removes all face enrollments for the specified userId. + void removeAll(IBinder token, int userId, IFingerprintServiceReceiver receiver, String opPackageName); + // Rename the fingerprint specified by fingerId and userId to the given name void rename(int fingerId, int userId, String name); diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index ebb3021bf083..a65f36b14f13 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -24,6 +24,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.app.ActivityThread; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -849,9 +850,18 @@ public final class ContextHubManager { attributionTag = context.getAttributionTag(); } + // Workaround for old APIs not providing a context + String packageName; + if (context != null) { + packageName = context.getPackageName(); + } else { + packageName = ActivityThread.currentPackageName(); + } + IContextHubClient clientProxy; try { - clientProxy = mService.createClient(hubInfo.getId(), clientInterface, attributionTag); + clientProxy = mService.createClient( + hubInfo.getId(), clientInterface, attributionTag, packageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl index 4961195a3017..92882c4f93bb 100644 --- a/core/java/android/hardware/location/IContextHubService.aidl +++ b/core/java/android/hardware/location/IContextHubService.aidl @@ -60,7 +60,8 @@ interface IContextHubService { // Creates a client to send and receive messages IContextHubClient createClient( - int contextHubId, in IContextHubClientCallback client, in String attributionTag); + int contextHubId, in IContextHubClientCallback client, in String attributionTag, + in String packageName); // Creates a PendingIntent-based client to send and receive messages IContextHubClient createPendingIntentClient( diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityMetricsEvent.aidl b/core/java/android/net/ConnectivityMetricsEvent.aidl index 1c541dc4c8cc..1c541dc4c8cc 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityMetricsEvent.aidl +++ b/core/java/android/net/ConnectivityMetricsEvent.aidl diff --git a/packages/Connectivity/framework/src/android/net/InterfaceConfiguration.aidl b/core/java/android/net/InterfaceConfiguration.aidl index 8aa5e3452853..8aa5e3452853 100644 --- a/packages/Connectivity/framework/src/android/net/InterfaceConfiguration.aidl +++ b/core/java/android/net/InterfaceConfiguration.aidl diff --git a/packages/Connectivity/framework/src/android/net/UidRange.aidl b/core/java/android/net/UidRange.aidl index f70fc8e2fefd..f70fc8e2fefd 100644 --- a/packages/Connectivity/framework/src/android/net/UidRange.aidl +++ b/core/java/android/net/UidRange.aidl diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl index 4f293eeb3c3b..6a3cb42ed75d 100644 --- a/core/java/android/net/vcn/IVcnManagementService.aidl +++ b/core/java/android/net/vcn/IVcnManagementService.aidl @@ -18,6 +18,7 @@ package android.net.vcn; import android.net.LinkProperties; import android.net.NetworkCapabilities; +import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; import android.net.vcn.VcnUnderlyingNetworkPolicy; @@ -33,4 +34,7 @@ interface IVcnManagementService { void addVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener); void removeVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener); VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy(in NetworkCapabilities nc, in LinkProperties lp); + + void registerVcnStatusCallback(in ParcelUuid subscriptionGroup, in IVcnStatusCallback callback, in String opPkgName); + void unregisterVcnStatusCallback(in IVcnStatusCallback callback); } diff --git a/core/java/android/net/vcn/IVcnStatusCallback.aidl b/core/java/android/net/vcn/IVcnStatusCallback.aidl new file mode 100644 index 000000000000..555e9b5883e8 --- /dev/null +++ b/core/java/android/net/vcn/IVcnStatusCallback.aidl @@ -0,0 +1,27 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.vcn; + +/** @hide */ +interface IVcnStatusCallback { + void onEnteredSafeMode(); + void onGatewayConnectionError( + in int[] gatewayNetworkCapabilities, + int errorCode, + in String exceptionClass, + in String exceptionMessage); +}
\ No newline at end of file diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java index 5eb4ba6a2f8e..52cc2182b094 100644 --- a/core/java/android/net/vcn/VcnConfig.java +++ b/core/java/android/net/vcn/VcnConfig.java @@ -20,6 +20,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.net.NetworkRequest; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; @@ -41,7 +42,6 @@ import java.util.Set; * brought up on demand based on active {@link NetworkRequest}(s). * * @see VcnManager for more information on the Virtual Carrier Network feature - * @hide */ public final class VcnConfig implements Parcelable { @NonNull private static final String TAG = VcnConfig.class.getSimpleName(); @@ -56,7 +56,8 @@ public final class VcnConfig implements Parcelable { @NonNull String packageName, @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) { mPackageName = packageName; - mGatewayConnectionConfigs = Collections.unmodifiableSet(gatewayConnectionConfigs); + mGatewayConnectionConfigs = + Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs)); validate(); } @@ -96,11 +97,7 @@ public final class VcnConfig implements Parcelable { return mPackageName; } - /** - * Retrieves the set of configured tunnels. - * - * @hide - */ + /** Retrieves the set of configured GatewayConnection(s). */ @NonNull public Set<VcnGatewayConnectionConfig> getGatewayConnectionConfigs() { return Collections.unmodifiableSet(mGatewayConnectionConfigs); @@ -168,11 +165,7 @@ public final class VcnConfig implements Parcelable { } }; - /** - * This class is used to incrementally build {@link VcnConfig} objects. - * - * @hide - */ + /** This class is used to incrementally build {@link VcnConfig} objects. */ public static final class Builder { @NonNull private final String mPackageName; @@ -190,7 +183,6 @@ public final class VcnConfig implements Parcelable { * * @param gatewayConnectionConfig the configuration for an individual gateway connection * @return this {@link Builder} instance, for chaining - * @hide */ @NonNull public Builder addGatewayConnectionConfig( @@ -205,7 +197,6 @@ public final class VcnConfig implements Parcelable { * Builds and validates the VcnConfig. * * @return an immutable VcnConfig instance - * @hide */ @NonNull public VcnConfig build() { diff --git a/core/java/android/net/vcn/VcnControlPlaneConfig.java b/core/java/android/net/vcn/VcnControlPlaneConfig.java new file mode 100644 index 000000000000..0c6ccfee5d5d --- /dev/null +++ b/core/java/android/net/vcn/VcnControlPlaneConfig.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net.vcn; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.PersistableBundle; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * This class represents a control plane configuration for a Virtual Carrier Network connection. + * + * <p>Each {@link VcnGatewayConnectionConfig} must have a {@link VcnControlPlaneConfig}, containing + * all connection, authentication and authorization parameters required to establish a Gateway + * Connection with a remote endpoint. + * + * <p>A {@link VcnControlPlaneConfig} object can be shared by multiple {@link + * VcnGatewayConnectionConfig}(s) if they will used for connecting with the same remote endpoint. + * + * @see VcnManager + * @see VcnGatewayConnectionConfig + * + * @hide + */ +public abstract class VcnControlPlaneConfig { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({CONFIG_TYPE_IKE}) + public @interface ConfigType {} + + /** @hide */ + public static final int CONFIG_TYPE_IKE = 1; + + private static final String CONFIG_TYPE_KEY = "mConfigType"; + @ConfigType private final int mConfigType; + + /** + * Package private constructor. + * + * @hide + */ + VcnControlPlaneConfig(int configType) { + mConfigType = configType; + } + + /** + * Constructs a VcnControlPlaneConfig object by deserializing a PersistableBundle. + * + * @param in the {@link PersistableBundle} containing an {@link VcnControlPlaneConfig} object + * @hide + */ + public static VcnControlPlaneConfig fromPersistableBundle(@NonNull PersistableBundle in) { + Objects.requireNonNull(in, "PersistableBundle was null"); + + int configType = in.getInt(CONFIG_TYPE_KEY); + switch (configType) { + case CONFIG_TYPE_IKE: + return new VcnControlPlaneIkeConfig(in); + default: + throw new IllegalStateException("Unrecognized configType: " + configType); + } + } + + /** + * Converts this VcnControlPlaneConfig to a PersistableBundle. + * + * @hide + */ + @NonNull + public PersistableBundle toPersistableBundle() { + final PersistableBundle result = new PersistableBundle(); + result.putInt(CONFIG_TYPE_KEY, mConfigType); + return result; + } + + /** @hide */ + @Override + public int hashCode() { + return Objects.hash(mConfigType); + } + + /** @hide */ + @Override + public boolean equals(Object o) { + if (!(o instanceof VcnControlPlaneConfig)) { + return false; + } + + return mConfigType == ((VcnControlPlaneConfig) o).mConfigType; + } + + /** + * Returns a deep copy of this object. + * + * @hide + */ + public abstract VcnControlPlaneConfig copy(); +} diff --git a/core/java/android/net/vcn/VcnControlPlaneIkeConfig.java b/core/java/android/net/vcn/VcnControlPlaneIkeConfig.java new file mode 100644 index 000000000000..2f6e1f63b960 --- /dev/null +++ b/core/java/android/net/vcn/VcnControlPlaneIkeConfig.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.vcn; + +import static android.net.vcn.VcnControlPlaneConfig.CONFIG_TYPE_IKE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.ipsec.ike.IkeSessionParams; +import android.net.ipsec.ike.TunnelModeChildSessionParams; +import android.os.PersistableBundle; +import android.util.ArraySet; + +import java.util.Objects; + +/** + * This class is an IKEv2 control plane configuration for a Virtual Carrier Network connection. + * + * <p>This class is an extension of the {@link VcnControlPlaneConfig}, containing IKEv2-specific + * configuration, authentication and authorization parameters. + * + * @see VcnControlPlaneConfig + * + * @hide + */ +public final class VcnControlPlaneIkeConfig extends VcnControlPlaneConfig { + private static final String TAG = VcnControlPlaneIkeConfig.class.getSimpleName(); + + // STOPSHIP: b/163604823 Make mIkeParams and mChildParams @NonNull when it is supported to + // construct mIkeParams and mChildParams from PersistableBundles. + + private static final String IKE_PARAMS_KEY = "mIkeParams"; + @Nullable private final IkeSessionParams mIkeParams; + + private static final String CHILD_PARAMS_KEY = "mChildParams"; + @Nullable private final TunnelModeChildSessionParams mChildParams; + + private static final ArraySet<String> BUNDLE_KEY_SET = new ArraySet<>(); + + { + BUNDLE_KEY_SET.add(IKE_PARAMS_KEY); + BUNDLE_KEY_SET.add(CHILD_PARAMS_KEY); + } + + /** + * Constructs a VcnControlPlaneIkeConfig object. + * + * @param ikeParams the IKE Session negotiation parameters + * @param childParams the tunnel mode Child Session negotiation parameters + */ + public VcnControlPlaneIkeConfig( + @NonNull IkeSessionParams ikeParams, + @NonNull TunnelModeChildSessionParams childParams) { + super(CONFIG_TYPE_IKE); + mIkeParams = ikeParams; + mChildParams = childParams; + validate(); + } + + /** + * Constructs a VcnControlPlaneIkeConfig object by deserializing a PersistableBundle. + * + * @param in the {@link PersistableBundle} containing an {@link VcnControlPlaneIkeConfig} object + * @hide + */ + public VcnControlPlaneIkeConfig(@NonNull PersistableBundle in) { + super(CONFIG_TYPE_IKE); + final PersistableBundle ikeParamsBundle = in.getPersistableBundle(IKE_PARAMS_KEY); + final PersistableBundle childParamsBundle = in.getPersistableBundle(CHILD_PARAMS_KEY); + + // STOPSHIP: b/163604823 Support constructing mIkeParams and mChildParams from + // PersistableBundles. + + mIkeParams = null; + mChildParams = null; + } + + private void validate() { + Objects.requireNonNull(mIkeParams, "mIkeParams was null"); + Objects.requireNonNull(mChildParams, "mChildParams was null"); + } + + /** + * Converts this VcnControlPlaneConfig to a PersistableBundle. + * + * @hide + */ + @Override + @NonNull + public PersistableBundle toPersistableBundle() { + final PersistableBundle result = super.toPersistableBundle(); + + // STOPSHIP: b/163604823 Support converting mIkeParams and mChildParams to + // PersistableBundles. + return result; + } + + /** Retrieves the IKE Session configuration. */ + @NonNull + public IkeSessionParams getIkeSessionParams() { + return mIkeParams; + } + + /** Retrieves the tunnel mode Child Session configuration. */ + @NonNull + public TunnelModeChildSessionParams getChildSessionParams() { + return mChildParams; + } + + /** @hide */ + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), mIkeParams, mChildParams); + } + + /** @hide */ + @Override + public boolean equals(Object o) { + if (!(o instanceof VcnControlPlaneIkeConfig)) { + return false; + } + + VcnControlPlaneIkeConfig other = (VcnControlPlaneIkeConfig) o; + + // STOPSHIP: b/163604823 Also check mIkeParams and mChildParams when it is supported to + // construct mIkeParams and mChildParams from PersistableBundles. They are not checked + // now so that VcnGatewayConnectionConfigTest and VcnConfigTest can pass. + return super.equals(o); + } + + /** @hide */ + @Override + public VcnControlPlaneConfig copy() { + return new VcnControlPlaneIkeConfig( + new IkeSessionParams.Builder(mIkeParams).build(), + new TunnelModeChildSessionParams.Builder(mChildParams).build()); + } +} diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index cead2f1caad1..94dff9159bd9 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -21,6 +21,8 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.net.Network; import android.net.NetworkCapabilities; import android.os.PersistableBundle; import android.util.ArraySet; @@ -55,28 +57,23 @@ import java.util.concurrent.TimeUnit; * subscription group under which this configuration is registered (see {@link * VcnManager#setVcnConfig}). * - * <p>Services that can be provided by a VCN network, or required for underlying networks are - * limited to services provided by cellular networks: + * <p>As an abstraction of a cellular network, services that can be provided by a VCN network, or + * required for underlying networks are limited to services provided by cellular networks: * * <ul> - * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_MMS} - * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_SUPL} - * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_DUN} - * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_FOTA} - * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_IMS} - * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_CBS} - * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_IA} - * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_RCS} - * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_XCAP} - * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_EIMS} - * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET} - * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_MCX} + * <li>{@link NetworkCapabilities#NET_CAPABILITY_MMS} + * <li>{@link NetworkCapabilities#NET_CAPABILITY_SUPL} + * <li>{@link NetworkCapabilities#NET_CAPABILITY_DUN} + * <li>{@link NetworkCapabilities#NET_CAPABILITY_FOTA} + * <li>{@link NetworkCapabilities#NET_CAPABILITY_IMS} + * <li>{@link NetworkCapabilities#NET_CAPABILITY_CBS} + * <li>{@link NetworkCapabilities#NET_CAPABILITY_IA} + * <li>{@link NetworkCapabilities#NET_CAPABILITY_RCS} + * <li>{@link NetworkCapabilities#NET_CAPABILITY_XCAP} + * <li>{@link NetworkCapabilities#NET_CAPABILITY_EIMS} + * <li>{@link NetworkCapabilities#NET_CAPABILITY_INTERNET} + * <li>{@link NetworkCapabilities#NET_CAPABILITY_MCX} * </ul> - * - * <p>The meteredness and roaming of the VCN {@link Network} will be determined by that of the - * underlying Network(s). - * - * @hide */ public final class VcnGatewayConnectionConfig { // TODO: Use MIN_MTU_V6 once it is public, @hide @@ -153,14 +150,15 @@ public final class VcnGatewayConnectionConfig { TimeUnit.MINUTES.toMillis(15) }; + private static final String CTRL_PLANE_CONFIG_KEY = "mCtrlPlaneConfig"; + @NonNull private VcnControlPlaneConfig mCtrlPlaneConfig; + private static final String EXPOSED_CAPABILITIES_KEY = "mExposedCapabilities"; @NonNull private final SortedSet<Integer> mExposedCapabilities; private static final String UNDERLYING_CAPABILITIES_KEY = "mUnderlyingCapabilities"; @NonNull private final SortedSet<Integer> mUnderlyingCapabilities; - // TODO: Add Ike/ChildSessionParams as a subclass - maybe VcnIkeGatewayConnectionConfig - private static final String MAX_MTU_KEY = "mMaxMtu"; private final int mMaxMtu; @@ -169,10 +167,12 @@ public final class VcnGatewayConnectionConfig { /** Builds a VcnGatewayConnectionConfig with the specified parameters. */ private VcnGatewayConnectionConfig( + @NonNull VcnControlPlaneConfig ctrlPlaneConfig, @NonNull Set<Integer> exposedCapabilities, @NonNull Set<Integer> underlyingCapabilities, @NonNull long[] retryIntervalsMs, @IntRange(from = MIN_MTU_V6) int maxMtu) { + mCtrlPlaneConfig = ctrlPlaneConfig; mExposedCapabilities = new TreeSet(exposedCapabilities); mUnderlyingCapabilities = new TreeSet(underlyingCapabilities); mRetryIntervalsMs = retryIntervalsMs; @@ -184,11 +184,16 @@ public final class VcnGatewayConnectionConfig { /** @hide */ @VisibleForTesting(visibility = Visibility.PRIVATE) public VcnGatewayConnectionConfig(@NonNull PersistableBundle in) { + final PersistableBundle ctrlPlaneConfigBundle = + in.getPersistableBundle(CTRL_PLANE_CONFIG_KEY); + Objects.requireNonNull(ctrlPlaneConfigBundle, "ctrlPlaneConfigBundle was null"); + final PersistableBundle exposedCapsBundle = in.getPersistableBundle(EXPOSED_CAPABILITIES_KEY); final PersistableBundle underlyingCapsBundle = in.getPersistableBundle(UNDERLYING_CAPABILITIES_KEY); + mCtrlPlaneConfig = VcnControlPlaneConfig.fromPersistableBundle(ctrlPlaneConfigBundle); mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList( exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER)); mUnderlyingCapabilities = new TreeSet<>(PersistableBundleUtils.toList( @@ -200,6 +205,8 @@ public final class VcnGatewayConnectionConfig { } private void validate() { + Objects.requireNonNull(mCtrlPlaneConfig, "control plane config was null"); + Preconditions.checkArgument( mExposedCapabilities != null && !mExposedCapabilities.isEmpty(), "exposedCapsBundle was null or empty"); @@ -243,14 +250,23 @@ public final class VcnGatewayConnectionConfig { } /** + * Returns control plane configuration. + * + * @hide + */ + @NonNull + public VcnControlPlaneConfig getControlPlaneConfig() { + return mCtrlPlaneConfig.copy(); + } + + /** * Returns all exposed capabilities. * * <p>The returned integer-value capabilities will not contain duplicates, and will be sorted in * ascending numerical order. * * @see Builder#addExposedCapability(int) - * @see Builder#clearExposedCapability(int) - * @hide + * @see Builder#removeExposedCapability(int) */ @NonNull public int[] getExposedCapabilities() { @@ -278,8 +294,7 @@ public final class VcnGatewayConnectionConfig { * <p>The returned integer-value capabilities will be sorted in ascending numerical order. * * @see Builder#addRequiredUnderlyingCapability(int) - * @see Builder#clearRequiredUnderlyingCapability(int) - * @hide + * @see Builder#removeRequiredUnderlyingCapability(int) */ @NonNull public int[] getRequiredUnderlyingCapabilities() { @@ -305,7 +320,6 @@ public final class VcnGatewayConnectionConfig { * Retrieves the configured retry intervals. * * @see Builder#setRetryInterval(long[]) - * @hide */ @NonNull public long[] getRetryInterval() { @@ -317,7 +331,7 @@ public final class VcnGatewayConnectionConfig { * * <p>Left to prevent the need to make major changes while changes are actively in flight. * - * @deprecated use getRequiredUnderlyingCapabilities() instead + * @deprecated use getRetryInterval() instead * @hide */ @Deprecated @@ -329,8 +343,7 @@ public final class VcnGatewayConnectionConfig { /** * Retrieves the maximum MTU allowed for this Gateway Connection. * - * @see Builder.setMaxMtu(int) - * @hide + * @see Builder#setMaxMtu(int) */ @IntRange(from = MIN_MTU_V6) public int getMaxMtu() { @@ -347,6 +360,7 @@ public final class VcnGatewayConnectionConfig { public PersistableBundle toPersistableBundle() { final PersistableBundle result = new PersistableBundle(); + final PersistableBundle ctrlPlaneConfigBundle = mCtrlPlaneConfig.toPersistableBundle(); final PersistableBundle exposedCapsBundle = PersistableBundleUtils.fromList( new ArrayList<>(mExposedCapabilities), @@ -356,6 +370,7 @@ public final class VcnGatewayConnectionConfig { new ArrayList<>(mUnderlyingCapabilities), PersistableBundleUtils.INTEGER_SERIALIZER); + result.putPersistableBundle(CTRL_PLANE_CONFIG_KEY, ctrlPlaneConfigBundle); result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle); result.putPersistableBundle(UNDERLYING_CAPABILITIES_KEY, underlyingCapsBundle); result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs); @@ -388,10 +403,9 @@ public final class VcnGatewayConnectionConfig { /** * This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects. - * - * @hide */ public static final class Builder { + @NonNull private final VcnControlPlaneConfig mCtrlPlaneConfig; @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet(); @NonNull private final Set<Integer> mUnderlyingCapabilities = new ArraySet(); @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS; @@ -402,6 +416,26 @@ public final class VcnGatewayConnectionConfig { // when on Cell. /** + * Construct a Builder object. + * + * @param ctrlPlaneConfig the control plane configuration + * @see VcnControlPlaneConfig + * @hide + */ + public Builder(@NonNull VcnControlPlaneConfig ctrlPlaneConfig) { + Objects.requireNonNull(ctrlPlaneConfig, "ctrlPlaneConfig was null"); + + mCtrlPlaneConfig = ctrlPlaneConfig; + } + + /** Construct a Builder object. */ + // TODO: Remove this constructor when #Builder(ctrlPlaneConfig) is exposed as public API. + // This constructor is created to avoid changing API shape in this CL + public Builder() { + mCtrlPlaneConfig = null; + } + + /** * Add a capability that this VCN Gateway Connection will support. * * @param exposedCapability the app-facing capability to be exposed by this VCN Gateway @@ -409,7 +443,6 @@ public final class VcnGatewayConnectionConfig { * @return this {@link Builder} instance, for chaining * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway * Connection - * @hide */ @NonNull public Builder addExposedCapability(@VcnSupportedCapability int exposedCapability) { @@ -427,10 +460,10 @@ public final class VcnGatewayConnectionConfig { * @return this {@link Builder} instance, for chaining * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway * Connection - * @hide */ @NonNull - public Builder clearExposedCapability(@VcnSupportedCapability int exposedCapability) { + @SuppressLint("BuilderSetStyle") // For consistency with NetCaps.Builder add/removeCap + public Builder removeExposedCapability(@VcnSupportedCapability int exposedCapability) { checkValidCapability(exposedCapability); mExposedCapabilities.remove(exposedCapability); @@ -445,7 +478,6 @@ public final class VcnGatewayConnectionConfig { * @return this {@link Builder} instance, for chaining * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying * networks - * @hide */ @NonNull public Builder addRequiredUnderlyingCapability( @@ -468,10 +500,10 @@ public final class VcnGatewayConnectionConfig { * @return this {@link Builder} instance, for chaining * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying * networks - * @hide */ @NonNull - public Builder clearRequiredUnderlyingCapability( + @SuppressLint("BuilderSetStyle") // For consistency with NetCaps.Builder add/removeCap + public Builder removeRequiredUnderlyingCapability( @VcnSupportedCapability int underlyingCapability) { checkValidCapability(underlyingCapability); @@ -501,7 +533,6 @@ public final class VcnGatewayConnectionConfig { * 15m]} * @return this {@link Builder} instance, for chaining * @see VcnManager for additional discussion on fail-safe mode - * @hide */ @NonNull public Builder setRetryInterval(@NonNull long[] retryIntervalsMs) { @@ -523,7 +554,6 @@ public final class VcnGatewayConnectionConfig { * @param maxMtu the maximum MTU allowed for this Gateway Connection. Must be greater than * the IPv6 minimum MTU of 1280. Defaults to 1500. * @return this {@link Builder} instance, for chaining - * @hide */ @NonNull public Builder setMaxMtu(@IntRange(from = MIN_MTU_V6) int maxMtu) { @@ -538,12 +568,15 @@ public final class VcnGatewayConnectionConfig { * Builds and validates the VcnGatewayConnectionConfig. * * @return an immutable VcnGatewayConnectionConfig instance - * @hide */ @NonNull public VcnGatewayConnectionConfig build() { return new VcnGatewayConnectionConfig( - mExposedCapabilities, mUnderlyingCapabilities, mRetryIntervalsMs, mMaxMtu); + mCtrlPlaneConfig, + mExposedCapabilities, + mUnderlyingCapabilities, + mRetryIntervalsMs, + mMaxMtu); } } } diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index 1a38338c26aa..aea0ea988f50 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -17,12 +17,15 @@ package android.net.vcn; import static java.util.Objects.requireNonNull; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.content.Context; import android.net.LinkProperties; import android.net.NetworkCapabilities; +import android.os.Binder; import android.os.ParcelUuid; import android.os.RemoteException; import android.os.ServiceSpecificException; @@ -31,6 +34,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -39,12 +44,12 @@ import java.util.concurrent.Executor; /** * VcnManager publishes APIs for applications to configure and manage Virtual Carrier Networks. * - * <p>A VCN creates a virtualization layer to allow MVNOs to aggregate heterogeneous physical + * <p>A VCN creates a virtualization layer to allow carriers to aggregate heterogeneous physical * networks, unifying them as a single carrier network. This enables infrastructure flexibility on - * the part of MVNOs without impacting user connectivity, abstracting the physical network + * the part of carriers without impacting user connectivity, abstracting the physical network * technologies as an implementation detail of their public network. * - * <p>Each VCN virtualizes an Carrier's network by building tunnels to a carrier's core network over + * <p>Each VCN virtualizes a carrier's network by building tunnels to a carrier's core network over * carrier-managed physical links and supports a IP mobility layer to ensure seamless transitions * between the underlying networks. Each VCN is configured based on a Subscription Group (see {@link * android.telephony.SubscriptionManager}) and aggregates all networks that are brought up based on @@ -62,8 +67,6 @@ import java.util.concurrent.Executor; * tasks. In Safe Mode, the system will allow underlying cellular networks to be used as default. * Additionally, during Safe Mode, the VCN will continue to retry the connections, and will * automatically exit Safe Mode if all active tunnels connect successfully. - * - * @hide */ @SystemService(Context.VCN_MANAGEMENT_SERVICE) public class VcnManager { @@ -101,7 +104,6 @@ public class VcnManager { return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS); } - // TODO: Make setVcnConfig(), clearVcnConfig() Public API /** * Sets the VCN configuration for a given subscription group. * @@ -113,11 +115,10 @@ public class VcnManager { * * @param subscriptionGroup the subscription group that the configuration should be applied to * @param config the configuration parameters for the VCN - * @throws SecurityException if the caller does not have carrier privileges, or is not running - * as the primary user - * @throws IOException if the configuration failed to be persisted. A caller encountering this - * exception should attempt to retry (possibly after a delay). - * @hide + * @throws SecurityException if the caller does not have carrier privileges for the provided + * subscriptionGroup, or is not running as the primary user + * @throws IOException if the configuration failed to be saved and persisted to disk. This may + * occur due to temporary disk errors, or more permanent conditions such as a full disk. */ @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) @@ -134,7 +135,6 @@ public class VcnManager { } } - // TODO: Make setVcnConfig(), clearVcnConfig() Public API /** * Clears the VCN configuration for a given subscription group. * @@ -145,9 +145,8 @@ public class VcnManager { * @param subscriptionGroup the subscription group that the configuration should be applied to * @throws SecurityException if the caller does not have carrier privileges, or is not running * as the primary user - * @throws IOException if the configuration failed to be cleared. A caller encountering this - * exception should attempt to retry (possibly after a delay). - * @hide + * @throws IOException if the configuration failed to be cleared from disk. This may occur due + * to temporary disk errors, or more permanent conditions such as a full disk. */ @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) throws IOException { @@ -267,6 +266,154 @@ public class VcnManager { } } + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + VCN_ERROR_CODE_INTERNAL_ERROR, + VCN_ERROR_CODE_CONFIG_ERROR, + VCN_ERROR_CODE_NETWORK_ERROR + }) + public @interface VcnErrorCode {} + + /** + * Value indicating that an internal failure occurred in this Gateway Connection. + * + * @hide + */ + public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; + + /** + * Value indicating that an error with this Gateway Connection's configuration occurred. + * + * <p>For example, this error code will be returned after authentication failures. + * + * @hide + */ + public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; + + /** + * Value indicating that a Network error occurred with this Gateway Connection. + * + * <p>For example, this error code will be returned if an underlying {@link android.net.Network} + * for this Gateway Connection is lost, or if an error occurs while resolving the connection + * endpoint address. + * + * @hide + */ + public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; + + // TODO: make VcnStatusCallback @SystemApi + /** + * VcnStatusCallback is the interface for Carrier apps to receive updates for their VCNs. + * + * <p>VcnStatusCallbacks may be registered before {@link VcnConfig}s are provided for a + * subscription group. + * + * @hide + */ + public abstract static class VcnStatusCallback { + private VcnStatusCallbackBinder mCbBinder; + + /** + * Invoked when the VCN for this Callback's subscription group enters safe mode. + * + * <p>A VCN will be put into safe mode if any of the gateway connections were unable to + * establish a connection within a system-determined timeout (while underlying networks were + * available). + * + * <p>A VCN-configuring app may opt to exit safe mode by (re)setting the VCN configuration + * via {@link #setVcnConfig(ParcelUuid, VcnConfig)}. + */ + public abstract void onEnteredSafeMode(); + + /** + * Invoked when a VCN Gateway Connection corresponding to this callback's subscription + * encounters an error. + * + * @param networkCapabilities an array of underlying NetworkCapabilities for the Gateway + * Connection that encountered the error for identification purposes. These will be a + * sorted list with no duplicates, matching one of the {@link + * VcnGatewayConnectionConfig}s set in the {@link VcnConfig} for this subscription + * group. + * @param errorCode {@link VcnErrorCode} to indicate the error that occurred + * @param detail Throwable to provide additional information about the error, or {@code + * null} if none + */ + public abstract void onGatewayConnectionError( + @NonNull int[] networkCapabilities, + @VcnErrorCode int errorCode, + @Nullable Throwable detail); + } + + /** + * Registers the given callback to receive status updates for the specified subscription. + * + * <p>Callbacks can be registered for a subscription before {@link VcnConfig}s are set for it. + * + * <p>A {@link VcnStatusCallback} may only be registered for one subscription at a time. {@link + * VcnStatusCallback}s may be reused once unregistered. + * + * <p>A {@link VcnStatusCallback} will only be invoked if the registering package has carrier + * privileges for the specified subscription at the time of invocation. + * + * @param subscriptionGroup The subscription group to match for callbacks + * @param executor The {@link Executor} to be used for invoking callbacks + * @param callback The VcnStatusCallback to be registered + * @throws IllegalStateException if callback is currently registered with VcnManager + * @hide + */ + public void registerVcnStatusCallback( + @NonNull ParcelUuid subscriptionGroup, + @NonNull Executor executor, + @NonNull VcnStatusCallback callback) { + requireNonNull(subscriptionGroup, "subscriptionGroup must not be null"); + requireNonNull(executor, "executor must not be null"); + requireNonNull(callback, "callback must not be null"); + + synchronized (callback) { + if (callback.mCbBinder != null) { + throw new IllegalStateException("callback is already registered with VcnManager"); + } + callback.mCbBinder = new VcnStatusCallbackBinder(executor, callback); + + try { + mService.registerVcnStatusCallback( + subscriptionGroup, callback.mCbBinder, mContext.getOpPackageName()); + } catch (RemoteException e) { + callback.mCbBinder = null; + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Unregisters the given callback. + * + * <p>Once unregistered, the callback will stop receiving status updates for the subscription it + * was registered with. + * + * @param callback The callback to be unregistered + * @hide + */ + public void unregisterVcnStatusCallback(@NonNull VcnStatusCallback callback) { + requireNonNull(callback, "callback must not be null"); + + synchronized (callback) { + if (callback.mCbBinder == null) { + // no Binder attached to this callback, so it's not currently registered + return; + } + + try { + mService.unregisterVcnStatusCallback(callback.mCbBinder); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } finally { + callback.mCbBinder = null; + } + } + } + /** * Binder wrapper for added VcnUnderlyingNetworkPolicyListeners to receive signals from System * Server. @@ -286,7 +433,62 @@ public class VcnManager { @Override public void onPolicyChanged() { - mExecutor.execute(() -> mListener.onPolicyChanged()); + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> mListener.onPolicyChanged())); + } + } + + /** + * Binder wrapper for VcnStatusCallbacks to receive signals from VcnManagementService. + * + * @hide + */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class VcnStatusCallbackBinder extends IVcnStatusCallback.Stub { + @NonNull private final Executor mExecutor; + @NonNull private final VcnStatusCallback mCallback; + + public VcnStatusCallbackBinder( + @NonNull Executor executor, @NonNull VcnStatusCallback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onEnteredSafeMode() { + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> mCallback.onEnteredSafeMode())); + } + + // TODO(b/180521637): use ServiceSpecificException for safer Exception 'parceling' + @Override + public void onGatewayConnectionError( + @NonNull int[] networkCapabilities, + @VcnErrorCode int errorCode, + @Nullable String exceptionClass, + @Nullable String exceptionMessage) { + final Throwable cause = createThrowableByClassName(exceptionClass, exceptionMessage); + + Binder.withCleanCallingIdentity( + () -> + mExecutor.execute( + () -> + mCallback.onGatewayConnectionError( + networkCapabilities, errorCode, cause))); + } + + private static Throwable createThrowableByClassName( + @Nullable String className, @Nullable String message) { + if (className == null) { + return null; + } + + try { + Class<?> c = Class.forName(className); + return (Throwable) c.getConstructor(String.class).newInstance(message); + } catch (ReflectiveOperationException | ClassCastException e) { + return new RuntimeException(className + ": " + message); + } } } } diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java b/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java new file mode 100644 index 000000000000..a97563724e50 --- /dev/null +++ b/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.vcn; + +import android.annotation.NonNull; +import android.net.NetworkSpecifier; +import android.net.TelephonyNetworkSpecifier; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.internal.util.ArrayUtils; + +import java.util.Arrays; +import java.util.Objects; + +/** + * NetworkSpecifier object for VCN underlying network requests. + * + * <p>This matches any underlying network with the appropriate subIds. + * + * @hide + */ +public final class VcnUnderlyingNetworkSpecifier extends NetworkSpecifier implements Parcelable { + @NonNull private final int[] mSubIds; + + /** + * Builds a new VcnUnderlyingNetworkSpecifier with the given list of subIds + * + * @hide + */ + public VcnUnderlyingNetworkSpecifier(@NonNull int[] subIds) { + mSubIds = Objects.requireNonNull(subIds, "subIds were null"); + } + + /** + * Retrieves the list of subIds supported by this VcnUnderlyingNetworkSpecifier + * + * @hide + */ + @NonNull + @VisibleForTesting(visibility = Visibility.PRIVATE) + public int[] getSubIds() { + return mSubIds; + } + + public static final @NonNull Creator<VcnUnderlyingNetworkSpecifier> CREATOR = + new Creator<VcnUnderlyingNetworkSpecifier>() { + @Override + public VcnUnderlyingNetworkSpecifier createFromParcel(Parcel in) { + int[] subIds = in.createIntArray(); + return new VcnUnderlyingNetworkSpecifier(subIds); + } + + @Override + public VcnUnderlyingNetworkSpecifier[] newArray(int size) { + return new VcnUnderlyingNetworkSpecifier[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeIntArray(mSubIds); + } + + @Override + public int hashCode() { + return Arrays.hashCode(mSubIds); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof VcnUnderlyingNetworkSpecifier)) { + return false; + } + + VcnUnderlyingNetworkSpecifier lhs = (VcnUnderlyingNetworkSpecifier) obj; + return Arrays.equals(mSubIds, lhs.mSubIds); + } + + @Override + public String toString() { + return new StringBuilder() + .append("VcnUnderlyingNetworkSpecifier [") + .append("mSubIds = ").append(Arrays.toString(mSubIds)) + .append("]") + .toString(); + } + + /** @hide */ + @Override + public boolean canBeSatisfiedBy(NetworkSpecifier other) { + if (other instanceof TelephonyNetworkSpecifier) { + return ArrayUtils.contains( + mSubIds, ((TelephonyNetworkSpecifier) other).getSubscriptionId()); + } + // TODO(b/180140053): Allow matching against WifiNetworkAgentSpecifier + + // MatchAllNetworkSpecifier matched in NetworkCapabilities. + return equals(other); + } +} diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index b39c182fcab6..7437e037fa3e 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -113,6 +113,7 @@ interface IUserManager { boolean hasBadge(int userId); boolean isUserUnlocked(int userId); boolean isUserRunning(int userId); + boolean isUserForeground(); boolean isUserNameSet(int userId); boolean hasRestrictedProfiles(); boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target, int flags); diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java index a828077ac055..bb40d905d481 100644 --- a/core/java/android/os/UidBatteryConsumer.java +++ b/core/java/android/os/UidBatteryConsumer.java @@ -29,21 +29,11 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela private final int mUid; @Nullable private final String mPackageWithHighestDrain; - private boolean mSystemComponent; public int getUid() { return mUid; } - /** - * Returns true if this battery consumer is considered to be a part of the operating - * system itself. For example, the UidBatteryConsumer with the UID {@link Process#BLUETOOTH_UID} - * is a system component. - */ - public boolean isSystemComponent() { - return mSystemComponent; - } - @Nullable public String getPackageWithHighestDrain() { return mPackageWithHighestDrain; @@ -52,7 +42,6 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela private UidBatteryConsumer(@NonNull Builder builder) { super(builder.mPowerComponentsBuilder.build()); mUid = builder.mUid; - mSystemComponent = builder.mSystemComponent; mPackageWithHighestDrain = builder.mPackageWithHighestDrain; } @@ -95,7 +84,6 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela private final BatteryStats.Uid mBatteryStatsUid; private final int mUid; private String mPackageWithHighestDrain; - private boolean mSystemComponent; private boolean mExcludeFromBatteryUsageStats; public Builder(int customPowerComponentCount, int customTimeComponentCount, diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index ea1ce3728365..8bdfd3d3d627 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2300,6 +2300,19 @@ public class UserManager { } /** + * Checks if the calling user is running on foreground. + * + * @return whether the calling user is running on foreground. + */ + public boolean isUserForeground() { + try { + return mService.isUserForeground(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Return whether the calling user is running in an "unlocked" state. * <p> * On devices with direct boot, a user is unlocked only after they've diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java index f2fe71913bb1..a078e0434867 100644 --- a/core/java/android/os/incremental/IncrementalFileStorages.java +++ b/core/java/android/os/incremental/IncrementalFileStorages.java @@ -36,6 +36,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.pm.DataLoaderParams; import android.content.pm.IDataLoaderStatusListener; +import android.content.pm.IPackageLoadingProgressCallback; import android.content.pm.InstallationFileParcel; import java.io.File; @@ -71,7 +72,8 @@ public final class IncrementalFileStorages { @Nullable StorageHealthCheckParams healthCheckParams, @Nullable IStorageHealthListener healthListener, @NonNull List<InstallationFileParcel> addedFiles, - @NonNull PerUidReadTimeouts[] perUidReadTimeouts) throws IOException { + @NonNull PerUidReadTimeouts[] perUidReadTimeouts, + IPackageLoadingProgressCallback progressCallback) throws IOException { // TODO(b/136132412): validity check if session should not be incremental IncrementalManager incrementalManager = (IncrementalManager) context.getSystemService( Context.INCREMENTAL_SERVICE); @@ -95,6 +97,11 @@ public final class IncrementalFileStorages { throw new IOException("Unknown file location: " + file.location); } } + // Register progress loading callback after files have been added + if (progressCallback != null) { + incrementalManager.registerLoadingProgressCallback(stageDir.getAbsolutePath(), + progressCallback); + } result.startLoading(dataLoaderParams, statusListener, healthCheckParams, healthListener, perUidReadTimeouts); @@ -205,6 +212,7 @@ public final class IncrementalFileStorages { try { mDefaultStorage.unBind(mStageDir.getAbsolutePath()); + mDefaultStorage.unregisterLoadingProgressListener(); } catch (IOException ignored) { } mDefaultStorage = null; diff --git a/core/java/android/permission/AdminPermissionControlParams.aidl b/core/java/android/permission/AdminPermissionControlParams.aidl new file mode 100644 index 000000000000..35e63d4851fc --- /dev/null +++ b/core/java/android/permission/AdminPermissionControlParams.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.permission; + +parcelable AdminPermissionControlParams; diff --git a/core/java/android/permission/AdminPermissionControlParams.java b/core/java/android/permission/AdminPermissionControlParams.java new file mode 100644 index 000000000000..49507220e7b0 --- /dev/null +++ b/core/java/android/permission/AdminPermissionControlParams.java @@ -0,0 +1,132 @@ +/* + * 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.permission; + +import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT; +import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED; +import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED; + +import static com.android.internal.util.Preconditions.checkArgument; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.app.admin.DevicePolicyManager; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * A data object representing an admin's request to control a certain permission + * for a certain app. + * This class is processed by the Permission Controller's + * setRuntimePermissionGrantStateByDeviceAdmin method. + * + * @hide + */ +@SystemApi +public final class AdminPermissionControlParams implements Parcelable { + // The package to grant/deny the permission to. + private final @NonNull String mGranteePackageName; + // The permission to grant/deny. + private final @NonNull String mPermission; + // The grant state (granted/denied/default). + private final @DevicePolicyManager.PermissionGrantState int mGrantState; + // Whether the admin can grant sensors-related permissions. + private final boolean mCanAdminGrantSensorsPermissions; + + /** + * @hide + * A new instance is only created by the framework, so the constructor need not be visible + * as system API. + */ + public AdminPermissionControlParams(@NonNull String granteePackageName, + @NonNull String permission, + int grantState, boolean canAdminGrantSensorsPermissions) { + Preconditions.checkStringNotEmpty(granteePackageName, "Package name must not be empty."); + Preconditions.checkStringNotEmpty(permission, "Permission must not be empty."); + checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED + || grantState == PERMISSION_GRANT_STATE_DENIED + || grantState == PERMISSION_GRANT_STATE_DEFAULT); + + mGranteePackageName = granteePackageName; + mPermission = permission; + mGrantState = grantState; + mCanAdminGrantSensorsPermissions = canAdminGrantSensorsPermissions; + } + + public static final @NonNull Creator<AdminPermissionControlParams> CREATOR = + new Creator<AdminPermissionControlParams>() { + @Override + public AdminPermissionControlParams createFromParcel(Parcel in) { + String granteePackageName = in.readString(); + String permission = in.readString(); + int grantState = in.readInt(); + boolean mayAdminGrantSensorPermissions = in.readBoolean(); + + return new AdminPermissionControlParams(granteePackageName, permission, + grantState, mayAdminGrantSensorPermissions); + } + + @Override + public AdminPermissionControlParams[] newArray(int size) { + return new AdminPermissionControlParams[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mGranteePackageName); + dest.writeString(mPermission); + dest.writeInt(mGrantState); + dest.writeBoolean(mCanAdminGrantSensorsPermissions); + } + + /** Returns the name of the package the permission applies to */ + public @NonNull String getGranteePackageName() { + return mGranteePackageName; + } + + /** Returns the permission name */ + public @NonNull String getPermission() { + return mPermission; + } + + /** Returns the grant state */ + public int getGrantState() { + return mGrantState; + } + + /** + * return true if the admin may control grants of permissions related to sensors. + */ + public boolean canAdminGrantSensorsPermissions() { + return mCanAdminGrantSensorsPermissions; + } + + @Override + public String toString() { + return String.format( + "Grantee %s Permission %s state: %d admin grant of sensors permissions: %b", + mGranteePackageName, mPermission, mGrantState, mCanAdminGrantSensorsPermissions); + } +} diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index 084cc2ff4aa4..6d677f35b563 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -20,6 +20,7 @@ import android.os.RemoteCallback; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.UserHandle; +import android.permission.AdminPermissionControlParams; import com.android.internal.infra.AndroidFuture; /** @@ -39,8 +40,8 @@ oneway interface IPermissionController { void countPermissionApps(in List<String> permissionNames, int flags, in AndroidFuture callback); void getPermissionUsages(boolean countSystem, long numMillis, in AndroidFuture callback); - void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName, String packageName, - String permission, int grantState, in AndroidFuture callback); + void setRuntimePermissionGrantStateByDeviceAdminFromParams(String callerPackageName, + in AdminPermissionControlParams params, in AndroidFuture callback); void grantOrUpgradeDefaultRuntimePermissions(in AndroidFuture callback); void notifyOneTimePermissionSessionTimeout(String packageName); void updateUserSensitiveForApp(int uid, in AndroidFuture callback); diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index f306805ac3a4..084b18eb2999 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -16,13 +16,9 @@ package android.permission; -import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT; -import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED; -import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED; import static android.permission.PermissionControllerService.SERVICE_INTERFACE; import static com.android.internal.util.FunctionalUtils.uncheckExceptions; -import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkArgumentNonnegative; import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull; import static com.android.internal.util.Preconditions.checkFlagsArgument; @@ -39,7 +35,6 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.ActivityThread; -import android.app.admin.DevicePolicyManager.PermissionGrantState; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -70,6 +65,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -323,11 +319,11 @@ public final class PermissionControllerManager { /** * Set the runtime permission state from a device admin. + * This variant takes into account whether the admin may or may not grant sensors-related + * permissions. * * @param callerPackageName The package name of the admin requesting the change - * @param packageName Package the permission belongs to - * @param permission Permission to change - * @param grantState State to set the permission into + * @param params Information about the permission being granted. * @param executor Executor to run the {@code callback} on * @param callback The callback * @@ -338,30 +334,27 @@ public final class PermissionControllerManager { Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY}, conditional = true) public void setRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName, - @NonNull String packageName, @NonNull String permission, - @PermissionGrantState int grantState, @NonNull @CallbackExecutor Executor executor, + @NonNull AdminPermissionControlParams params, + @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { checkStringNotEmpty(callerPackageName); - checkStringNotEmpty(packageName); - checkStringNotEmpty(permission); - checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED - || grantState == PERMISSION_GRANT_STATE_DENIED - || grantState == PERMISSION_GRANT_STATE_DEFAULT); - checkNotNull(executor); - checkNotNull(callback); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + Objects.requireNonNull(params, "Admin control params must not be null."); mRemoteService.postAsync(service -> { AndroidFuture<Boolean> setRuntimePermissionGrantStateResult = new AndroidFuture<>(); - service.setRuntimePermissionGrantStateByDeviceAdmin( - callerPackageName, packageName, permission, grantState, + service.setRuntimePermissionGrantStateByDeviceAdminFromParams( + callerPackageName, params, setRuntimePermissionGrantStateResult); return setRuntimePermissionGrantStateResult; }).whenCompleteAsync((setRuntimePermissionGrantStateResult, err) -> { final long token = Binder.clearCallingIdentity(); try { if (err != null) { - Log.e(TAG, "Error setting permissions state for device admin " + packageName, - err); + Log.e(TAG, + "Error setting permissions state for device admin " + + callerPackageName, err); callback.accept(false); } else { callback.accept(Boolean.TRUE.equals(setRuntimePermissionGrantStateResult)); diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index 8105b6517015..ad9e8b3d6dd4 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -16,9 +16,7 @@ package android.permission; -import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT; import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED; -import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED; import static android.permission.PermissionControllerManager.COUNT_ONLY_WHEN_GRANTED; import static android.permission.PermissionControllerManager.COUNT_WHEN_SYSTEM; @@ -259,6 +257,8 @@ public abstract class PermissionControllerService extends Service { } /** + * @deprecated See {@link #onSetRuntimePermissionGrantStateByDeviceAdmin(String, + * AdminPermissionControlParams, Consumer)}. * Set the runtime permission state from a device admin. * * @param callerPackageName The package name of the admin requesting the change @@ -267,6 +267,7 @@ public abstract class PermissionControllerService extends Service { * @param grantState State to set the permission into * @param callback Callback waiting for whether the state could be set or not */ + @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin( @NonNull String callerPackageName, @NonNull String packageName, @@ -274,6 +275,20 @@ public abstract class PermissionControllerService extends Service { @NonNull Consumer<Boolean> callback); /** + * Set the runtime permission state from a device admin. + * + * @param callerPackageName The package name of the admin requesting the change + * @param params Parameters of admin request. + * @param callback Callback waiting for whether the state could be set or not + */ + @BinderThread + public void onSetRuntimePermissionGrantStateByDeviceAdmin( + @NonNull String callerPackageName, @NonNull AdminPermissionControlParams params, + @NonNull Consumer<Boolean> callback) { + throw new AbstractMethodError("Must be overridden in implementing class"); + } + + /** * Called when a package is considered inactive based on the criteria given by * {@link PermissionManager#startOneTimePermissionSession(String, long, int, int)}. * This method is called at the end of a one-time permission session @@ -468,32 +483,26 @@ public abstract class PermissionControllerService extends Service { } @Override - public void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName, - String packageName, String permission, int grantState, + public void setRuntimePermissionGrantStateByDeviceAdminFromParams( + String callerPackageName, AdminPermissionControlParams params, AndroidFuture callback) { checkStringNotEmpty(callerPackageName); - checkStringNotEmpty(packageName); - checkStringNotEmpty(permission); - checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED - || grantState == PERMISSION_GRANT_STATE_DENIED - || grantState == PERMISSION_GRANT_STATE_DEFAULT); - checkNotNull(callback); - - if (grantState == PERMISSION_GRANT_STATE_DENIED) { + if (params.getGrantState() == PERMISSION_GRANT_STATE_DENIED) { enforceSomePermissionsGrantedToCaller( Manifest.permission.GRANT_RUNTIME_PERMISSIONS); } - if (grantState == PERMISSION_GRANT_STATE_DENIED) { + if (params.getGrantState() == PERMISSION_GRANT_STATE_DENIED) { enforceSomePermissionsGrantedToCaller( Manifest.permission.REVOKE_RUNTIME_PERMISSIONS); } enforceSomePermissionsGrantedToCaller( Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY); + checkNotNull(callback); onSetRuntimePermissionGrantStateByDeviceAdmin(callerPackageName, - packageName, permission, grantState, callback::complete); + params, callback::complete); } @Override diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 91e091c50532..e134c29520b2 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -525,6 +525,13 @@ public final class DeviceConfig { */ public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor"; + /** + * Namespace for game overlay related features. + * + * @hide + */ + public static final String NAMESPACE_GAME_OVERLAY = "game_overlay"; + private static final Object sLock = new Object(); @GuardedBy("sLock") private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners = diff --git a/core/java/android/provider/SimPhonebookContract.java b/core/java/android/provider/SimPhonebookContract.java index f3a78562f73a..074d5f167ec3 100644 --- a/core/java/android/provider/SimPhonebookContract.java +++ b/core/java/android/provider/SimPhonebookContract.java @@ -262,7 +262,7 @@ public final class SimPhonebookContract { @WorkerThread public static int getEncodedNameLength( @NonNull ContentResolver resolver, @NonNull String name) { - name = Objects.requireNonNull(name); + Objects.requireNonNull(name); Bundle result = resolver.call(AUTHORITY, GET_ENCODED_NAME_LENGTH_METHOD_NAME, name, null); if (result == null || !result.containsKey(EXTRA_ENCODED_NAME_LENGTH)) { diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index f013976e8d0a..7996f090b1a4 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -5286,7 +5286,8 @@ public final class Telephony { * which network types are allowed for * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER}, * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER}, - * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER}. + * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER}, + * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}. * <P>Type: TEXT </P> * * @hide diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index a79b197d3faa..5a89cdf1d340 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -188,6 +188,7 @@ public final class KeymasterDefs { public static final int KM_PURPOSE_VERIFY = KeyPurpose.VERIFY; public static final int KM_PURPOSE_WRAP = KeyPurpose.WRAP_KEY; public static final int KM_PURPOSE_AGREE_KEY = KeyPurpose.AGREE_KEY; + public static final int KM_PURPOSE_ATTEST_KEY = KeyPurpose.ATTEST_KEY; // Key formats. public static final int KM_KEY_FORMAT_X509 = KeyFormat.X509; diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl index f91e122ea9cb..cc1cdedd0f96 100644 --- a/core/java/android/speech/IRecognitionService.aidl +++ b/core/java/android/speech/IRecognitionService.aidl @@ -43,7 +43,7 @@ oneway interface IRecognitionService { * @param featureId The feature in the package */ void startListening(in Intent recognizerIntent, in IRecognitionListener listener, - String packageName, String featureId); + String packageName, String featureId, int callingUid); /** * Stops listening for speech. Speech captured so far will be recognized as @@ -62,6 +62,7 @@ oneway interface IRecognitionService { * @param listener to receive callbacks, note that this must be non-null * @param packageName the package name calling this API * @param featureId The feature in the package + * @param isShutdown Whether the cancellation is caused by a client calling #shutdown */ - void cancel(in IRecognitionListener listener, String packageName, String featureId); + void cancel(in IRecognitionListener listener, String packageName, String featureId, boolean isShutdown); } diff --git a/core/java/android/speech/IRecognitionServiceManager.aidl b/core/java/android/speech/IRecognitionServiceManager.aidl index 7158ba2f9f63..8e5292d1ddf1 100644 --- a/core/java/android/speech/IRecognitionServiceManager.aidl +++ b/core/java/android/speech/IRecognitionServiceManager.aidl @@ -16,6 +16,8 @@ package android.speech; +import android.content.ComponentName; + import android.speech.IRecognitionServiceManagerCallback; /** @@ -23,6 +25,10 @@ import android.speech.IRecognitionServiceManagerCallback; * * {@hide} */ -interface IRecognitionServiceManager { - void createSession(in IRecognitionServiceManagerCallback callback); +oneway interface IRecognitionServiceManager { + void createSession( + in ComponentName componentName, + in IBinder clientToken, + boolean onDevice, + in IRecognitionServiceManagerCallback callback); } diff --git a/core/java/android/speech/IRecognitionServiceManagerCallback.aidl b/core/java/android/speech/IRecognitionServiceManagerCallback.aidl index d760810deda8..26afdaafd919 100644 --- a/core/java/android/speech/IRecognitionServiceManagerCallback.aidl +++ b/core/java/android/speech/IRecognitionServiceManagerCallback.aidl @@ -25,5 +25,5 @@ import android.speech.IRecognitionService; */ oneway interface IRecognitionServiceManagerCallback { void onSuccess(in IRecognitionService service); - void onError(); + void onError(int errorCode); } diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java index c97dbfe38ead..fd584f191743 100644 --- a/core/java/android/speech/RecognitionService.java +++ b/core/java/android/speech/RecognitionService.java @@ -105,17 +105,6 @@ public abstract class RecognitionService extends Service { int callingUid) { if (mCurrentCallback == null) { if (DBG) Log.d(TAG, "created new mCurrentCallback, listener = " + listener.asBinder()); - try { - listener.asBinder().linkToDeath(new IBinder.DeathRecipient() { - @Override - public void binderDied() { - mHandler.sendMessage(mHandler.obtainMessage(MSG_CANCEL, listener)); - } - }, 0); - } catch (RemoteException re) { - Log.e(TAG, "dead listener on startListening"); - return; - } mCurrentCallback = new Callback(listener, callingUid); RecognitionService.this.onStartListening(intent, mCurrentCallback); } else { @@ -352,7 +341,6 @@ public abstract class RecognitionService extends Service { * Return the Linux uid assigned to the process that sent you the current transaction that * is being processed. This is obtained from {@link Binder#getCallingUid()}. */ - // TODO(b/176578753): need to make sure this is fixed when proxied through system. public int getCallingUid() { return mCallingUid; } @@ -368,7 +356,7 @@ public abstract class RecognitionService extends Service { @Override public void startListening(Intent recognizerIntent, IRecognitionListener listener, - String packageName, String featureId) { + String packageName, String featureId, int callingUid) { Preconditions.checkNotNull(packageName); if (DBG) Log.d(TAG, "startListening called by:" + listener.asBinder()); @@ -377,7 +365,7 @@ public abstract class RecognitionService extends Service { packageName, featureId)) { service.mHandler.sendMessage(Message.obtain(service.mHandler, MSG_START_LISTENING, service.new StartListeningArgs( - recognizerIntent, listener, Binder.getCallingUid()))); + recognizerIntent, listener, callingUid))); } } @@ -397,7 +385,7 @@ public abstract class RecognitionService extends Service { @Override public void cancel(IRecognitionListener listener, String packageName, - String featureId) { + String featureId, boolean isShutdown) { Preconditions.checkNotNull(packageName); if (DBG) Log.d(TAG, "cancel called by:" + listener.asBinder()); diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index de879c63a1a6..850f997a2d2f 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -20,8 +20,8 @@ import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.content.pm.ResolveInfo; +import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -32,10 +32,9 @@ import android.os.ServiceManager; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; +import android.util.Slog; -import java.util.LinkedList; import java.util.List; -import java.util.Queue; /** * This class provides access to the speech recognition service. This service allows access to the @@ -107,6 +106,12 @@ public class SpeechRecognizer { /** Insufficient permissions */ public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9; + /** Too many requests from the same client. */ + public static final int ERROR_TOO_MANY_REQUESTS = 10; + + /** Server has been disconnected, e.g. because the app has crashed. */ + public static final int ERROR_SERVER_DISCONNECTED = 11; + /** action codes */ private final static int MSG_START = 1; private final static int MSG_STOP = 2; @@ -116,9 +121,6 @@ public class SpeechRecognizer { /** The actual RecognitionService endpoint */ private IRecognitionService mService; - /** The connection to the actual service */ - private Connection mConnection; - /** Context with which the manager was created */ private final Context mContext; @@ -151,15 +153,11 @@ public class SpeechRecognizer { } }; - /** - * Temporary queue, saving the messages until the connection will be established, afterwards, - * only mHandler will receive the messages - */ - private final Queue<Message> mPendingTasks = new LinkedList<Message>(); - /** The Listener that will receive all the callbacks */ private final InternalListener mListener = new InternalListener(); + private final IBinder mClientToken = new Binder(); + /** * The right way to create a {@code SpeechRecognizer} is by using * {@link #createSpeechRecognizer} static factory method @@ -181,30 +179,6 @@ public class SpeechRecognizer { } /** - * Basic ServiceConnection that records the mService variable. Additionally, on creation it - * invokes the {@link IRecognitionService#startListening(Intent, IRecognitionListener)}. - */ - private class Connection implements ServiceConnection { - - public void onServiceConnected(final ComponentName name, final IBinder service) { - // always done on the application main thread, so no need to send message to mHandler - mService = IRecognitionService.Stub.asInterface(service); - if (DBG) Log.d(TAG, "onServiceConnected - Success"); - while (!mPendingTasks.isEmpty()) { - mHandler.sendMessage(mPendingTasks.poll()); - } - } - - public void onServiceDisconnected(final ComponentName name) { - // always done on the application main thread, so no need to send message to mHandler - mService = null; - mConnection = null; - mPendingTasks.clear(); - if (DBG) Log.d(TAG, "onServiceDisconnected - Success"); - } - } - - /** * Checks whether a speech recognition service is available on the system. If this method * returns {@code false}, {@link SpeechRecognizer#createSpeechRecognizer(Context)} will * fail. @@ -303,87 +277,52 @@ public class SpeechRecognizer { throw new IllegalArgumentException("intent must not be null"); } checkIsCalledFromMainThread(); - if (mConnection == null) { // first time connection - // TODO(b/176578753): both flows should go through system service. - if (mOnDevice) { - connectToSystemService(); - } else { - connectToService(); - } - } - putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent)); - } - private void connectToSystemService() { - mManagerService = IRecognitionServiceManager.Stub.asInterface( - ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE)); - - if (mManagerService == null) { - mListener.onError(ERROR_CLIENT); - return; - } - - try { - // TODO(b/176578753): this has to supply information on whether to use on-device impl. - mManagerService.createSession(new IRecognitionServiceManagerCallback.Stub(){ - @Override - public void onSuccess(IRecognitionService service) throws RemoteException { - mService = service; - } - - @Override - public void onError() throws RemoteException { - Log.e(TAG, "Bind to system recognition service failed"); - mListener.onError(ERROR_CLIENT); - } - }); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - } - - private void connectToService() { - mConnection = new Connection(); - - Intent serviceIntent = new Intent(RecognitionService.SERVICE_INTERFACE); - - if (mServiceComponent == null) { - String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.VOICE_RECOGNITION_SERVICE); - - if (TextUtils.isEmpty(serviceComponent)) { - Log.e(TAG, "no selected voice recognition service"); - mListener.onError(ERROR_CLIENT); - return; + if (DBG) { + Slog.i(TAG, "#startListening called"); + if (mService == null) { + Slog.i(TAG, "Connection is not established yet"); } + } - serviceIntent.setComponent( - ComponentName.unflattenFromString(serviceComponent)); + if (mService == null) { + // First time connection: first establish a connection, then dispatch #startListening. + connectToSystemService( + () -> putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent))); } else { - serviceIntent.setComponent(mServiceComponent); - } - if (!mContext.bindService(serviceIntent, mConnection, - Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES)) { - Log.e(TAG, "bind to recognition service failed"); - mConnection = null; - mService = null; - mListener.onError(ERROR_CLIENT); - return; + putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent)); } } /** * Stops listening for speech. Speech captured so far will be recognized as if the user had - * stopped speaking at this point. Note that in the default case, this does not need to be - * called, as the speech endpointer will automatically stop the recognizer listening when it - * determines speech has completed. However, you can manipulate endpointer parameters directly - * using the intent extras defined in {@link RecognizerIntent}, in which case you may sometimes - * want to manually call this method to stop listening sooner. Please note that + * stopped speaking at this point. + * + * <p>Note that in the default case, this does not need to be called, as the speech endpointer + * will automatically stop the recognizer listening when it determines speech has completed. + * However, you can manipulate endpointer parameters directly using the intent extras defined in + * {@link RecognizerIntent}, in which case you may sometimes want to manually call this method + * to stop listening sooner. + * + * <p>Upon invocation clients must wait until {@link RecognitionListener#onResults} or + * {@link RecognitionListener#onError} are invoked before calling + * {@link SpeechRecognizer#startListening} again. Otherwise such an attempt would be rejected by + * recognition service. + * + * <p>Please note that * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise * no notifications will be received. */ public void stopListening() { checkIsCalledFromMainThread(); + + if (DBG) { + Slog.i(TAG, "#stopListening called"); + if (mService == null) { + Slog.i(TAG, "Connection is not established yet"); + } + } + putMessage(Message.obtain(mHandler, MSG_STOP)); } @@ -405,11 +344,7 @@ public class SpeechRecognizer { } private void putMessage(Message msg) { - if (mService == null) { - mPendingTasks.offer(msg); - } else { - mHandler.sendMessage(msg); - } + mHandler.sendMessage(msg); } /** sends the actual message to the service */ @@ -419,7 +354,7 @@ public class SpeechRecognizer { } try { mService.startListening(recognizerIntent, mListener, mContext.getOpPackageName(), - mContext.getAttributionTag()); + mContext.getAttributionTag(), android.os.Process.myUid()); if (DBG) Log.d(TAG, "service start listening command succeded"); } catch (final RemoteException e) { Log.e(TAG, "startListening() failed", e); @@ -448,7 +383,11 @@ public class SpeechRecognizer { return; } try { - mService.cancel(mListener, mContext.getOpPackageName(), mContext.getAttributionTag()); + mService.cancel( + mListener, + mContext.getOpPackageName(), + mContext.getAttributionTag(), + false /* isShutdown */); if (DBG) Log.d(TAG, "service cancel command succeded"); } catch (final RemoteException e) { Log.e(TAG, "cancel() failed", e); @@ -471,28 +410,94 @@ public class SpeechRecognizer { mListener.mInternalListener = listener; } - /** - * Destroys the {@code SpeechRecognizer} object. - */ + /** Destroys the {@code SpeechRecognizer} object. */ public void destroy() { if (mService != null) { try { mService.cancel(mListener, mContext.getOpPackageName(), - mContext.getAttributionTag()); + mContext.getAttributionTag(), true /* isShutdown */); } catch (final RemoteException e) { // Not important } } - if (mConnection != null) { - mContext.unbindService(mConnection); - } - mPendingTasks.clear(); mService = null; - mConnection = null; mListener.mInternalListener = null; } + /** Establishes a connection to system server proxy and initializes the session. */ + private void connectToSystemService(Runnable onSuccess) { + mManagerService = IRecognitionServiceManager.Stub.asInterface( + ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE)); + + if (mManagerService == null) { + mListener.onError(ERROR_CLIENT); + return; + } + + ComponentName componentName = getSpeechRecognizerComponentName(); + + if (!mOnDevice && componentName == null) { + mListener.onError(ERROR_CLIENT); + return; + } + + try { + mManagerService.createSession( + componentName, + mClientToken, + mOnDevice, + new IRecognitionServiceManagerCallback.Stub(){ + @Override + public void onSuccess(IRecognitionService service) throws RemoteException { + mService = service; + onSuccess.run(); + } + + @Override + public void onError(int errorCode) throws RemoteException { + Log.e(TAG, "Bind to system recognition service failed"); + mListener.onError(errorCode); + } + }); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Returns the component name to be used for establishing a connection, based on the parameters + * used during initialization. + * + * <p>Note the 3 different scenarios: + * <ol> + * <li>On-device speech recognizer which is determined by the manufacturer and not + * changeable by the user + * <li>Default user-selected speech recognizer as specified by + * {@code Settings.Secure.VOICE_RECOGNITION_SERVICE} + * <li>Custom speech recognizer supplied by the client. + */ + private ComponentName getSpeechRecognizerComponentName() { + if (mOnDevice) { + return null; + } + + if (mServiceComponent != null) { + return mServiceComponent; + } + + String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.VOICE_RECOGNITION_SERVICE); + + if (TextUtils.isEmpty(serviceComponent)) { + Log.e(TAG, "no selected voice recognition service"); + mListener.onError(ERROR_CLIENT); + return null; + } + + return ComponentName.unflattenFromString(serviceComponent); + } + /** * Internal wrapper of IRecognitionListener which will propagate the results to * RecognitionListener diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 5d66dc7f29eb..7a185382a631 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -35,7 +35,6 @@ import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; -import android.os.ServiceManager; import android.text.TextUtils; import android.util.Log; @@ -52,7 +51,6 @@ import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.Set; -import java.util.concurrent.Executor; /** * @@ -697,8 +695,6 @@ public class TextToSpeech { public static final String KEY_FEATURE_NETWORK_RETRIES_COUNT = "networkRetriesCount"; } - private static final boolean DEBUG = false; - private final Context mContext; @UnsupportedAppUsage private Connection mConnectingServiceConnection; @@ -720,9 +716,6 @@ public class TextToSpeech { private final Map<CharSequence, Uri> mUtterances; private final Bundle mParams = new Bundle(); private final TtsEngines mEnginesHelper; - private final boolean mIsSystem; - @Nullable private final Executor mInitExecutor; - @UnsupportedAppUsage private volatile String mCurrentEngine = null; @@ -765,21 +758,8 @@ public class TextToSpeech { */ public TextToSpeech(Context context, OnInitListener listener, String engine, String packageName, boolean useFallback) { - this(context, /* initExecutor= */ null, listener, engine, packageName, - useFallback, /* isSystem= */ true); - } - - /** - * Used internally to instantiate TextToSpeech objects. - * - * @hide - */ - private TextToSpeech(Context context, @Nullable Executor initExecutor, - OnInitListener initListener, String engine, String packageName, boolean useFallback, - boolean isSystem) { mContext = context; - mInitExecutor = initExecutor; - mInitListener = initListener; + mInitListener = listener; mRequestedEngine = engine; mUseFallback = useFallback; @@ -788,9 +768,6 @@ public class TextToSpeech { mUtteranceProgressListener = null; mEnginesHelper = new TtsEngines(mContext); - - mIsSystem = isSystem; - initTts(); } @@ -865,14 +842,10 @@ public class TextToSpeech { } private boolean connectToEngine(String engine) { - Connection connection; - if (mIsSystem) { - connection = new SystemConnection(); - } else { - connection = new DirectConnection(); - } - - boolean bound = connection.connect(engine); + Connection connection = new Connection(); + Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE); + intent.setPackage(engine); + boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE); if (!bound) { Log.e(TAG, "Failed to bind to " + engine); return false; @@ -884,19 +857,11 @@ public class TextToSpeech { } private void dispatchOnInit(int result) { - Runnable onInitCommand = () -> { - synchronized (mStartLock) { - if (mInitListener != null) { - mInitListener.onInit(result); - mInitListener = null; - } + synchronized (mStartLock) { + if (mInitListener != null) { + mInitListener.onInit(result); + mInitListener = null; } - }; - - if (mInitExecutor != null) { - mInitExecutor.execute(onInitCommand); - } else { - onInitCommand.run(); } } @@ -2162,17 +2127,13 @@ public class TextToSpeech { return mEnginesHelper.getEngines(); } - private abstract class Connection implements ServiceConnection { + private class Connection implements ServiceConnection { private ITextToSpeechService mService; private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask; private boolean mEstablished; - abstract boolean connect(String engine); - - abstract void disconnect(); - private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() { public void onStop(String utteranceId, boolean isStarted) @@ -2238,6 +2199,11 @@ public class TextToSpeech { }; private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> { + private final ComponentName mName; + + public SetupConnectionAsyncTask(ComponentName name) { + mName = name; + } @Override protected Integer doInBackground(Void... params) { @@ -2261,7 +2227,7 @@ public class TextToSpeech { mParams.putString(Engine.KEY_PARAM_VOICE_NAME, defaultVoiceName); } - Log.i(TAG, "Setting up the connection to TTS engine..."); + Log.i(TAG, "Set up connection to " + mName); return SUCCESS; } catch (RemoteException re) { Log.e(TAG, "Error connecting to service, setCallback() failed"); @@ -2283,11 +2249,11 @@ public class TextToSpeech { } @Override - public void onServiceConnected(ComponentName componentName, IBinder service) { + public void onServiceConnected(ComponentName name, IBinder service) { synchronized(mStartLock) { mConnectingServiceConnection = null; - Log.i(TAG, "Connected to TTS engine"); + Log.i(TAG, "Connected to " + name); if (mOnSetupConnectionAsyncTask != null) { mOnSetupConnectionAsyncTask.cancel(false); @@ -2297,7 +2263,7 @@ public class TextToSpeech { mServiceConnection = Connection.this; mEstablished = false; - mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(); + mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name); mOnSetupConnectionAsyncTask.execute(); } } @@ -2311,7 +2277,7 @@ public class TextToSpeech { * * @return true if we cancel mOnSetupConnectionAsyncTask in progress. */ - protected boolean clearServiceConnection() { + private boolean clearServiceConnection() { synchronized(mStartLock) { boolean result = false; if (mOnSetupConnectionAsyncTask != null) { @@ -2329,8 +2295,8 @@ public class TextToSpeech { } @Override - public void onServiceDisconnected(ComponentName componentName) { - Log.i(TAG, "Disconnected from TTS engine"); + public void onServiceDisconnected(ComponentName name) { + Log.i(TAG, "Asked to disconnect from " + name); if (clearServiceConnection()) { /* We need to protect against a rare case where engine * dies just after successful connection - and we process onServiceDisconnected @@ -2342,6 +2308,11 @@ public class TextToSpeech { } } + public void disconnect() { + mContext.unbindService(this); + clearServiceConnection(); + } + public boolean isEstablished() { return mService != null && mEstablished; } @@ -2371,91 +2342,6 @@ public class TextToSpeech { } } - // Currently all the clients are routed through the System connection. Direct connection - // is left for debugging, testing and benchmarking purposes. - // TODO(b/179599071): Remove direct connection once system one is fully tested. - private class DirectConnection extends Connection { - @Override - boolean connect(String engine) { - Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE); - intent.setPackage(engine); - return mContext.bindService(intent, this, Context.BIND_AUTO_CREATE); - } - - @Override - void disconnect() { - mContext.unbindService(this); - clearServiceConnection(); - } - } - - private class SystemConnection extends Connection { - - @Nullable - private volatile ITextToSpeechSession mSession; - - @Override - boolean connect(String engine) { - IBinder binder = ServiceManager.getService(Context.TEXT_TO_SPEECH_MANAGER_SERVICE); - - ITextToSpeechManager manager = ITextToSpeechManager.Stub.asInterface(binder); - - if (manager == null) { - Log.e(TAG, "System service is not available!"); - return false; - } - - if (DEBUG) { - Log.d(TAG, "Connecting to engine: " + engine); - } - - try { - manager.createSession(engine, new ITextToSpeechSessionCallback.Stub() { - @Override - public void onConnected(ITextToSpeechSession session, IBinder serviceBinder) { - mSession = session; - onServiceConnected( - /* componentName= */ null, - serviceBinder); - } - - @Override - public void onDisconnected() { - onServiceDisconnected(/* componentName= */ null); - } - - @Override - public void onError(String errorInfo) { - Log.w(TAG, "System TTS connection error: " + errorInfo); - // The connection was not established successfully - handle as - // disconnection: clear the state and notify the user. - onServiceDisconnected(/* componentName= */ null); - } - }); - - return true; - } catch (RemoteException ex) { - Log.e(TAG, "Error communicating with the System Server: ", ex); - throw ex.rethrowFromSystemServer(); - } - } - - @Override - void disconnect() { - ITextToSpeechSession session = mSession; - - if (session != null) { - try { - session.disconnect(); - } catch (RemoteException ex) { - Log.w(TAG, "Error disconnecting session", ex); - } - - clearServiceConnection(); - } - } - } - private interface Action<R> { R run(ITextToSpeechService service) throws RemoteException; } diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index 77f9c1a6fc90..e7ceada6180a 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -1840,13 +1840,15 @@ public class PhoneStateListener { * * @param allowedNetworkTypesList Map associating all allowed network type reasons * ({@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER}, - * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER}, and - * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER}) with reason's allowed + * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER}, + * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER}, and + * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}) with reason's allowed * network type values. * For example: * map{{TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER, long type value}, * {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER, long type value}, - * {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER, long type value}} + * {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER, long type value}, + * {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, long type value}} */ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) void onAllowedNetworkTypesChanged( diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 87820a89280c..e599888c3bee 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -12706,12 +12706,12 @@ public class BatteryStatsImpl extends BatteryStats { // When the battery is not on, we don't attribute the cpu times to any timers but we still // need to take the snapshots. if (!onBattery) { - mCpuUidUserSysTimeReader.readDelta(null); - mCpuUidFreqTimeReader.readDelta(null); + mCpuUidUserSysTimeReader.readDelta(false, null); + mCpuUidFreqTimeReader.readDelta(false, null); mNumAllUidCpuTimeReads += 2; if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) { - mCpuUidActiveTimeReader.readDelta(null); - mCpuUidClusterTimeReader.readDelta(null); + mCpuUidActiveTimeReader.readDelta(false, null); + mCpuUidClusterTimeReader.readDelta(false, null); mNumAllUidCpuTimeReads += 2; } for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) { @@ -12897,7 +12897,7 @@ public class BatteryStatsImpl extends BatteryStats { final long startTimeMs = mClocks.uptimeMillis(); final long elapsedRealtimeMs = mClocks.elapsedRealtime(); - mCpuUidUserSysTimeReader.readDelta((uid, timesUs) -> { + mCpuUidUserSysTimeReader.readDelta(false, (uid, timesUs) -> { long userTimeUs = timesUs[0], systemTimeUs = timesUs[1]; uid = mapUid(uid); @@ -13011,7 +13011,7 @@ public class BatteryStatsImpl extends BatteryStats { final long startTimeMs = mClocks.uptimeMillis(); final long elapsedRealtimeMs = mClocks.elapsedRealtime(); final List<Integer> uidsToRemove = new ArrayList<>(); - mCpuUidFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> { + mCpuUidFreqTimeReader.readDelta(false, (uid, cpuFreqTimeMs) -> { uid = mapUid(uid); if (Process.isIsolated(uid)) { uidsToRemove.add(uid); @@ -13129,7 +13129,7 @@ public class BatteryStatsImpl extends BatteryStats { final long startTimeMs = mClocks.uptimeMillis(); final long elapsedRealtimeMs = mClocks.elapsedRealtime(); final List<Integer> uidsToRemove = new ArrayList<>(); - mCpuUidActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> { + mCpuUidActiveTimeReader.readDelta(false, (uid, cpuActiveTimesMs) -> { uid = mapUid(uid); if (Process.isIsolated(uid)) { uidsToRemove.add(uid); @@ -13163,7 +13163,7 @@ public class BatteryStatsImpl extends BatteryStats { final long startTimeMs = mClocks.uptimeMillis(); final long elapsedRealtimeMs = mClocks.elapsedRealtime(); final List<Integer> uidsToRemove = new ArrayList<>(); - mCpuUidClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> { + mCpuUidClusterTimeReader.readDelta(false, (uid, cpuClusterTimesMs) -> { uid = mapUid(uid); if (Process.isIsolated(uid)) { uidsToRemove.add(uid); diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java index 45d81280af4a..97f727ba72c5 100644 --- a/core/java/com/android/internal/os/CpuPowerCalculator.java +++ b/core/java/com/android/internal/os/CpuPowerCalculator.java @@ -124,15 +124,15 @@ public class CpuPowerCalculator extends PowerCalculator { long durationMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000; // Constant battery drain when CPU is active - double powerMah = mCpuActivePowerEstimator.calculatePower(u.getCpuActiveTime()); + double powerMah = calculateActiveCpuPowerMah(u.getCpuActiveTime()); // Additional per-cluster battery drain long[] cpuClusterTimes = u.getCpuClusterTimes(); if (cpuClusterTimes != null) { if (cpuClusterTimes.length == mNumCpuClusters) { for (int cluster = 0; cluster < mNumCpuClusters; cluster++) { - double power = mPerClusterPowerEstimators[cluster] - .calculatePower(cpuClusterTimes[cluster]); + double power = calculatePerCpuClusterPowerMah(cluster, + cpuClusterTimes[cluster]); powerMah += power; if (DEBUG) { Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster @@ -151,8 +151,8 @@ public class CpuPowerCalculator extends PowerCalculator { final int speedsForCluster = mPerCpuFreqPowerEstimators[cluster].length; for (int speed = 0; speed < speedsForCluster; speed++) { final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType); - final double power = - mPerCpuFreqPowerEstimators[cluster][speed].calculatePower(timeUs / 1000); + final double power = calculatePerCpuFreqPowerMah(cluster, speed, + timeUs / 1000); if (DEBUG) { Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #" + speed + " timeUs=" + timeUs + " power=" @@ -207,4 +207,39 @@ public class CpuPowerCalculator extends PowerCalculator { result.powerMah = powerMah; result.packageWithHighestDrain = packageWithHighestDrain; } + + /** + * Calculates active CPU power consumption. + * + * @param durationsMs duration of CPU usage. + * @return a double in milliamp-hours of estimated active CPU power consumption. + */ + public double calculateActiveCpuPowerMah(long durationsMs) { + return mCpuActivePowerEstimator.calculatePower(durationsMs); + } + + /** + * Calculates CPU cluster power consumption. + * + * @param cluster CPU cluster used. + * @param clusterDurationMs duration of CPU cluster usage. + * @return a double in milliamp-hours of estimated CPU cluster power consumption. + */ + public double calculatePerCpuClusterPowerMah(int cluster, long clusterDurationMs) { + return mPerClusterPowerEstimators[cluster].calculatePower(clusterDurationMs); + } + + /** + * Calculates CPU cluster power consumption at a specific speedstep. + * + * @param cluster CPU cluster used. + * @param speedStep which speedstep used. + * @param clusterSpeedDurationsMs duration of CPU cluster usage at the specified speed step. + * @return a double in milliamp-hours of estimated CPU cluster-speed power consumption. + */ + public double calculatePerCpuFreqPowerMah(int cluster, int speedStep, + long clusterSpeedDurationsMs) { + return mPerCpuFreqPowerEstimators[cluster][speedStep].calculatePower( + clusterSpeedDurationsMs); + } } diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java index f7fad2c5bbaa..4299f0936dae 100644 --- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java +++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java @@ -91,15 +91,24 @@ public abstract class KernelCpuUidTimeReader<T> { * Reads the proc file, calling into the callback with a delta of time for each UID. * * @param cb The callback to invoke for each line of the proc file. If null,the data is - * consumed and subsequent calls to readDelta will provide a fresh delta. */ public void readDelta(@Nullable Callback<T> cb) { + readDelta(false, cb); + } + + /** + * Reads the proc file, calling into the callback with a delta of time for each UID. + * + * @param force Ignore the throttling and force read the delta. + * @param cb The callback to invoke for each line of the proc file. If null,the data is + */ + public void readDelta(boolean force, @Nullable Callback<T> cb) { if (!mThrottle) { readDeltaImpl(cb); return; } final long currTimeMs = SystemClock.elapsedRealtime(); - if (currTimeMs < mLastReadTimeMs + mMinTimeBetweenRead) { + if (!force && currTimeMs < mLastReadTimeMs + mMinTimeBetweenRead) { if (DEBUG) { Slog.d(mTag, "Throttle readDelta"); } diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java index 95999a716707..fe3042d9da54 100644 --- a/core/java/com/android/server/BootReceiver.java +++ b/core/java/com/android/server/BootReceiver.java @@ -74,6 +74,7 @@ public class BootReceiver extends BroadcastReceiver { private static final int GMSCORE_LASTK_LOG_SIZE = 196608; private static final String TAG_TOMBSTONE = "SYSTEM_TOMBSTONE"; + private static final String TAG_TOMBSTONE_PROTO = "SYSTEM_TOMBSTONE_PROTO"; // The pre-froyo package and class of the system updater, which // ran in the system process. We need to remove its packages here @@ -251,14 +252,14 @@ public class BootReceiver extends BroadcastReceiver { * @param ctx Context * @param tombstone path to the tombstone */ - public static void addTombstoneToDropBox(Context ctx, File tombstone) { + public static void addTombstoneToDropBox(Context ctx, File tombstone, boolean proto) { final DropBoxManager db = ctx.getSystemService(DropBoxManager.class); final String bootReason = SystemProperties.get("ro.boot.bootreason", null); HashMap<String, Long> timestamps = readTimestamps(); try { final String headers = getBootHeadersToLogAndUpdate(); addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE, - TAG_TOMBSTONE); + proto ? TAG_TOMBSTONE_PROTO : TAG_TOMBSTONE); } catch (IOException e) { Slog.e(TAG, "Can't log tombstone", e); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index fe4106db7185..c61802d54e04 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1313,11 +1313,13 @@ android:protectionLevel="dangerous|instant" /> <!-- ====================================================================== --> - <!-- Permissions for accessing the UCE Service --> + <!-- Permissions for accessing the vendor UCE Service --> <!-- ====================================================================== --> <!-- @hide Allows an application to Access UCE-Presence. <p>Protection level: signature|privileged + @deprecated Framework should no longer use this permission to access the vendor UCE service + using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase --> <permission android:name="android.permission.ACCESS_UCE_PRESENCE_SERVICE" android:permissionGroup="android.permission-group.PHONE" @@ -1325,6 +1327,8 @@ <!-- @hide Allows an application to Access UCE-OPTIONS. <p>Protection level: signature|privileged + @deprecated Framework should no longer use this permission to access the vendor UCE service + using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase --> <permission android:name="android.permission.ACCESS_UCE_OPTIONS_SERVICE" android:permissionGroup="android.permission-group.PHONE" @@ -2278,6 +2282,11 @@ <permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows read access to privileged network state in the device config. + @hide Used internally. --> + <permission android:name="android.permission.READ_NETWORK_DEVICE_CONFIG" + android:protectionLevel="signature|privileged" /> + <!-- Allows to read device identifiers and use ICC based authentication like EAP-AKA. Often required in authentication to access the carrier's server and manage services of the subscriber. @@ -2463,6 +2472,15 @@ <permission android:name="android.permission.BIND_GBA_SERVICE" android:protectionLevel="signature" /> + <!-- Required for an Application to access APIs related to RCS User Capability Exchange. + <p> This permission is only granted to system applications fulfilling the SMS, Dialer, and + Contacts app roles. + <p>Protection level: internal|role + @SystemApi + @hide --> + <permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE" + android:protectionLevel="internal|role" /> + <!-- ================================== --> <!-- Permissions for sdcard interaction --> <!-- ================================== --> @@ -3887,6 +3905,12 @@ <permission android:name="android.permission.SET_KEYBOARD_LAYOUT" android:protectionLevel="signature" /> + <!-- Allows an app to use exact alarm scheduling APIs to perform timing + sensitive background work. + --> + <permission android:name="android.permission.SCHEDULE_EXACT_ALARM" + android:protectionLevel="normal|appop"/> + <!-- Allows an application to query tablet mode state and monitor changes in it. <p>Not for use by third-party applications. diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index e7c4947bed5f..59c260cecfbf 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -240,77 +240,114 @@ <color name="conversation_important_highlight">#F9AB00</color> - <!-- Lightest shade of the main color used by the system. White. + <!-- Lightest shade of the primary color used by the system. White. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_0">#ffffff</color> - <!-- Shade of the main system color at 95% lightness. + <color name="system_primary_0">#ffffff</color> + <!-- Shade of the primary system color at 95% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_50">#f2f2f2</color> - <!-- Shade of the main system color at 90% lightness. + <color name="system_primary_50">#f2f2f2</color> + <!-- Shade of the primary system color at 90% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_100">#e3e3e3</color> - <!-- Shade of the main system color at 80% lightness. + <color name="system_primary_100">#e3e3e3</color> + <!-- Shade of the primary system color at 80% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_200">#c7c7c7</color> - <!-- Shade of the main system color at 70% lightness. + <color name="system_primary_200">#c7c7c7</color> + <!-- Shade of the primary system color at 70% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_300">#ababab</color> - <!-- Shade of the main system color at 60% lightness. + <color name="system_primary_300">#ababab</color> + <!-- Shade of the primary system color at 60% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_400">#8f8f8f</color> - <!-- Shade of the main system color at 50% lightness. + <color name="system_primary_400">#8f8f8f</color> + <!-- Shade of the primary system color at 50% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_500">#757575</color> - <!-- Shade of the main system color at 40% lightness. + <color name="system_primary_500">#757575</color> + <!-- Shade of the primary system color at 40% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_600">#5e5e5e</color> - <!-- Shade of the main system color at 30% lightness. + <color name="system_primary_600">#5e5e5e</color> + <!-- Shade of the primary system color at 30% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_700">#474747</color> - <!-- Shade of the main system color at 20% lightness. + <color name="system_primary_700">#474747</color> + <!-- Shade of the primary system color at 20% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_800">#303030</color> - <!-- Shade of the main system color at 10% lightness. + <color name="system_primary_800">#303030</color> + <!-- Shade of the primary system color at 10% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_900">#1f1f1f</color> - <!-- Darkest shade of the main color used by the system. Black. + <color name="system_primary_900">#1f1f1f</color> + <!-- Darkest shade of the primary color used by the system. Black. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_main_1000">#000000</color> + <color name="system_primary_1000">#000000</color> - <!-- Lightest shade of the accent color used by the system. White. + <!-- Lightest shade of the secondary color used by the system. White. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent_0">#ffffff</color> - <!-- Shade of the accent system color at 95% lightness. + <color name="system_secondary_0">#ffffff</color> + <!-- Shade of the secondary system color at 95% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent_50">#91fff4</color> - <!-- Shade of the accent system color at 90% lightness. + <color name="system_secondary_50">#91fff4</color> + <!-- Shade of the secondary system color at 90% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent_100">#83f6e5</color> - <!-- Shade of the accent system color at 80% lightness. + <color name="system_secondary_100">#83f6e5</color> + <!-- Shade of the secondary system color at 80% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent_200">#65d9c9</color> - <!-- Shade of the accent system color at 70% lightness. + <color name="system_secondary_200">#65d9c9</color> + <!-- Shade of the secondary system color at 70% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent_300">#45bdae</color> - <!-- Shade of the accent system color at 60% lightness. + <color name="system_secondary_300">#45bdae</color> + <!-- Shade of the secondary system color at 60% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent_400">#1fa293</color> - <!-- Shade of the accent system color at 50% lightness. + <color name="system_secondary_400">#1fa293</color> + <!-- Shade of the secondary system color at 50% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent_500">#008377</color> - <!-- Shade of the accent system color at 40% lightness. + <color name="system_secondary_500">#008377</color> + <!-- Shade of the secondary system color at 40% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent_600">#006d61</color> - <!-- Shade of the accent system color at 30% lightness. + <color name="system_secondary_600">#006d61</color> + <!-- Shade of the secondary system color at 30% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent_700">#005449</color> - <!-- Shade of the accent system color at 20% lightness. + <color name="system_secondary_700">#005449</color> + <!-- Shade of the secondary system color at 20% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent_800">#003c33</color> - <!-- Shade of the accent system color at 10% lightness. + <color name="system_secondary_800">#003c33</color> + <!-- Shade of the secondary system color at 10% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent_900">#00271e</color> - <!-- Darkest shade of the accent color used by the system. Black. + <color name="system_secondary_900">#00271e</color> + <!-- Darkest shade of the secondary color used by the system. Black. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent_1000">#000000</color> + <color name="system_secondary_1000">#000000</color> + + <!-- Lightest shade of the neutral color used by the system. White. + This value can be overlaid at runtime by OverlayManager RROs. --> + <color name="system_neutral_0">#ffffff</color> + <!-- Shade of the neutral system color at 95% lightness. + This value can be overlaid at runtime by OverlayManager RROs. --> + <color name="system_neutral_50">#f0f0f0</color> + <!-- Shade of the neutral system color at 90% lightness. + This value can be overlaid at runtime by OverlayManager RROs. --> + <color name="system_neutral_100">#e2e2e2</color> + <!-- Shade of the neutral system color at 80% lightness. + This value can be overlaid at runtime by OverlayManager RROs. --> + <color name="system_neutral_200">#c6c6c6</color> + <!-- Shade of the neutral system color at 70% lightness. + This value can be overlaid at runtime by OverlayManager RROs. --> + <color name="system_neutral_300">#ababab</color> + <!-- Shade of the neutral system color at 60% lightness. + This value can be overlaid at runtime by OverlayManager RROs. --> + <color name="system_neutral_400">#909090</color> + <!-- Shade of the neutral system color at 50% lightness. + This value can be overlaid at runtime by OverlayManager RROs. --> + <color name="system_neutral_500">#757575</color> + <!-- Shade of the neutral system color at 40% lightness. + This value can be overlaid at runtime by OverlayManager RROs. --> + <color name="system_neutral_600">#5e5e5e</color> + <!-- Shade of the neutral system color at 30% lightness. + This value can be overlaid at runtime by OverlayManager RROs. --> + <color name="system_neutral_700">#464646</color> + <!-- Shade of the neutral system color at 20% lightness. + This value can be overlaid at runtime by OverlayManager RROs. --> + <color name="system_neutral_800">#303030</color> + <!-- Shade of the neutral system color at 10% lightness. + This value can be overlaid at runtime by OverlayManager RROs. --> + <color name="system_neutral_900">#1b1b1b</color> + <!-- Darkest shade of the neutral color used by the system. Black. + This value can be overlaid at runtime by OverlayManager RROs. --> + <color name="system_neutral_1000">#000000</color> </resources> diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml index f7234269f227..1020c14ef665 100644 --- a/core/res/res/values/colors_device_defaults.xml +++ b/core/res/res/values/colors_device_defaults.xml @@ -17,9 +17,9 @@ <!-- Colors specific to DeviceDefault themes. These are mostly pass-throughs to enable overlaying new theme colors. --> <resources> - <color name="primary_device_default_dark">@color/system_main_800</color> - <color name="primary_device_default_light">@color/system_main_50</color> - <color name="primary_device_default_settings">@color/system_main_800</color> + <color name="primary_device_default_dark">@color/system_primary_800</color> + <color name="primary_device_default_light">@color/system_primary_50</color> + <color name="primary_device_default_settings">@color/system_primary_800</color> <color name="primary_device_default_settings_light">@color/primary_device_default_light</color> <color name="primary_dark_device_default_dark">@color/primary_device_default_dark</color> <color name="primary_dark_device_default_light">@color/primary_device_default_light</color> @@ -33,21 +33,21 @@ <color name="tertiary_device_default_settings">@color/tertiary_material_settings</color> <color name="quaternary_device_default_settings">@color/quaternary_material_settings</color> - <color name="accent_device_default_light">@color/system_accent_600</color> - <color name="accent_device_default_dark">@color/system_accent_200</color> + <color name="accent_device_default_light">@color/system_secondary_600</color> + <color name="accent_device_default_dark">@color/system_secondary_200</color> <color name="accent_device_default">@color/accent_device_default_light</color> - <color name="background_device_default_dark">@color/system_main_800</color> - <color name="background_device_default_light">@color/system_main_50</color> - <color name="background_floating_device_default_dark">@color/system_main_900</color> - <color name="background_floating_device_default_light">@color/system_main_100</color> + <color name="background_device_default_dark">@color/system_primary_800</color> + <color name="background_device_default_light">@color/system_primary_50</color> + <color name="background_floating_device_default_dark">@color/system_primary_900</color> + <color name="background_floating_device_default_light">@color/system_primary_100</color> - <color name="text_color_primary_device_default_light">@color/system_main_900</color> - <color name="text_color_primary_device_default_dark">@color/system_main_50</color> - <color name="text_color_secondary_device_default_light">@color/system_main_700</color> - <color name="text_color_secondary_device_default_dark">@color/system_main_200</color> - <color name="text_color_tertiary_device_default_light">@color/system_main_500</color> - <color name="text_color_tertiary_device_default_dark">@color/system_main_400</color> + <color name="text_color_primary_device_default_light">@color/system_primary_900</color> + <color name="text_color_primary_device_default_dark">@color/system_primary_50</color> + <color name="text_color_secondary_device_default_light">@color/system_primary_700</color> + <color name="text_color_secondary_device_default_dark">@color/system_primary_200</color> + <color name="text_color_tertiary_device_default_light">@color/system_primary_500</color> + <color name="text_color_tertiary_device_default_dark">@color/system_primary_400</color> <color name="foreground_device_default_light">@color/text_color_primary_device_default_light</color> <color name="foreground_device_default_dark">@color/text_color_primary_device_default_dark</color> @@ -55,8 +55,8 @@ <color name="error_color_device_default_dark">@color/error_color_material_dark</color> <color name="error_color_device_default_light">@color/error_color_material_light</color> - <color name="list_divider_color_light">@color/system_main_500</color> - <color name="list_divider_color_dark">@color/system_main_400</color> + <color name="list_divider_color_light">@color/system_primary_500</color> + <color name="list_divider_color_dark">@color/system_primary_400</color> <color name="list_divider_opacity_device_default_light">@android:color/white</color> <color name="list_divider_opacity_device_default_dark">@android:color/white</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index dd048f3e0993..faa2157be302 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1948,6 +1948,8 @@ <string name="config_systemAutomotiveCluster" translatable="false"></string> <!-- The name of the package that will hold the system shell role. --> <string name="config_systemShell" translatable="false">com.android.shell</string> + <!-- The name of the package that will hold the system contacts role. --> + <string name="config_systemContacts" translatable="false">com.android.contacts</string> <!-- The name of the package that will be allowed to change its components' label/icon. --> <string name="config_overrideComponentUiPackage" translatable="false"></string> @@ -2509,10 +2511,9 @@ <string name="config_ethernet_tcp_buffers" translatable="false">524288,1048576,3145728,524288,1048576,2097152</string> <!-- What source to use to estimate link upstream and downstream bandwidth capacities. - Default is carrier_config, but it should be set to modem if the modem is returning - predictive (instead of instantaneous) bandwidth estimate. - Values are carrier_config and modem. --> - <string name="config_bandwidthEstimateSource">carrier_config</string> + Default is bandwidth_estimator. + Values are bandwidth_estimator, carrier_config and modem. --> + <string name="config_bandwidthEstimateSource">bandwidth_estimator</string> <!-- Whether WiFi display is supported by this device. There are many prerequisites for this feature to work correctly. diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 1ebcf77d1ad3..22dce9b9935c 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3086,32 +3086,45 @@ <!-- color definitions go here --> <!-- Material design dynamic system palette:--> - <!-- Dominant color --> - <public name="system_main_0" /> - <public name="system_main_50" /> - <public name="system_main_100" /> - <public name="system_main_200" /> - <public name="system_main_300" /> - <public name="system_main_400" /> - <public name="system_main_500" /> - <public name="system_main_600" /> - <public name="system_main_700" /> - <public name="system_main_800" /> - <public name="system_main_900" /> - <public name="system_main_1000" /> - <!-- Accent color --> - <public name="system_accent_0" /> - <public name="system_accent_50" /> - <public name="system_accent_100" /> - <public name="system_accent_200" /> - <public name="system_accent_300" /> - <public name="system_accent_400" /> - <public name="system_accent_500" /> - <public name="system_accent_600" /> - <public name="system_accent_700" /> - <public name="system_accent_800" /> - <public name="system_accent_900" /> - <public name="system_accent_1000" /> + <!-- Primary color --> + <public name="system_primary_0" /> + <public name="system_primary_50" /> + <public name="system_primary_100" /> + <public name="system_primary_200" /> + <public name="system_primary_300" /> + <public name="system_primary_400" /> + <public name="system_primary_500" /> + <public name="system_primary_600" /> + <public name="system_primary_700" /> + <public name="system_primary_800" /> + <public name="system_primary_900" /> + <public name="system_primary_1000" /> + <!-- Secondary color --> + <public name="system_secondary_0" /> + <public name="system_secondary_50" /> + <public name="system_secondary_100" /> + <public name="system_secondary_200" /> + <public name="system_secondary_300" /> + <public name="system_secondary_400" /> + <public name="system_secondary_500" /> + <public name="system_secondary_600" /> + <public name="system_secondary_700" /> + <public name="system_secondary_800" /> + <public name="system_secondary_900" /> + <public name="system_secondary_1000" /> + <!-- Neutral color --> + <public name="system_neutral_0" /> + <public name="system_neutral_50" /> + <public name="system_neutral_100" /> + <public name="system_neutral_200" /> + <public name="system_neutral_300" /> + <public name="system_neutral_400" /> + <public name="system_neutral_500" /> + <public name="system_neutral_600" /> + <public name="system_neutral_700" /> + <public name="system_neutral_800" /> + <public name="system_neutral_900" /> + <public name="system_neutral_1000" /> </public-group> <public-group type="dimen" first-id="0x01050008"> @@ -3138,6 +3151,8 @@ <public name="config_systemAutomotiveProjection" /> <!-- @hide @SystemApi --> <public name="config_systemShell" /> + <!-- @hide @SystemApi --> + <public name="config_systemContacts" /> </public-group> <public-group type="id" first-id="0x01020055"> diff --git a/core/tests/coretests/src/android/app/people/PeopleManagerTest.java b/core/tests/coretests/src/android/app/people/PeopleManagerTest.java new file mode 100644 index 000000000000..a2afc777a552 --- /dev/null +++ b/core/tests/coretests/src/android/app/people/PeopleManagerTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.people; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.pm.ShortcutInfo; +import android.os.test.TestLooper; +import android.util.Pair; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Map; +import java.util.concurrent.Executor; + +/** + * Tests for {@link android.app.people.PeopleManager.ConversationListener} and relevant APIs. + */ +@RunWith(AndroidJUnit4.class) +public class PeopleManagerTest { + + private static final String CONVERSATION_ID_1 = "12"; + private static final String CONVERSATION_ID_2 = "123"; + + private Context mContext; + + private final TestLooper mTestLooper = new TestLooper(); + + @Mock + private IPeopleManager mService; + private PeopleManager mPeopleManager; + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getContext(); + MockitoAnnotations.initMocks(this); + + mPeopleManager = new PeopleManager(mContext, mService); + } + + @Test + public void testCorrectlyMapsToProxyConversationListener() throws Exception { + PeopleManager.ConversationListener listenerForConversation1 = mock( + PeopleManager.ConversationListener.class); + registerListener(CONVERSATION_ID_1, listenerForConversation1); + PeopleManager.ConversationListener listenerForConversation2 = mock( + PeopleManager.ConversationListener.class); + registerListener(CONVERSATION_ID_2, listenerForConversation2); + + Map<PeopleManager.ConversationListener, Pair<Executor, IConversationListener>> + listenersToProxy = + mPeopleManager.mConversationListeners; + Pair<Executor, IConversationListener> listener = listenersToProxy.get( + listenerForConversation1); + ConversationChannel conversation = getConversation(CONVERSATION_ID_1); + listener.second.onConversationUpdate(getConversation(CONVERSATION_ID_1)); + mTestLooper.dispatchAll(); + + // Only call the associated listener. + verify(listenerForConversation2, never()).onConversationUpdate(any()); + // Should update the listeners mapped to the proxy. + ArgumentCaptor<ConversationChannel> capturedConversation = ArgumentCaptor.forClass( + ConversationChannel.class); + verify(listenerForConversation1, times(1)).onConversationUpdate( + capturedConversation.capture()); + ConversationChannel conversationChannel = capturedConversation.getValue(); + assertEquals(conversationChannel.getShortcutInfo().getId(), CONVERSATION_ID_1); + assertEquals(conversationChannel.getShortcutInfo().getLabel(), + conversation.getShortcutInfo().getLabel()); + } + + private ConversationChannel getConversation(String shortcutId) { + ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, + shortcutId).setLongLabel( + "name").build(); + NotificationChannel notificationChannel = new NotificationChannel("123", + "channel", + NotificationManager.IMPORTANCE_DEFAULT); + return new ConversationChannel(shortcutInfo, 0, + notificationChannel, null, + 123L, false); + } + + private void registerListener(String conversationId, + PeopleManager.ConversationListener listener) { + mPeopleManager.registerConversationListener(mContext.getPackageName(), mContext.getUserId(), + conversationId, listener, + mTestLooper.getNewExecutor()); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java index 8f81ea237a15..7dca0cb92f9d 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java @@ -93,35 +93,62 @@ public class KernelCpuUidUserSysTimeReaderTest { mReader.setThrottle(500); writeToFile(uidLines(mUids, mInitialTimes)); - mReader.readDelta(mCallback); + mReader.readDelta(false, mCallback); assertEquals(6, mCallback.mData.size()); long[][] times1 = increaseTime(mInitialTimes); writeToFile(uidLines(mUids, times1)); mCallback.clear(); - mReader.readDelta(mCallback); + mReader.readDelta(false, mCallback); assertEquals(0, mCallback.mData.size()); + // TODO(b/180473895): Replace sleeps with injected simulated time. SystemClock.sleep(600); long[][] times2 = increaseTime(times1); writeToFile(uidLines(mUids, times2)); mCallback.clear(); - mReader.readDelta(mCallback); + mReader.readDelta(false, mCallback); assertEquals(6, mCallback.mData.size()); long[][] times3 = increaseTime(times2); writeToFile(uidLines(mUids, times3)); mCallback.clear(); - mReader.readDelta(mCallback); + mReader.readDelta(false, mCallback); assertEquals(0, mCallback.mData.size()); + + // Force the delta read, previously skipped increments should now be read + mCallback.clear(); + mReader.readDelta(true, mCallback); + assertEquals(6, mCallback.mData.size()); + + SystemClock.sleep(600); + + long[][] times4 = increaseTime(times3); + writeToFile(uidLines(mUids, times4)); + mCallback.clear(); + mReader.readDelta(true, mCallback); + assertEquals(6, mCallback.mData.size()); + + // Don't force the delta read, throttle should be set from last read. + long[][] times5 = increaseTime(times4); + writeToFile(uidLines(mUids, times5)); + mCallback.clear(); + mReader.readDelta(false, mCallback); + assertEquals(0, mCallback.mData.size()); + + SystemClock.sleep(600); + + mCallback.clear(); + mReader.readDelta(false, mCallback); + assertEquals(6, mCallback.mData.size()); } @Test public void testReadDelta() throws Exception { final long[][] times1 = mInitialTimes; writeToFile(uidLines(mUids, times1)); - mReader.readDelta(mCallback); + mReader.readDelta(false, mCallback); for (int i = 0; i < mUids.length; i++) { mCallback.verify(mUids[i], times1[i]); } @@ -131,7 +158,7 @@ public class KernelCpuUidUserSysTimeReaderTest { // Verify that a second call will only return deltas. final long[][] times2 = increaseTime(times1); writeToFile(uidLines(mUids, times2)); - mReader.readDelta(mCallback); + mReader.readDelta(false, mCallback); for (int i = 0; i < mUids.length; i++) { mCallback.verify(mUids[i], subtract(times2[i], times1[i])); } @@ -139,20 +166,20 @@ public class KernelCpuUidUserSysTimeReaderTest { mCallback.clear(); // Verify that there won't be a callback if the proc file values didn't change. - mReader.readDelta(mCallback); + mReader.readDelta(false, mCallback); mCallback.verifyNoMoreInteractions(); mCallback.clear(); // Verify that calling with a null callback doesn't result in any crashes final long[][] times3 = increaseTime(times2); writeToFile(uidLines(mUids, times3)); - mReader.readDelta(null); + mReader.readDelta(false, null); // Verify that the readDelta call will only return deltas when // the previous call had null callback. final long[][] times4 = increaseTime(times3); writeToFile(uidLines(mUids, times4)); - mReader.readDelta(mCallback); + mReader.readDelta(false, mCallback); for (int i = 0; i < mUids.length; i++) { mCallback.verify(mUids[i], subtract(times4[i], times3[i])); } @@ -165,7 +192,7 @@ public class KernelCpuUidUserSysTimeReaderTest { public void testReadDeltaWrongData() throws Exception { final long[][] times1 = mInitialTimes; writeToFile(uidLines(mUids, times1)); - mReader.readDelta(mCallback); + mReader.readDelta(false, mCallback); for (int i = 0; i < mUids.length; i++) { mCallback.verify(mUids[i], times1[i]); } @@ -176,7 +203,7 @@ public class KernelCpuUidUserSysTimeReaderTest { final long[][] times2 = increaseTime(times1); times2[0][0] = 1000; writeToFile(uidLines(mUids, times2)); - mReader.readDelta(mCallback); + mReader.readDelta(false, mCallback); for (int i = 1; i < mUids.length; i++) { mCallback.verify(mUids[i], subtract(times2[i], times1[i])); } diff --git a/data/etc/com.android.emergency.xml b/data/etc/com.android.emergency.xml index fa92b6da9460..2d6ae2ebfb0a 100644 --- a/data/etc/com.android.emergency.xml +++ b/data/etc/com.android.emergency.xml @@ -21,5 +21,7 @@ <permission name="android.permission.MANAGE_USERS"/> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/> + <!-- Required to update emergency gesture settings --> + <permission name="android.permission.WRITE_SECURE_SETTINGS"/> </privapp-permissions> </permissions> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 84da930aac54..5633de348433 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -259,6 +259,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1834214907": { + "message": "createNonAppWindowAnimations()", + "level": "DEBUG", + "group": "WM_DEBUG_REMOTE_ANIMATIONS", + "at": "com\/android\/server\/wm\/RemoteAnimationController.java" + }, "-1824578273": { "message": "Reporting new frame to %s: %s", "level": "VERBOSE", @@ -811,6 +817,18 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimation.java" }, + "-1153814764": { + "message": "onAnimationCancelled", + "level": "DEBUG", + "group": "WM_DEBUG_REMOTE_ANIMATIONS", + "at": "com\/android\/server\/wm\/NonAppWindowAnimationAdapter.java" + }, + "-1144293044": { + "message": "SURFACE SET FREEZE LAYER: %s", + "level": "INFO", + "group": "WM_SHOW_TRANSACTIONS", + "at": "com\/android\/server\/wm\/WindowStateAnimator.java" + }, "-1142279614": { "message": "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s", "level": "VERBOSE", diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 2b0d7e53b749..c79c12cd3343 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -279,8 +279,8 @@ import javax.security.auth.x500.X500Principal; * } */ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAuthArgs { - - private static final X500Principal DEFAULT_CERT_SUBJECT = new X500Principal("CN=fake"); + private static final X500Principal DEFAULT_CERT_SUBJECT = + new X500Principal("CN=Android Keystore Key"); private static final BigInteger DEFAULT_CERT_SERIAL_NUMBER = new BigInteger("1"); private static final Date DEFAULT_CERT_NOT_BEFORE = new Date(0L); // Jan 1 1970 private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048 @@ -317,6 +317,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private final boolean mUnlockedDeviceRequired; private final boolean mCriticalToDeviceEncryption; private final int mMaxUsageCount; + private final String mAttestKeyAlias; /* * ***NOTE***: All new fields MUST also be added to the following: * ParcelableKeyGenParameterSpec class. @@ -358,7 +359,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu boolean userConfirmationRequired, boolean unlockedDeviceRequired, boolean criticalToDeviceEncryption, - int maxUsageCount) { + int maxUsageCount, + String attestKeyAlias) { if (TextUtils.isEmpty(keyStoreAlias)) { throw new IllegalArgumentException("keyStoreAlias must not be empty"); } @@ -413,6 +415,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUnlockedDeviceRequired = unlockedDeviceRequired; mCriticalToDeviceEncryption = criticalToDeviceEncryption; mMaxUsageCount = maxUsageCount; + mAttestKeyAlias = attestKeyAlias; } /** @@ -869,6 +872,18 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * Returns the alias of the attestation key that will be used to sign the attestation + * certificate of the generated key. Note that an attestation certificate will only be + * generated if an attestation challenge is set. + * + * @see Builder#setAttestKeyAlias(String) + */ + @Nullable + public String getAttestKeyAlias() { + return mAttestKeyAlias; + } + + /** * Builder of {@link KeyGenParameterSpec} instances. */ public final static class Builder { @@ -906,6 +921,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private boolean mUnlockedDeviceRequired = false; private boolean mCriticalToDeviceEncryption = false; private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT; + private String mAttestKeyAlias = null; /** * Creates a new instance of the {@code Builder}. @@ -975,6 +991,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired(); mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption(); mMaxUsageCount = sourceSpec.getMaxUsageCount(); + mAttestKeyAlias = sourceSpec.getAttestKeyAlias(); } /** @@ -1695,6 +1712,28 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * Sets the alias of the attestation key that will be used to sign the attestation + * certificate for the generated key pair, if an attestation challenge is set with {@link + * #setAttestationChallenge}. If an attestKeyAlias is set but no challenge, {@link + * java.security.KeyPairGenerator#initialize} will throw {@link + * java.security.InvalidAlgorithmParameterException}. + * + * <p>If the attestKeyAlias is set to null (the default), Android Keystore will select an + * appropriate system-provided attestation signing key. If not null, the alias must + * reference an Android Keystore Key that was created with {@link + * android.security.keystore.KeyProperties#PURPOSE_ATTEST_KEY}, or key generation will throw + * {@link java.security.InvalidAlgorithmParameterException}. + * + * @param attestKeyAlias the alias of the attestation key to be used to sign the + * attestation certificate. + */ + @NonNull + public Builder setAttestKeyAlias(@Nullable String attestKeyAlias) { + mAttestKeyAlias = attestKeyAlias; + return this; + } + + /** * Builds an instance of {@code KeyGenParameterSpec}. */ @NonNull @@ -1731,7 +1770,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserConfirmationRequired, mUnlockedDeviceRequired, mCriticalToDeviceEncryption, - mMaxUsageCount); + mMaxUsageCount, + mAttestKeyAlias); } } } diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index 293ab05e0b10..7b0fa91380e1 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -70,6 +70,7 @@ public abstract class KeyProperties { PURPOSE_VERIFY, PURPOSE_WRAP_KEY, PURPOSE_AGREE_KEY, + PURPOSE_ATTEST_KEY, }) public @interface PurposeEnum {} @@ -113,6 +114,13 @@ public abstract class KeyProperties { public static final int PURPOSE_AGREE_KEY = 1 << 6; /** + * Purpose of key: Signing attestaions. This purpose is incompatible with all others, meaning + * that when generating a key with PURPOSE_ATTEST_KEY, no other purposes may be specified. In + * addition, PURPOSE_ATTEST_KEY may not be specified for imported keys. + */ + public static final int PURPOSE_ATTEST_KEY = 1 << 7; + + /** * @hide */ public static abstract class Purpose { @@ -132,6 +140,8 @@ public abstract class KeyProperties { return KeymasterDefs.KM_PURPOSE_WRAP; case PURPOSE_AGREE_KEY: return KeymasterDefs.KM_PURPOSE_AGREE_KEY; + case PURPOSE_ATTEST_KEY: + return KeymasterDefs.KM_PURPOSE_ATTEST_KEY; default: throw new IllegalArgumentException("Unknown purpose: " + purpose); } @@ -151,6 +161,8 @@ public abstract class KeyProperties { return PURPOSE_WRAP_KEY; case KeymasterDefs.KM_PURPOSE_AGREE_KEY: return PURPOSE_AGREE_KEY; + case KeymasterDefs.KM_PURPOSE_ATTEST_KEY: + return PURPOSE_ATTEST_KEY; default: throw new IllegalArgumentException("Unknown purpose: " + purpose); } diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index aaa37150c0e9..fe92270ca508 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -588,7 +588,8 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID; private boolean mCriticalToDeviceEncryption = false; private boolean mIsStrongBoxBacked = false; - private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT; + private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT; + private String mAttestKeyAlias = null; /** * Creates a new instance of the {@code Builder}. diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java index 1f2f853b67a8..c20cf01a993e 100644 --- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java @@ -110,6 +110,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { out.writeBoolean(mSpec.isUnlockedDeviceRequired()); out.writeBoolean(mSpec.isCriticalToDeviceEncryption()); out.writeInt(mSpec.getMaxUsageCount()); + out.writeString(mSpec.getAttestKeyAlias()); } private static Date readDateOrNull(Parcel in) { @@ -170,6 +171,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { final boolean unlockedDeviceRequired = in.readBoolean(); final boolean criticalToDeviceEncryption = in.readBoolean(); final int maxUsageCount = in.readInt(); + final String attestKeyAlias = in.readString(); // The KeyGenParameterSpec is intentionally not constructed using a Builder here: // The intention is for this class to break if new parameters are added to the // KeyGenParameterSpec constructor (whereas using a builder would silently drop them). @@ -205,7 +207,8 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { userConfirmationRequired, unlockedDeviceRequired, criticalToDeviceEncryption, - maxUsageCount); + maxUsageCount, + attestKeyAlias); } public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() { diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index 4d27c3454a84..b3bfd6a3a97a 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -20,7 +20,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.security.keymint.KeyParameter; +import android.hardware.security.keymint.KeyPurpose; import android.hardware.security.keymint.SecurityLevel; +import android.hardware.security.keymint.Tag; import android.os.Build; import android.security.KeyPairGeneratorSpec; import android.security.KeyStore; @@ -37,9 +39,11 @@ import android.security.keystore.KeyProperties; import android.security.keystore.KeymasterUtils; import android.security.keystore.SecureKeyImportUnavailableException; import android.security.keystore.StrongBoxUnavailableException; +import android.system.keystore2.Authorization; import android.system.keystore2.Domain; import android.system.keystore2.IKeystoreSecurityLevel; import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyEntryResponse; import android.system.keystore2.KeyMetadata; import android.system.keystore2.ResponseCode; import android.telephony.TelephonyManager; @@ -61,6 +65,7 @@ import java.security.spec.AlgorithmParameterSpec; import java.security.spec.ECGenParameterSpec; import java.security.spec.RSAKeyGenParameterSpec; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -69,6 +74,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; /** * Provides a way to create instances of a KeyPair which will be placed in the @@ -153,6 +159,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private int mKeymasterAlgorithm = -1; private int mKeySizeBits; private SecureRandom mRng; + private KeyDescriptor mAttestKeyDescriptor; private int[] mKeymasterPurposes; private int[] mKeymasterBlockModes; @@ -197,83 +204,9 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato // Legacy/deprecated spec KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params; try { - KeyGenParameterSpec.Builder specBuilder; - String specKeyAlgorithm = legacySpec.getKeyType(); - if (specKeyAlgorithm != null) { - // Spec overrides the generator's default key algorithm - try { - keymasterAlgorithm = - KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( - specKeyAlgorithm); - } catch (IllegalArgumentException e) { - throw new InvalidAlgorithmParameterException( - "Invalid key type in parameters", e); - } - } - switch (keymasterAlgorithm) { - case KeymasterDefs.KM_ALGORITHM_EC: - specBuilder = new KeyGenParameterSpec.Builder( - legacySpec.getKeystoreAlias(), - KeyProperties.PURPOSE_SIGN - | KeyProperties.PURPOSE_VERIFY); - // Authorized to be used with any digest (including no digest). - // MD5 was never offered for Android Keystore for ECDSA. - specBuilder.setDigests( - KeyProperties.DIGEST_NONE, - KeyProperties.DIGEST_SHA1, - KeyProperties.DIGEST_SHA224, - KeyProperties.DIGEST_SHA256, - KeyProperties.DIGEST_SHA384, - KeyProperties.DIGEST_SHA512); - break; - case KeymasterDefs.KM_ALGORITHM_RSA: - specBuilder = new KeyGenParameterSpec.Builder( - legacySpec.getKeystoreAlias(), - KeyProperties.PURPOSE_ENCRYPT - | KeyProperties.PURPOSE_DECRYPT - | KeyProperties.PURPOSE_SIGN - | KeyProperties.PURPOSE_VERIFY); - // Authorized to be used with any digest (including no digest). - specBuilder.setDigests( - KeyProperties.DIGEST_NONE, - KeyProperties.DIGEST_MD5, - KeyProperties.DIGEST_SHA1, - KeyProperties.DIGEST_SHA224, - KeyProperties.DIGEST_SHA256, - KeyProperties.DIGEST_SHA384, - KeyProperties.DIGEST_SHA512); - // Authorized to be used with any encryption and signature padding - // schemes (including no padding). - specBuilder.setEncryptionPaddings( - KeyProperties.ENCRYPTION_PADDING_NONE, - KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1, - KeyProperties.ENCRYPTION_PADDING_RSA_OAEP); - specBuilder.setSignaturePaddings( - KeyProperties.SIGNATURE_PADDING_RSA_PKCS1, - KeyProperties.SIGNATURE_PADDING_RSA_PSS); - // Disable randomized encryption requirement to support encryption - // padding NONE above. - specBuilder.setRandomizedEncryptionRequired(false); - break; - default: - throw new ProviderException( - "Unsupported algorithm: " + mKeymasterAlgorithm); - } - - if (legacySpec.getKeySize() != -1) { - specBuilder.setKeySize(legacySpec.getKeySize()); - } - if (legacySpec.getAlgorithmParameterSpec() != null) { - specBuilder.setAlgorithmParameterSpec( - legacySpec.getAlgorithmParameterSpec()); - } - specBuilder.setCertificateSubject(legacySpec.getSubjectDN()); - specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber()); - specBuilder.setCertificateNotBefore(legacySpec.getStartDate()); - specBuilder.setCertificateNotAfter(legacySpec.getEndDate()); - specBuilder.setUserAuthenticationRequired(false); - - spec = specBuilder.build(); + keymasterAlgorithm = getKeymasterAlgorithmFromLegacy(keymasterAlgorithm, + legacySpec); + spec = buildKeyGenParameterSpecFromLegacy(legacySpec, keymasterAlgorithm); } catch (NullPointerException | IllegalArgumentException e) { throw new InvalidAlgorithmParameterException(e); } @@ -342,6 +275,10 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mJcaKeyAlgorithm = jcaKeyAlgorithm; mRng = random; mKeyStore = KeyStore2.getInstance(); + + mAttestKeyDescriptor = buildAndCheckAttestKeyDescriptor(spec); + checkAttestKeyPurpose(spec); + success = true; } finally { if (!success) { @@ -350,6 +287,156 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } } + private void checkAttestKeyPurpose(KeyGenParameterSpec spec) + throws InvalidAlgorithmParameterException { + if ((spec.getPurposes() & KeyProperties.PURPOSE_ATTEST_KEY) != 0 + && spec.getPurposes() != KeyProperties.PURPOSE_ATTEST_KEY) { + throw new InvalidAlgorithmParameterException( + "PURPOSE_ATTEST_KEY may not be specified with any other purposes"); + } + } + + private KeyDescriptor buildAndCheckAttestKeyDescriptor(KeyGenParameterSpec spec) + throws InvalidAlgorithmParameterException { + if (spec.getAttestKeyAlias() != null) { + KeyDescriptor attestKeyDescriptor = new KeyDescriptor(); + attestKeyDescriptor.domain = Domain.APP; + attestKeyDescriptor.alias = spec.getAttestKeyAlias(); + try { + KeyEntryResponse attestKey = mKeyStore.getKeyEntry(attestKeyDescriptor); + checkAttestKeyChallenge(spec); + checkAttestKeyPurpose(attestKey.metadata.authorizations); + checkAttestKeySecurityLevel(spec, attestKey); + } catch (KeyStoreException e) { + throw new InvalidAlgorithmParameterException("Invalid attestKeyAlias", e); + } + return attestKeyDescriptor; + } + return null; + } + + private void checkAttestKeyChallenge(KeyGenParameterSpec spec) + throws InvalidAlgorithmParameterException { + if (spec.getAttestationChallenge() == null) { + throw new InvalidAlgorithmParameterException( + "AttestKey specified but no attestation challenge provided"); + } + } + + private void checkAttestKeyPurpose(Authorization[] keyAuths) + throws InvalidAlgorithmParameterException { + Predicate<Authorization> isAttestKeyPurpose = x -> x.keyParameter.tag == Tag.PURPOSE + && x.keyParameter.value.getKeyPurpose() == KeyPurpose.ATTEST_KEY; + + if (Arrays.stream(keyAuths).noneMatch(isAttestKeyPurpose)) { + throw new InvalidAlgorithmParameterException( + ("Invalid attestKey, does not have PURPOSE_ATTEST_KEY")); + } + } + + private void checkAttestKeySecurityLevel(KeyGenParameterSpec spec, KeyEntryResponse key) + throws InvalidAlgorithmParameterException { + boolean attestKeyInStrongBox = key.metadata.keySecurityLevel == SecurityLevel.STRONGBOX; + if (spec.isStrongBoxBacked() != attestKeyInStrongBox) { + if (attestKeyInStrongBox) { + throw new InvalidAlgorithmParameterException( + "Invalid security level: Cannot sign non-StrongBox key with " + + "StrongBox attestKey"); + + } else { + throw new InvalidAlgorithmParameterException( + "Invalid security level: Cannot sign StrongBox key with " + + "non-StrongBox attestKey"); + } + } + } + + private int getKeymasterAlgorithmFromLegacy(int keymasterAlgorithm, + KeyPairGeneratorSpec legacySpec) throws InvalidAlgorithmParameterException { + String specKeyAlgorithm = legacySpec.getKeyType(); + if (specKeyAlgorithm != null) { + // Spec overrides the generator's default key algorithm + try { + keymasterAlgorithm = + KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( + specKeyAlgorithm); + } catch (IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException( + "Invalid key type in parameters", e); + } + } + return keymasterAlgorithm; + } + + private KeyGenParameterSpec buildKeyGenParameterSpecFromLegacy(KeyPairGeneratorSpec legacySpec, + int keymasterAlgorithm) { + KeyGenParameterSpec.Builder specBuilder; + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + // MD5 was never offered for Android Keystore for ECDSA. + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + break; + case KeymasterDefs.KM_ALGORITHM_RSA: + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_ENCRYPT + | KeyProperties.PURPOSE_DECRYPT + | KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + // Authorized to be used with any encryption and signature padding + // schemes (including no padding). + specBuilder.setEncryptionPaddings( + KeyProperties.ENCRYPTION_PADDING_NONE, + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1, + KeyProperties.ENCRYPTION_PADDING_RSA_OAEP); + specBuilder.setSignaturePaddings( + KeyProperties.SIGNATURE_PADDING_RSA_PKCS1, + KeyProperties.SIGNATURE_PADDING_RSA_PSS); + // Disable randomized encryption requirement to support encryption + // padding NONE above. + specBuilder.setRandomizedEncryptionRequired(false); + break; + default: + throw new ProviderException( + "Unsupported algorithm: " + mKeymasterAlgorithm); + } + + if (legacySpec.getKeySize() != -1) { + specBuilder.setKeySize(legacySpec.getKeySize()); + } + if (legacySpec.getAlgorithmParameterSpec() != null) { + specBuilder.setAlgorithmParameterSpec( + legacySpec.getAlgorithmParameterSpec()); + } + specBuilder.setCertificateSubject(legacySpec.getSubjectDN()); + specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber()); + specBuilder.setCertificateNotBefore(legacySpec.getStartDate()); + specBuilder.setCertificateNotAfter(legacySpec.getEndDate()); + specBuilder.setUserAuthenticationRequired(false); + + return specBuilder.build(); + } + private void resetAll() { mEntryAlias = null; mEntryUid = KeyProperties.NAMESPACE_APPLICATION; @@ -464,7 +551,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato try { KeyStoreSecurityLevel iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel); - KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, null, + KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, mAttestKeyDescriptor, constructKeyGenerationArguments(), flags, additionalEntropy); AndroidKeyStorePublicKey publicKey = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java index 73371e7eff20..56fe126f507e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java @@ -75,7 +75,7 @@ public class StackAnimationController extends */ public static final int SPRING_TO_TOUCH_STIFFNESS = 12000; public static final float IME_ANIMATION_STIFFNESS = SpringForce.STIFFNESS_LOW; - private static final int CHAIN_STIFFNESS = 600; + private static final int CHAIN_STIFFNESS = 800; public static final float DEFAULT_BOUNCINESS = 0.9f; private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfig = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 71331dfcef44..843e27f1b651 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -978,7 +978,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, finishResizeForMenu(destinationBounds); } - private void finishResizeForMenu(Rect destinationBounds) { + /** Moves the PiP menu to the destination bounds. */ + public void finishResizeForMenu(Rect destinationBounds) { mPipMenuController.movePipMenu(null, null, destinationBounds); mPipMenuController.updateMenuBounds(destinationBounds); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index c3970e33d736..4a2a032d8d1c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -552,6 +552,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb // mTouchHandler would rely on the bounds populated from mPipTaskOrganizer mPipTaskOrganizer.onMovementBoundsChanged(outBounds, fromRotation, fromImeAdjustment, fromShelfAdjustment, wct); + mPipTaskOrganizer.finishResizeForMenu(outBounds); mTouchHandler.onMovementBoundsChanged(mTmpInsetBounds, mPipBoundsState.getNormalBounds(), outBounds, fromImeAdjustment, fromShelfAdjustment, rotation); } diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS index 2c6c7b358e3b..d80699de8a2d 100644 --- a/libs/WindowManager/Shell/tests/OWNERS +++ b/libs/WindowManager/Shell/tests/OWNERS @@ -1,2 +1,3 @@ +# Bug component: 909476 # includes OWNERS from parent directories natanieljr@google.com diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 385d465619c4..678b0ad3924d 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -445,6 +445,7 @@ cc_defaults { "renderthread/TimeLord.cpp", "hwui/AnimatedImageDrawable.cpp", "hwui/Bitmap.cpp", + "hwui/BlurDrawLooper.cpp", "hwui/Canvas.cpp", "hwui/ImageDecoder.cpp", "hwui/MinikinSkia.cpp", diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp index ce9b2889cd9e..5d3f6f2f28c9 100644 --- a/libs/hwui/FrameInfo.cpp +++ b/libs/hwui/FrameInfo.cpp @@ -40,6 +40,7 @@ const std::array<std::string, static_cast<int>(FrameInfoIndex::NumIndexes)> Fram "DequeueBufferDuration", "QueueBufferDuration", "GpuCompleted", + "SwapBuffersCompleted" }; static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 20, diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 8fddf713f1fa..1fddac4cd05d 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -463,9 +463,7 @@ void SkiaCanvas::drawPoints(const float* points, int count, const Paint& paint, } void SkiaCanvas::drawPoint(float x, float y, const Paint& paint) { - apply_looper(&paint, [&](const SkPaint& p) { - mCanvas->drawPoint(x, y, p); - }); + apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawPoint(x, y, p); }); } void SkiaCanvas::drawPoints(const float* points, int count, const Paint& paint) { @@ -493,9 +491,7 @@ void SkiaCanvas::drawRect(float left, float top, float right, float bottom, cons void SkiaCanvas::drawRegion(const SkRegion& region, const Paint& paint) { if (CC_UNLIKELY(paint.nothingToDraw())) return; - apply_looper(&paint, [&](const SkPaint& p) { - mCanvas->drawRegion(region, p); - }); + apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawRegion(region, p); }); } void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, @@ -509,24 +505,18 @@ void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom, void SkiaCanvas::drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner, const Paint& paint) { - apply_looper(&paint, [&](const SkPaint& p) { - mCanvas->drawDRRect(outer, inner, p); - }); + apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawDRRect(outer, inner, p); }); } void SkiaCanvas::drawCircle(float x, float y, float radius, const Paint& paint) { if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return; - apply_looper(&paint, [&](const SkPaint& p) { - mCanvas->drawCircle(x, y, radius, p); - }); + apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawCircle(x, y, radius, p); }); } void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const Paint& paint) { if (CC_UNLIKELY(paint.nothingToDraw())) return; SkRect oval = SkRect::MakeLTRB(left, top, right, bottom); - apply_looper(&paint, [&](const SkPaint& p) { - mCanvas->drawOval(oval, p); - }); + apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawOval(oval, p); }); } void SkiaCanvas::drawArc(float left, float top, float right, float bottom, float startAngle, @@ -547,9 +537,7 @@ void SkiaCanvas::drawPath(const SkPath& path, const Paint& paint) { if (CC_UNLIKELY(path.isEmpty() && (!path.isInverseFillType()))) { return; } - apply_looper(&paint, [&](const SkPaint& p) { - mCanvas->drawPath(path, p); - }); + apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawPath(path, p); }); } void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, const Paint& paint) { diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index 155f6df7b703..eac3f2217bd8 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -208,29 +208,21 @@ protected: */ PaintCoW&& filterPaint(PaintCoW&& paint) const; + // proc(const SkPaint& modifiedPaint) template <typename Proc> void apply_looper(const Paint* paint, Proc proc) { SkPaint skp; - SkDrawLooper* looper = nullptr; + BlurDrawLooper* looper = nullptr; if (paint) { skp = *filterPaint(paint); looper = paint->getLooper(); } if (looper) { - SkSTArenaAlloc<256> alloc; - SkDrawLooper::Context* ctx = looper->makeContext(&alloc); - if (ctx) { - SkDrawLooper::Context::Info info; - for (;;) { - SkPaint p = skp; - if (!ctx->next(&info, &p)) { - break; - } - mCanvas->save(); - mCanvas->translate(info.fTranslate.fX, info.fTranslate.fY); - proc(p); - mCanvas->restore(); - } - } + looper->apply(skp, [&](SkPoint offset, const SkPaint& modifiedPaint) { + mCanvas->save(); + mCanvas->translate(offset.fX, offset.fY); + proc(modifiedPaint); + mCanvas->restore(); + }); } else { proc(skp); } diff --git a/libs/hwui/hwui/BlurDrawLooper.cpp b/libs/hwui/hwui/BlurDrawLooper.cpp new file mode 100644 index 000000000000..27a038d4598e --- /dev/null +++ b/libs/hwui/hwui/BlurDrawLooper.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "BlurDrawLooper.h" +#include <SkMaskFilter.h> + +namespace android { + +BlurDrawLooper::BlurDrawLooper(SkColor4f color, float blurSigma, SkPoint offset) + : mColor(color), mBlurSigma(blurSigma), mOffset(offset) {} + +BlurDrawLooper::~BlurDrawLooper() = default; + +SkPoint BlurDrawLooper::apply(SkPaint* paint) const { + paint->setColor(mColor); + if (mBlurSigma > 0) { + paint->setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, mBlurSigma, true)); + } + return mOffset; +} + +sk_sp<BlurDrawLooper> BlurDrawLooper::Make(SkColor4f color, SkColorSpace* cs, float blurSigma, + SkPoint offset) { + if (cs) { + SkPaint tmp; + tmp.setColor(color, cs); // converts color to sRGB + color = tmp.getColor4f(); + } + return sk_sp<BlurDrawLooper>(new BlurDrawLooper(color, blurSigma, offset)); +} + +} // namespace android diff --git a/libs/hwui/hwui/BlurDrawLooper.h b/libs/hwui/hwui/BlurDrawLooper.h new file mode 100644 index 000000000000..7e6786f7dfbc --- /dev/null +++ b/libs/hwui/hwui/BlurDrawLooper.h @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#ifndef ANDROID_GRAPHICS_BLURDRAWLOOPER_H_ +#define ANDROID_GRAPHICS_BLURDRAWLOOPER_H_ + +#include <SkPaint.h> +#include <SkRefCnt.h> + +class SkColorSpace; + +namespace android { + +class BlurDrawLooper : public SkRefCnt { +public: + static sk_sp<BlurDrawLooper> Make(SkColor4f, SkColorSpace*, float blurSigma, SkPoint offset); + + ~BlurDrawLooper() override; + + // proc(SkPoint offset, const SkPaint& modifiedPaint) + template <typename DrawProc> + void apply(const SkPaint& paint, DrawProc proc) const { + SkPaint p(paint); + proc(this->apply(&p), p); // draw the shadow + proc({0, 0}, paint); // draw the original (on top) + } + +private: + const SkColor4f mColor; + const float mBlurSigma; + const SkPoint mOffset; + + SkPoint apply(SkPaint* paint) const; + + BlurDrawLooper(SkColor4f, float, SkPoint); +}; + +} // namespace android + +#endif // ANDROID_GRAPHICS_BLURDRAWLOOPER_H_ diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index 05bae5c9f778..d9c9eeed03e9 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -17,11 +17,11 @@ #ifndef ANDROID_GRAPHICS_PAINT_H_ #define ANDROID_GRAPHICS_PAINT_H_ +#include "BlurDrawLooper.h" #include "Typeface.h" #include <cutils/compiler.h> -#include <SkDrawLooper.h> #include <SkFont.h> #include <SkPaint.h> #include <string> @@ -59,8 +59,8 @@ public: SkFont& getSkFont() { return mFont; } const SkFont& getSkFont() const { return mFont; } - SkDrawLooper* getLooper() const { return mLooper.get(); } - void setLooper(sk_sp<SkDrawLooper> looper) { mLooper = std::move(looper); } + BlurDrawLooper* getLooper() const { return mLooper.get(); } + void setLooper(sk_sp<BlurDrawLooper> looper) { mLooper = std::move(looper); } // These shadow the methods on SkPaint, but we need to so we can keep related // attributes in-sync. @@ -155,7 +155,7 @@ public: private: SkFont mFont; - sk_sp<SkDrawLooper> mLooper; + sk_sp<BlurDrawLooper> mLooper; float mLetterSpacing = 0; float mWordSpacing = 0; diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index 3c86b28262b0..bcec0fa8a1cc 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -25,7 +25,6 @@ #include <nativehelper/ScopedUtfChars.h> #include <nativehelper/ScopedPrimitiveArray.h> -#include "SkBlurDrawLooper.h" #include "SkColorFilter.h" #include "SkFont.h" #include "SkFontMetrics.h" @@ -39,6 +38,7 @@ #include "unicode/ushape.h" #include "utils/Blur.h" +#include <hwui/BlurDrawLooper.h> #include <hwui/MinikinSkia.h> #include <hwui/MinikinUtils.h> #include <hwui/Paint.h> @@ -964,13 +964,13 @@ namespace PaintGlue { } else { SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius); - paint->setLooper(SkBlurDrawLooper::Make(color, cs.get(), sigma, dx, dy)); + paint->setLooper(BlurDrawLooper::Make(color, cs.get(), sigma, {dx, dy})); } } static jboolean hasShadowLayer(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { Paint* paint = reinterpret_cast<Paint*>(paintHandle); - return paint->getLooper() && paint->getLooper()->asABlurShadow(nullptr); + return paint->getLooper() != nullptr; } static jboolean equalsForTextMeasurement(CRITICAL_JNI_PARAMS_COMMA jlong lPaint, jlong rPaint) { diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index ee7c4d8bb54a..b2884023a83d 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -194,28 +194,20 @@ SkiaCanvas::PaintCoW&& SkiaRecordingCanvas::filterBitmap(PaintCoW&& paint) { return filterPaint(std::move(paint)); } -static SkDrawLooper* get_looper(const Paint* paint) { +static BlurDrawLooper* get_looper(const Paint* paint) { return paint ? paint->getLooper() : nullptr; } template <typename Proc> -void applyLooper(SkDrawLooper* looper, const SkPaint* paint, Proc proc) { +void applyLooper(BlurDrawLooper* looper, const SkPaint* paint, Proc proc) { if (looper) { - SkSTArenaAlloc<256> alloc; - SkDrawLooper::Context* ctx = looper->makeContext(&alloc); - if (ctx) { - SkDrawLooper::Context::Info info; - for (;;) { - SkPaint p; - if (paint) { - p = *paint; - } - if (!ctx->next(&info, &p)) { - break; - } - proc(info.fTranslate.fX, info.fTranslate.fY, &p); - } + SkPaint p; + if (paint) { + p = *paint; } + looper->apply(p, [&](SkPoint offset, const SkPaint& modifiedPaint) { + proc(offset.fX, offset.fY, &modifiedPaint); + }); } else { proc(0, 0, paint); } diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp index 7951537e1525..a1ba70a22581 100644 --- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp +++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp @@ -16,7 +16,6 @@ #include "tests/common/TestUtils.h" -#include <SkBlurDrawLooper.h> #include <SkColorMatrixFilter.h> #include <SkColorSpace.h> #include <SkImagePriv.h> @@ -85,15 +84,3 @@ TEST(SkiaBehavior, srgbColorSpaceIsSingleton) { ASSERT_EQ(sRGB1.get(), sRGB2.get()); } -TEST(SkiaBehavior, blurDrawLooper) { - sk_sp<SkDrawLooper> looper = SkBlurDrawLooper::Make(SK_ColorRED, 5.0f, 3.0f, 4.0f); - - SkDrawLooper::BlurShadowRec blur; - bool success = looper->asABlurShadow(&blur); - ASSERT_TRUE(success); - - ASSERT_EQ(SK_ColorRED, blur.fColor); - ASSERT_EQ(5.0f, blur.fSigma); - ASSERT_EQ(3.0f, blur.fOffset.fX); - ASSERT_EQ(4.0f, blur.fOffset.fY); -} diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp index f77ca2a8c06c..dae3c9435712 100644 --- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp +++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp @@ -17,7 +17,6 @@ #include "tests/common/TestUtils.h" #include <hwui/Paint.h> -#include <SkBlurDrawLooper.h> #include <SkCanvasStateUtils.h> #include <SkPicture.h> #include <SkPictureRecorder.h> @@ -37,7 +36,7 @@ TEST(SkiaCanvas, drawShadowLayer) { // it is transparent to ensure that we still draw the rect since it has a looper paint.setColor(SK_ColorTRANSPARENT); // this is how view's shadow layers are implemented - paint.setLooper(SkBlurDrawLooper::Make(0xF0000000, 6.0f, 0, 10)); + paint.setLooper(BlurDrawLooper::Make({0, 0, 0, 240.0f / 255}, nullptr, 6.0f, {0, 10})); canvas.drawRect(3, 3, 7, 7, paint); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE); diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h index e2fdf2fcb5a5..09c6a4fdf50d 100644 --- a/libs/hwui/utils/PaintUtils.h +++ b/libs/hwui/utils/PaintUtils.h @@ -20,7 +20,6 @@ #include <utils/Blur.h> #include <SkColorFilter.h> -#include <SkDrawLooper.h> #include <SkPaint.h> #include <SkShader.h> diff --git a/media/OWNERS b/media/OWNERS index e74149019b11..abfc8bfa976e 100644 --- a/media/OWNERS +++ b/media/OWNERS @@ -26,3 +26,7 @@ olly@google.com # SEO sungsoo@google.com + +# SEA/KIR/BVE +jtinker@google.com +robertshih@google.com diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index 49f9d6612341..adb8a54c0167 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -38,6 +38,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; +import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; @@ -2493,4 +2494,80 @@ public final class MediaDrm implements AutoCloseable { return mPlaybackId; } } + + /** + * Returns recent {@link LogMessage LogMessages} associated with this {@link MediaDrm} + * instance. + */ + @NonNull + public native List<LogMessage> getLogMessages(); + + /** + * A {@link LogMessage} records an event in the {@link MediaDrm} framework + * or vendor plugin. + */ + public static class LogMessage { + + /** + * Timing of the recorded event measured in milliseconds since the Epoch, + * 1970-01-01 00:00:00 +0000 (UTC). + */ + public final long timestampMillis; + + /** + * Priority of the recorded event. + * <p> + * Possible priority constants are defined in {@link Log}, e.g.: + * <ul> + * <li>{@link Log#ASSERT}</li> + * <li>{@link Log#ERROR}</li> + * <li>{@link Log#WARN}</li> + * <li>{@link Log#INFO}</li> + * <li>{@link Log#DEBUG}</li> + * <li>{@link Log#VERBOSE}</li> + * </ul> + */ + @Log.Level + public final int priority; + + /** + * Description of the recorded event. + */ + @NonNull + public final String message; + + private LogMessage(long timestampMillis, int priority, String message) { + this.timestampMillis = timestampMillis; + if (priority < Log.VERBOSE || priority > Log.ASSERT) { + throw new IllegalArgumentException("invalid log priority " + priority); + } + this.priority = priority; + this.message = message; + } + + private char logPriorityChar() { + switch (priority) { + case Log.VERBOSE: + return 'V'; + case Log.DEBUG: + return 'D'; + case Log.INFO: + return 'I'; + case Log.WARN: + return 'W'; + case Log.ERROR: + return 'E'; + case Log.ASSERT: + return 'F'; + default: + } + return 'U'; + } + + @Override + public String toString() { + return String.format("LogMessage{%s %c %s}", + Instant.ofEpochMilli(timestampMillis), logPriorityChar(), message); + } + } } diff --git a/media/java/android/media/metrics/NetworkEvent.java b/media/java/android/media/metrics/NetworkEvent.java index a330bc0b66df..029edeb93374 100644 --- a/media/java/android/media/metrics/NetworkEvent.java +++ b/media/java/android/media/metrics/NetworkEvent.java @@ -17,6 +17,7 @@ package android.media.metrics; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; @@ -27,22 +28,30 @@ import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** - * Playback network event. - * @hide + * Media network event. */ -public final class NetworkEvent implements Parcelable { +public final class NetworkEvent extends Event implements Parcelable { + /** Network type is not specified. Default type. */ public static final int NETWORK_TYPE_NONE = 0; + /** Other network type */ public static final int NETWORK_TYPE_OTHER = 1; + /** Wi-Fi network */ public static final int NETWORK_TYPE_WIFI = 2; + /** Ethernet network */ public static final int NETWORK_TYPE_ETHERNET = 3; + /** 2G network */ public static final int NETWORK_TYPE_2G = 4; + /** 3G network */ public static final int NETWORK_TYPE_3G = 5; + /** 4G network */ public static final int NETWORK_TYPE_4G = 6; + /** 5G NSA network */ public static final int NETWORK_TYPE_5G_NSA = 7; + /** 5G SA network */ public static final int NETWORK_TYPE_5G_SA = 8; - private final int mType; - private final long mTimeSincePlaybackCreatedMillis; + private final int mNetworkType; + private final long mTimeSinceCreatedMillis; /** @hide */ @IntDef(prefix = "NETWORK_TYPE_", value = { @@ -61,6 +70,7 @@ public final class NetworkEvent implements Parcelable { /** * Network type to string. + * @hide */ public static String networkTypeToString(@NetworkType int value) { switch (value) { @@ -92,25 +102,34 @@ public final class NetworkEvent implements Parcelable { * * @hide */ - public NetworkEvent(@NetworkType int type, long timeSincePlaybackCreatedMillis) { - this.mType = type; - this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis; + public NetworkEvent(@NetworkType int type, long timeSinceCreatedMillis) { + this.mNetworkType = type; + this.mTimeSinceCreatedMillis = timeSinceCreatedMillis; } + /** + * Gets network type. + */ @NetworkType - public int getType() { - return mType; + public int getNetworkType() { + return mNetworkType; } - public long getTimeSincePlaybackCreatedMillis() { - return mTimeSincePlaybackCreatedMillis; + /** + * Gets timestamp since the creation in milliseconds. + * @return the timestamp since the creation in milliseconds, or -1 if unknown. + */ + @Override + @IntRange(from = -1) + public long getTimeSinceCreatedMillis() { + return mTimeSinceCreatedMillis; } @Override public String toString() { return "NetworkEvent { " - + "type = " + mType + ", " - + "timeSincePlaybackCreatedMillis = " + mTimeSincePlaybackCreatedMillis + + "networkType = " + mNetworkType + ", " + + "timeSinceCreatedMillis = " + mTimeSinceCreatedMillis + " }"; } @@ -119,19 +138,19 @@ public final class NetworkEvent implements Parcelable { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; NetworkEvent that = (NetworkEvent) o; - return mType == that.mType - && mTimeSincePlaybackCreatedMillis == that.mTimeSincePlaybackCreatedMillis; + return mNetworkType == that.mNetworkType + && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis; } @Override public int hashCode() { - return Objects.hash(mType, mTimeSincePlaybackCreatedMillis); + return Objects.hash(mNetworkType, mTimeSinceCreatedMillis); } @Override public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { - dest.writeInt(mType); - dest.writeLong(mTimeSincePlaybackCreatedMillis); + dest.writeInt(mNetworkType); + dest.writeLong(mTimeSinceCreatedMillis); } @Override @@ -142,12 +161,15 @@ public final class NetworkEvent implements Parcelable { /** @hide */ /* package-private */ NetworkEvent(@NonNull android.os.Parcel in) { int type = in.readInt(); - long timeSincePlaybackCreatedMillis = in.readLong(); + long timeSinceCreatedMillis = in.readLong(); - this.mType = type; - this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis; + this.mNetworkType = type; + this.mTimeSinceCreatedMillis = timeSinceCreatedMillis; } + /** + * Used to read a NetworkEvent from a Parcel. + */ public static final @NonNull Parcelable.Creator<NetworkEvent> CREATOR = new Parcelable.Creator<NetworkEvent>() { @Override @@ -165,13 +187,11 @@ public final class NetworkEvent implements Parcelable { * A builder for {@link NetworkEvent} */ public static final class Builder { - private int mType; - private long mTimeSincePlaybackCreatedMillis; + private int mNetworkType = NETWORK_TYPE_NONE; + private long mTimeSinceCreatedMillis = -1; /** * Creates a new Builder. - * - * @hide */ public Builder() { } @@ -179,24 +199,24 @@ public final class NetworkEvent implements Parcelable { /** * Sets network type. */ - public @NonNull Builder setType(@NetworkType int value) { - mType = value; + public @NonNull Builder setNetworkType(@NetworkType int value) { + mNetworkType = value; return this; } /** * Sets timestamp since the creation in milliseconds. + * @param value the timestamp since the creation in milliseconds. + * -1 indicates the value is unknown. */ - public @NonNull Builder setTimeSincePlaybackCreatedMillis(long value) { - mTimeSincePlaybackCreatedMillis = value; + public @NonNull Builder setTimeSinceCreatedMillis(@IntRange(from = -1) long value) { + mTimeSinceCreatedMillis = value; return this; } /** Builds the instance. */ public @NonNull NetworkEvent build() { - NetworkEvent o = new NetworkEvent( - mType, - mTimeSincePlaybackCreatedMillis); + NetworkEvent o = new NetworkEvent(mNetworkType, mTimeSinceCreatedMillis); return o; } } diff --git a/media/java/android/media/metrics/PlaybackComponent.java b/media/java/android/media/metrics/PlaybackComponent.java index 94e55b4843c1..1cadf3be38ee 100644 --- a/media/java/android/media/metrics/PlaybackComponent.java +++ b/media/java/android/media/metrics/PlaybackComponent.java @@ -17,13 +17,10 @@ package android.media.metrics; import android.annotation.NonNull; -import android.annotation.TestApi; /** * Interface for playback related components used by playback metrics. - * @hide */ -@TestApi public interface PlaybackComponent { /** diff --git a/media/java/android/media/metrics/PlaybackMetrics.java b/media/java/android/media/metrics/PlaybackMetrics.java index 070b4e4aa14b..4aa61662ba52 100644 --- a/media/java/android/media/metrics/PlaybackMetrics.java +++ b/media/java/android/media/metrics/PlaybackMetrics.java @@ -17,6 +17,7 @@ package android.media.metrics; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; @@ -33,35 +34,57 @@ import java.util.Objects; /** * This class is used to store playback data. - * @hide */ public final class PlaybackMetrics implements Parcelable { - // TODO(b/177209128): JavaDoc for the constants. + /** Unknown stream source. */ public static final int STREAM_SOURCE_UNKNOWN = 0; + /** Stream from network. */ public static final int STREAM_SOURCE_NETWORK = 1; + /** Stream from device. */ public static final int STREAM_SOURCE_DEVICE = 2; + /** Stream from more than one sources. */ public static final int STREAM_SOURCE_MIXED = 3; + /** Unknown stream type. */ public static final int STREAM_TYPE_UNKNOWN = 0; + /** Other stream type. */ public static final int STREAM_TYPE_OTHER = 1; + /** Progressive stream type. */ public static final int STREAM_TYPE_PROGRESSIVE = 2; + /** DASH (Dynamic Adaptive Streaming over HTTP) stream type. */ public static final int STREAM_TYPE_DASH = 3; + /** HLS (HTTP Live Streaming) stream type. */ public static final int STREAM_TYPE_HLS = 4; + /** SS (HTTP Smooth Streaming) stream type. */ public static final int STREAM_TYPE_SS = 5; + /** VOD (Video on Demand) playback type. */ public static final int PLAYBACK_TYPE_VOD = 0; + /** Live playback type. */ public static final int PLAYBACK_TYPE_LIVE = 1; + /** Other playback type. */ public static final int PLAYBACK_TYPE_OTHER = 2; + /** DRM is not used. */ public static final int DRM_TYPE_NONE = 0; + /** Other DRM type. */ public static final int DRM_TYPE_OTHER = 1; + /** Play ready DRM type. */ public static final int DRM_TYPE_PLAY_READY = 2; + /** Widevine L1 DRM type. */ public static final int DRM_TYPE_WIDEVINE_L1 = 3; + /** Widevine L3 DRM type. */ public static final int DRM_TYPE_WIDEVINE_L3 = 4; - // TODO: add DRM_TYPE_CLEARKEY + /** Widevine L3 fallback DRM type. */ + public static final int DRM_TYPE_WV_L3_FALLBACK = 5; + /** Clear key DRM type. */ + public static final int DRM_TYPE_CLEARKEY = 6; + /** Main contents. */ public static final int CONTENT_TYPE_MAIN = 0; + /** Advertisement contents. */ public static final int CONTENT_TYPE_AD = 1; + /** Other contents. */ public static final int CONTENT_TYPE_OTHER = 2; @@ -102,7 +125,9 @@ public final class PlaybackMetrics implements Parcelable { DRM_TYPE_OTHER, DRM_TYPE_PLAY_READY, DRM_TYPE_WIDEVINE_L1, - DRM_TYPE_WIDEVINE_L3 + DRM_TYPE_WIDEVINE_L3, + DRM_TYPE_WV_L3_FALLBACK, + DRM_TYPE_CLEARKEY }) @Retention(RetentionPolicy.SOURCE) public @interface DrmType {} @@ -173,6 +198,11 @@ public final class PlaybackMetrics implements Parcelable { this.mNetworkTransferDurationMillis = networkTransferDurationMillis; } + /** + * Gets the media duration in milliseconds. + * @return the media duration in milliseconds, or -1 if unknown. + */ + @IntRange(from = -1) public long getMediaDurationMillis() { return mMediaDurationMillis; } @@ -241,28 +271,36 @@ public final class PlaybackMetrics implements Parcelable { /** * Gets video frames played. + * @return the video frames played, or -1 if unknown. */ + @IntRange(from = -1, to = Integer.MAX_VALUE) public int getVideoFramesPlayed() { return mVideoFramesPlayed; } /** * Gets video frames dropped. + * @return the video frames dropped, or -1 if unknown. */ + @IntRange(from = -1, to = Integer.MAX_VALUE) public int getVideoFramesDropped() { return mVideoFramesDropped; } /** * Gets audio underrun count. + * @return the audio underrun count, or -1 if unknown. */ + @IntRange(from = -1, to = Integer.MAX_VALUE) public int getAudioUnderrunCount() { return mAudioUnderrunCount; } /** * Gets number of network bytes read. + * @return the number of network bytes read, or -1 if unknown. */ + @IntRange(from = -1) public long getNetworkBytesRead() { return mNetworkBytesRead; } @@ -270,6 +308,7 @@ public final class PlaybackMetrics implements Parcelable { /** * Gets number of local bytes read. */ + @IntRange(from = -1) public long getLocalBytesRead() { return mLocalBytesRead; } @@ -277,6 +316,7 @@ public final class PlaybackMetrics implements Parcelable { /** * Gets network transfer duration in milliseconds. */ + @IntRange(from = -1) public long getNetworkTransferDurationMillis() { return mNetworkTransferDurationMillis; } @@ -415,34 +455,33 @@ public final class PlaybackMetrics implements Parcelable { */ public static final class Builder { - private long mMediaDurationMillis; - private int mStreamSource; - private int mStreamType; - private int mPlaybackType; - private int mDrmType; - private int mContentType; + private long mMediaDurationMillis = -1; + private int mStreamSource = STREAM_SOURCE_UNKNOWN; + private int mStreamType = STREAM_TYPE_UNKNOWN; + private int mPlaybackType = PLAYBACK_TYPE_OTHER; + private int mDrmType = DRM_TYPE_NONE; + private int mContentType = CONTENT_TYPE_OTHER; private @Nullable String mPlayerName; private @Nullable String mPlayerVersion; private @NonNull List<Long> mExperimentIds = new ArrayList<>(); - private int mVideoFramesPlayed; - private int mVideoFramesDropped; - private int mAudioUnderrunCount; - private long mNetworkBytesRead; - private long mLocalBytesRead; - private long mNetworkTransferDurationMillis; + private int mVideoFramesPlayed = -1; + private int mVideoFramesDropped = -1; + private int mAudioUnderrunCount = -1; + private long mNetworkBytesRead = -1; + private long mLocalBytesRead = -1; + private long mNetworkTransferDurationMillis = -1; /** * Creates a new Builder. - * - * @hide */ public Builder() { } /** * Sets the media duration in milliseconds. + * @param value the media duration in milliseconds. -1 indicates the value is unknown. */ - public @NonNull Builder setMediaDurationMillis(long value) { + public @NonNull Builder setMediaDurationMillis(@IntRange(from = -1) long value) { mMediaDurationMillis = value; return this; } @@ -474,7 +513,7 @@ public final class PlaybackMetrics implements Parcelable { /** * Sets the DRM type. */ - public @NonNull Builder setDrmType(@StreamType int value) { + public @NonNull Builder setDrmType(@DrmType int value) { mDrmType = value; return this; } @@ -513,48 +552,58 @@ public final class PlaybackMetrics implements Parcelable { /** * Sets the video frames played. + * @param value the video frames played. -1 indicates the value is unknown. */ - public @NonNull Builder setVideoFramesPlayed(int value) { + public @NonNull Builder setVideoFramesPlayed( + @IntRange(from = -1, to = Integer.MAX_VALUE) int value) { mVideoFramesPlayed = value; return this; } /** * Sets the video frames dropped. + * @param value the video frames dropped. -1 indicates the value is unknown. */ - public @NonNull Builder setVideoFramesDropped(int value) { + public @NonNull Builder setVideoFramesDropped( + @IntRange(from = -1, to = Integer.MAX_VALUE) int value) { mVideoFramesDropped = value; return this; } /** * Sets the audio underrun count. + * @param value the audio underrun count. -1 indicates the value is unknown. */ - public @NonNull Builder setAudioUnderrunCount(int value) { + public @NonNull Builder setAudioUnderrunCount( + @IntRange(from = -1, to = Integer.MAX_VALUE) int value) { mAudioUnderrunCount = value; return this; } /** * Sets the number of network bytes read. + * @param value the number of network bytes read. -1 indicates the value is unknown. */ - public @NonNull Builder setNetworkBytesRead(long value) { + public @NonNull Builder setNetworkBytesRead(@IntRange(from = -1) long value) { mNetworkBytesRead = value; return this; } /** * Sets the number of local bytes read. + * @param value the number of local bytes read. -1 indicates the value is unknown. */ - public @NonNull Builder setLocalBytesRead(long value) { + public @NonNull Builder setLocalBytesRead(@IntRange(from = -1) long value) { mLocalBytesRead = value; return this; } /** * Sets the network transfer duration in milliseconds. + * @param value the network transfer duration in milliseconds. + * -1 indicates the value is unknown. */ - public @NonNull Builder setNetworkTransferDurationMillis(long value) { + public @NonNull Builder setNetworkTransferDurationMillis(@IntRange(from = -1) long value) { mNetworkTransferDurationMillis = value; return this; } diff --git a/media/java/android/media/metrics/PlaybackSession.java b/media/java/android/media/metrics/PlaybackSession.java index 4cb957f4e597..4ee8a45fe196 100644 --- a/media/java/android/media/metrics/PlaybackSession.java +++ b/media/java/android/media/metrics/PlaybackSession.java @@ -45,7 +45,6 @@ public final class PlaybackSession implements AutoCloseable { /** * Reports playback metrics. - * @hide */ public void reportPlaybackMetrics(@NonNull PlaybackMetrics metrics) { mManager.reportPlaybackMetrics(mId, metrics); @@ -60,9 +59,8 @@ public final class PlaybackSession implements AutoCloseable { /** * Reports network event. - * @hide */ - public void reportNetworkEvent(NetworkEvent event) { + public void reportNetworkEvent(@NonNull NetworkEvent event) { mManager.reportNetworkEvent(mId, event); } @@ -75,9 +73,8 @@ public final class PlaybackSession implements AutoCloseable { /** * Reports track change event. - * @hide */ - public void reportTrackChangeEvent(TrackChangeEvent event) { + public void reportTrackChangeEvent(@NonNull TrackChangeEvent event) { mManager.reportTrackChangeEvent(mId, event); } diff --git a/media/java/android/media/metrics/TrackChangeEvent.java b/media/java/android/media/metrics/TrackChangeEvent.java index fff0e36c062a..ef25357457c5 100644 --- a/media/java/android/media/metrics/TrackChangeEvent.java +++ b/media/java/android/media/metrics/TrackChangeEvent.java @@ -17,6 +17,7 @@ package android.media.metrics; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; @@ -28,20 +29,29 @@ import java.util.Objects; /** * Playback track change event. - * @hide */ -public final class TrackChangeEvent implements Parcelable { +public final class TrackChangeEvent extends Event implements Parcelable { + /** The track is off. */ public static final int TRACK_STATE_OFF = 0; + /** The track is on. */ public static final int TRACK_STATE_ON = 1; + /** Unknown track change reason. */ public static final int TRACK_CHANGE_REASON_UNKNOWN = 0; + /** Other track change reason. */ public static final int TRACK_CHANGE_REASON_OTHER = 1; + /** Track change reason for initial state. */ public static final int TRACK_CHANGE_REASON_INITIAL = 2; + /** Track change reason for manual changes. */ public static final int TRACK_CHANGE_REASON_MANUAL = 3; + /** Track change reason for adaptive changes. */ public static final int TRACK_CHANGE_REASON_ADAPTIVE = 4; + /** Audio track. */ public static final int TRACK_TYPE_AUDIO = 0; + /** Video track. */ public static final int TRACK_TYPE_VIDEO = 1; + /** Text track. */ public static final int TRACK_TYPE_TEXT = 2; private final int mState; @@ -50,7 +60,7 @@ public final class TrackChangeEvent implements Parcelable { private final @Nullable String mSampleMimeType; private final @Nullable String mCodecName; private final int mBitrate; - private final long mTimeSincePlaybackCreatedMillis; + private final long mTimeSinceCreatedMillis; private final int mType; private final @Nullable String mLanguage; private final @Nullable String mLanguageRegion; @@ -96,7 +106,7 @@ public final class TrackChangeEvent implements Parcelable { @Nullable String sampleMimeType, @Nullable String codecName, int bitrate, - long timeSincePlaybackCreatedMillis, + long timeSinceCreatedMillis, int type, @Nullable String language, @Nullable String languageRegion, @@ -110,7 +120,7 @@ public final class TrackChangeEvent implements Parcelable { this.mSampleMimeType = sampleMimeType; this.mCodecName = codecName; this.mBitrate = bitrate; - this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis; + this.mTimeSinceCreatedMillis = timeSinceCreatedMillis; this.mType = type; this.mLanguage = language; this.mLanguageRegion = languageRegion; @@ -120,34 +130,60 @@ public final class TrackChangeEvent implements Parcelable { this.mHeight = height; } + /** + * Gets track state. + */ @TrackState public int getTrackState() { return mState; } + /** + * Gets track change reason. + */ @TrackChangeReason public int getTrackChangeReason() { return mReason; } + /** + * Gets container MIME type. + */ public @Nullable String getContainerMimeType() { return mContainerMimeType; } + /** + * Gets the MIME type of the video/audio/text samples. + */ public @Nullable String getSampleMimeType() { return mSampleMimeType; } + /** + * Gets codec name. + */ public @Nullable String getCodecName() { return mCodecName; } + /** + * Gets bitrate. + * @return the bitrate, or -1 if unknown. + */ + @IntRange(from = -1, to = Integer.MAX_VALUE) public int getBitrate() { return mBitrate; } - public long getTimeSincePlaybackCreatedMillis() { - return mTimeSincePlaybackCreatedMillis; + /** + * Gets timestamp since the creation in milliseconds. + * @return the timestamp since the creation in milliseconds, or -1 if unknown. + */ + @Override + @IntRange(from = -1) + public long getTimeSinceCreatedMillis() { + return mTimeSinceCreatedMillis; } @TrackType @@ -155,26 +191,55 @@ public final class TrackChangeEvent implements Parcelable { return mType; } + /** + * Gets language code. + * @return a two-letter ISO 639-1 language code. + */ public @Nullable String getLanguage() { return mLanguage; } + + /** + * Gets language region code. + * @return an IETF BCP 47 optional language region subtag based on a two-letter country code. + */ public @Nullable String getLanguageRegion() { return mLanguageRegion; } + /** + * Gets channel count. + * @return the channel count, or -1 if unknown. + */ + @IntRange(from = -1, to = Integer.MAX_VALUE) public int getChannelCount() { return mChannelCount; } + /** + * Gets sample rate. + * @return the sample rate, or -1 if unknown. + */ + @IntRange(from = -1, to = Integer.MAX_VALUE) public int getSampleRate() { return mSampleRate; } + /** + * Gets video width. + * @return the video width, or -1 if unknown. + */ + @IntRange(from = -1, to = Integer.MAX_VALUE) public int getWidth() { return mWidth; } + /** + * Gets video height. + * @return the video height, or -1 if unknown. + */ + @IntRange(from = -1, to = Integer.MAX_VALUE) public int getHeight() { return mHeight; } @@ -194,7 +259,7 @@ public final class TrackChangeEvent implements Parcelable { if (mSampleMimeType != null) dest.writeString(mSampleMimeType); if (mCodecName != null) dest.writeString(mCodecName); dest.writeInt(mBitrate); - dest.writeLong(mTimeSincePlaybackCreatedMillis); + dest.writeLong(mTimeSinceCreatedMillis); dest.writeInt(mType); if (mLanguage != null) dest.writeString(mLanguage); if (mLanguageRegion != null) dest.writeString(mLanguageRegion); @@ -218,7 +283,7 @@ public final class TrackChangeEvent implements Parcelable { String sampleMimeType = (flg & 0x8) == 0 ? null : in.readString(); String codecName = (flg & 0x10) == 0 ? null : in.readString(); int bitrate = in.readInt(); - long timeSincePlaybackCreatedMillis = in.readLong(); + long timeSinceCreatedMillis = in.readLong(); int type = in.readInt(); String language = (flg & 0x100) == 0 ? null : in.readString(); String languageRegion = (flg & 0x200) == 0 ? null : in.readString(); @@ -233,7 +298,7 @@ public final class TrackChangeEvent implements Parcelable { this.mSampleMimeType = sampleMimeType; this.mCodecName = codecName; this.mBitrate = bitrate; - this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis; + this.mTimeSinceCreatedMillis = timeSinceCreatedMillis; this.mType = type; this.mLanguage = language; this.mLanguageRegion = languageRegion; @@ -256,38 +321,24 @@ public final class TrackChangeEvent implements Parcelable { } }; - - - // Code below generated by codegen v1.0.22. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/media/java/android/media/metrics/TrackChangeEvent.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - @Override public String toString() { - return "TrackChangeEvent { " + - "state = " + mState + ", " + - "reason = " + mReason + ", " + - "containerMimeType = " + mContainerMimeType + ", " + - "sampleMimeType = " + mSampleMimeType + ", " + - "codecName = " + mCodecName + ", " + - "bitrate = " + mBitrate + ", " + - "timeSincePlaybackCreatedMillis = " + mTimeSincePlaybackCreatedMillis + ", " + - "type = " + mType + ", " + - "language = " + mLanguage + ", " + - "languageRegion = " + mLanguageRegion + ", " + - "channelCount = " + mChannelCount + ", " + - "sampleRate = " + mSampleRate + ", " + - "width = " + mWidth + ", " + - "height = " + mHeight + - " }"; + return "TrackChangeEvent { " + + "state = " + mState + ", " + + "reason = " + mReason + ", " + + "containerMimeType = " + mContainerMimeType + ", " + + "sampleMimeType = " + mSampleMimeType + ", " + + "codecName = " + mCodecName + ", " + + "bitrate = " + mBitrate + ", " + + "timeSinceCreatedMillis = " + mTimeSinceCreatedMillis + ", " + + "type = " + mType + ", " + + "language = " + mLanguage + ", " + + "languageRegion = " + mLanguageRegion + ", " + + "channelCount = " + mChannelCount + ", " + + "sampleRate = " + mSampleRate + ", " + + "width = " + mWidth + ", " + + "height = " + mHeight + + " }"; } @Override @@ -301,7 +352,7 @@ public final class TrackChangeEvent implements Parcelable { && Objects.equals(mSampleMimeType, that.mSampleMimeType) && Objects.equals(mCodecName, that.mCodecName) && mBitrate == that.mBitrate - && mTimeSincePlaybackCreatedMillis == that.mTimeSincePlaybackCreatedMillis + && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis && mType == that.mType && Objects.equals(mLanguage, that.mLanguage) && Objects.equals(mLanguageRegion, that.mLanguageRegion) @@ -314,7 +365,7 @@ public final class TrackChangeEvent implements Parcelable { @Override public int hashCode() { return Objects.hash(mState, mReason, mContainerMimeType, mSampleMimeType, mCodecName, - mBitrate, mTimeSincePlaybackCreatedMillis, mType, mLanguage, mLanguageRegion, + mBitrate, mTimeSinceCreatedMillis, mType, mLanguage, mLanguageRegion, mChannelCount, mSampleRate, mWidth, mHeight); } @@ -323,32 +374,33 @@ public final class TrackChangeEvent implements Parcelable { */ public static final class Builder { // TODO: check track type for the setters. - private int mState; - private int mReason; + private int mState = TRACK_STATE_OFF; + private int mReason = TRACK_CHANGE_REASON_UNKNOWN; private @Nullable String mContainerMimeType; private @Nullable String mSampleMimeType; private @Nullable String mCodecName; - private int mBitrate; - private long mTimeSincePlaybackCreatedMillis; - private int mType; + private int mBitrate = -1; + private long mTimeSinceCreatedMillis = -1; + private final int mType; private @Nullable String mLanguage; private @Nullable String mLanguageRegion; - private int mChannelCount; - private int mSampleRate; - private int mWidth; - private int mHeight; + private int mChannelCount = -1; + private int mSampleRate = -1; + private int mWidth = -1; + private int mHeight = -1; private long mBuilderFieldsSet = 0L; /** * Creates a new Builder. - * - * @hide */ public Builder(int type) { mType = type; } + /** + * Sets track state. + */ public @NonNull Builder setTrackState(@TrackState int value) { checkNotUsed(); mBuilderFieldsSet |= 0x1; @@ -356,6 +408,9 @@ public final class TrackChangeEvent implements Parcelable { return this; } + /** + * Sets track change reason. + */ public @NonNull Builder setTrackChangeReason(@TrackChangeReason int value) { checkNotUsed(); mBuilderFieldsSet |= 0x2; @@ -363,6 +418,9 @@ public final class TrackChangeEvent implements Parcelable { return this; } + /** + * Sets container MIME type. + */ public @NonNull Builder setContainerMimeType(@NonNull String value) { checkNotUsed(); mBuilderFieldsSet |= 0x4; @@ -370,6 +428,9 @@ public final class TrackChangeEvent implements Parcelable { return this; } + /** + * Sets the MIME type of the video/audio/text samples. + */ public @NonNull Builder setSampleMimeType(@NonNull String value) { checkNotUsed(); mBuilderFieldsSet |= 0x8; @@ -377,6 +438,9 @@ public final class TrackChangeEvent implements Parcelable { return this; } + /** + * Sets codec name. + */ public @NonNull Builder setCodecName(@NonNull String value) { checkNotUsed(); mBuilderFieldsSet |= 0x10; @@ -384,27 +448,33 @@ public final class TrackChangeEvent implements Parcelable { return this; } - public @NonNull Builder setBitrate(int value) { + /** + * Sets bitrate in bits per second. + * @param value the bitrate in bits per second. -1 indicates the value is unknown. + */ + public @NonNull Builder setBitrate(@IntRange(from = -1, to = Integer.MAX_VALUE) int value) { checkNotUsed(); mBuilderFieldsSet |= 0x20; mBitrate = value; return this; } - public @NonNull Builder setTimeSincePlaybackCreatedMillis(long value) { + /** + * Sets timestamp since the creation in milliseconds. + * @param value the timestamp since the creation in milliseconds. + * -1 indicates the value is unknown. + */ + public @NonNull Builder setTimeSinceCreatedMillis(@IntRange(from = -1) long value) { checkNotUsed(); mBuilderFieldsSet |= 0x40; - mTimeSincePlaybackCreatedMillis = value; - return this; - } - - public @NonNull Builder setTrackType(@TrackType int value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x80; - mType = value; + mTimeSinceCreatedMillis = value; return this; } + /** + * Sets language code. + * @param value a two-letter ISO 639-1 language code. + */ public @NonNull Builder setLanguage(@NonNull String value) { checkNotUsed(); mBuilderFieldsSet |= 0x100; @@ -412,6 +482,11 @@ public final class TrackChangeEvent implements Parcelable { return this; } + /** + * Sets language region code. + * @param value an IETF BCP 47 optional language region subtag based on a two-letter country + * code. + */ public @NonNull Builder setLanguageRegion(@NonNull String value) { checkNotUsed(); mBuilderFieldsSet |= 0x200; @@ -419,28 +494,46 @@ public final class TrackChangeEvent implements Parcelable { return this; } - public @NonNull Builder setChannelCount(int value) { + /** + * Sets channel count. + * @param value the channel count. -1 indicates the value is unknown. + */ + public @NonNull Builder setChannelCount( + @IntRange(from = -1, to = Integer.MAX_VALUE) int value) { checkNotUsed(); mBuilderFieldsSet |= 0x400; mChannelCount = value; return this; } - public @NonNull Builder setSampleRate(int value) { + /** + * Sets sample rate. + * @param value the sample rate. -1 indicates the value is unknown. + */ + public @NonNull Builder setSampleRate( + @IntRange(from = -1, to = Integer.MAX_VALUE) int value) { checkNotUsed(); mBuilderFieldsSet |= 0x800; mSampleRate = value; return this; } - public @NonNull Builder setWidth(int value) { + /** + * Sets video width. + * @param value the video width. -1 indicates the value is unknown. + */ + public @NonNull Builder setWidth(@IntRange(from = -1, to = Integer.MAX_VALUE) int value) { checkNotUsed(); mBuilderFieldsSet |= 0x1000; mWidth = value; return this; } - public @NonNull Builder setHeight(int value) { + /** + * Sets video height. + * @param value the video height. -1 indicates the value is unknown. + */ + public @NonNull Builder setHeight(@IntRange(from = -1, to = Integer.MAX_VALUE) int value) { checkNotUsed(); mBuilderFieldsSet |= 0x2000; mHeight = value; @@ -459,7 +552,7 @@ public final class TrackChangeEvent implements Parcelable { mSampleMimeType, mCodecName, mBitrate, - mTimeSincePlaybackCreatedMillis, + mTimeSinceCreatedMillis, mType, mLanguage, mLanguageRegion, diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 740bc2dbeb43..c0185dcc4539 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -2525,7 +2525,9 @@ public final class TvInputManager { /** * Pauses TV program recording in the current recording session. * - * @param params A set of extra parameters which might be handled with this event. + * @param params Domain-specific data for this request. Keys <em>must</em> be a scoped + * name, i.e. prefixed with a package name you own, so that different developers + * will not create conflicting keys. * {@link TvRecordingClient#pauseRecording(Bundle)}. */ void pauseRecording(@NonNull Bundle params) { @@ -2543,7 +2545,9 @@ public final class TvInputManager { /** * Resumes TV program recording in the current recording session. * - * @param params A set of extra parameters which might be handled with this event. + * @param params Domain-specific data for this request. Keys <em>must</em> be a scoped + * name, i.e. prefixed with a package name you own, so that different developers + * will not create conflicting keys. * {@link TvRecordingClient#resumeRecording(Bundle)}. */ void resumeRecording(@NonNull Bundle params) { diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 4972529eb705..6160b8152295 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -64,6 +64,7 @@ cc_library_shared { "android.hardware.cas@1.0", "android.hardware.cas.native@1.0", "android.hardware.drm@1.3", + "android.hardware.drm@1.4", "android.hidl.memory@1.0", "android.hidl.token@1.0-utils", ], diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index 0e8719eeb79c..22c3572e963b 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -37,6 +37,7 @@ #include <mediadrm/DrmUtils.h> #include <mediadrm/IDrmMetricsConsumer.h> #include <mediadrm/IDrm.h> +#include <utils/Vector.h> using ::android::os::PersistableBundle; namespace drm = ::android::hardware::drm; @@ -187,6 +188,11 @@ struct KeyStatusFields { jclass classId; }; +struct LogMessageFields { + jmethodID init; + jclass classId; +}; + struct fields_t { jfieldID context; jmethodID post_event; @@ -208,6 +214,7 @@ struct fields_t { jmethodID createFromParcelId; jclass parcelCreatorClassId; KeyStatusFields keyStatus; + LogMessageFields logMessage; }; static fields_t gFields; @@ -224,6 +231,19 @@ jbyteArray hidlVectorToJByteArray(const hardware::hidl_vec<uint8_t> &vector) { return result; } +jobject hidlLogMessagesToJavaList(JNIEnv *env, const Vector<drm::V1_4::LogMessage> &logs) { + jclass clazz = gFields.arraylistClassId; + jobject arrayList = env->NewObject(clazz, gFields.arraylist.init); + clazz = gFields.logMessage.classId; + for (auto log: logs) { + jobject jLog = env->NewObject(clazz, gFields.logMessage.init, + static_cast<jlong>(log.timeMs), + static_cast<jint>(log.priority), + env->NewStringUTF(log.message.c_str())); + env->CallBooleanMethod(arrayList, gFields.arraylist.add, jLog); + } + return arrayList; +} } // namespace anonymous // ---------------------------------------------------------------------------- @@ -907,6 +927,10 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) { FIND_CLASS(clazz, "android/media/MediaDrm$KeyStatus"); gFields.keyStatus.classId = static_cast<jclass>(env->NewGlobalRef(clazz)); GET_METHOD_ID(gFields.keyStatus.init, clazz, "<init>", "([BI)V"); + + FIND_CLASS(clazz, "android/media/MediaDrm$LogMessage"); + gFields.logMessage.classId = static_cast<jclass>(env->NewGlobalRef(clazz)); + GET_METHOD_ID(gFields.logMessage.init, clazz, "<init>", "(JILjava/lang/String;)V"); } static void android_media_MediaDrm_native_setup( @@ -1996,6 +2020,22 @@ static void android_media_MediaDrm_setPlaybackId( throwExceptionAsNecessary(env, err, "Failed to set playbackId"); } +static jobject android_media_MediaDrm_getLogMessages( + JNIEnv *env, jobject thiz) { + sp<IDrm> drm = GetDrm(env, thiz); + if (!CheckDrm(env, drm)) { + return NULL; + } + + Vector<drm::V1_4::LogMessage> logs; + status_t err = drm->getLogMessages(logs); + ALOGI("drm->getLogMessages %zu logs", logs.size()); + if (throwExceptionAsNecessary(env, err, "Failed to get log messages")) { + return NULL; + } + return hidlLogMessagesToJavaList(env, logs); +} + static const JNINativeMethod gMethods[] = { { "native_release", "()V", (void *)android_media_MediaDrm_native_release }, @@ -2123,6 +2163,9 @@ static const JNINativeMethod gMethods[] = { { "setPlaybackId", "([BLjava/lang/String;)V", (void *)android_media_MediaDrm_setPlaybackId }, + + { "getLogMessages", "()Ljava/util/List;", + (void *)android_media_MediaDrm_getLogMessages }, }; int register_android_media_Drm(JNIEnv *env) { diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java index 72589e39d555..c30d4bf322cf 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java @@ -165,6 +165,7 @@ public class DeviceChooserActivity extends Activity { protected void onStop() { super.onStop(); if (!isFinishing() && !isChangingConfigurations()) { + Log.i(LOG_TAG, "onStop() - cancelling"); cancel(); } } @@ -195,7 +196,6 @@ public class DeviceChooserActivity extends Activity { titleView.setText(title); } - //TODO put in resources xmls private ProgressBar getProgressBar() { final ProgressBar progressBar = new ProgressBar(this); progressBar.setForegroundGravity(Gravity.CENTER_HORIZONTAL); diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java index 606cd5718d98..67d4b41f164c 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java @@ -309,7 +309,7 @@ public class DeviceDiscoveryService extends Service { } private void onDeviceLost(@Nullable DeviceFilterPair device) { - if (DEBUG) Log.i(LOG_TAG, "Lost device " + device.getDisplayName()); + Log.i(LOG_TAG, "Lost device " + device.getDisplayName()); Handler.getMain().sendMessage(obtainMessage( DeviceDiscoveryService::onDeviceLostMainThread, this, device)); } diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp index 8db8d7699a1e..73e15116443f 100644 --- a/packages/Connectivity/framework/Android.bp +++ b/packages/Connectivity/framework/Android.bp @@ -14,15 +14,37 @@ // limitations under the License. // -// TODO: use a java_library in the bootclasspath instead filegroup { - name: "framework-connectivity-sources", + name: "framework-connectivity-internal-sources", srcs: [ "src/**/*.java", "src/**/*.aidl", ], path: "src", visibility: [ + "//visibility:private", + ], +} + +filegroup { + name: "framework-connectivity-aidl-export-sources", + srcs: [ + "aidl-export/**/*.aidl", + ], + path: "aidl-export", + visibility: [ + "//visibility:private", + ], +} + +// TODO: use a java_library in the bootclasspath instead +filegroup { + name: "framework-connectivity-sources", + srcs: [ + ":framework-connectivity-internal-sources", + ":framework-connectivity-aidl-export-sources", + ], + visibility: [ "//frameworks/base", "//packages/modules/Connectivity:__subpackages__", ], diff --git a/packages/Connectivity/framework/src/android/net/CaptivePortalData.aidl b/packages/Connectivity/framework/aidl-export/android/net/CaptivePortalData.aidl index 1d57ee759136..1d57ee759136 100644 --- a/packages/Connectivity/framework/src/android/net/CaptivePortalData.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/CaptivePortalData.aidl diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityDiagnosticsManager.aidl b/packages/Connectivity/framework/aidl-export/android/net/ConnectivityDiagnosticsManager.aidl index 82ba0ca113c5..82ba0ca113c5 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityDiagnosticsManager.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/ConnectivityDiagnosticsManager.aidl diff --git a/packages/Connectivity/framework/src/android/net/DhcpInfo.aidl b/packages/Connectivity/framework/aidl-export/android/net/DhcpInfo.aidl index 29cd21fe7652..29cd21fe7652 100644 --- a/packages/Connectivity/framework/src/android/net/DhcpInfo.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/DhcpInfo.aidl diff --git a/packages/Connectivity/framework/src/android/net/IpConfiguration.aidl b/packages/Connectivity/framework/aidl-export/android/net/IpConfiguration.aidl index 7a30f0e79cad..7a30f0e79cad 100644 --- a/packages/Connectivity/framework/src/android/net/IpConfiguration.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/IpConfiguration.aidl diff --git a/packages/Connectivity/framework/src/android/net/IpPrefix.aidl b/packages/Connectivity/framework/aidl-export/android/net/IpPrefix.aidl index 0d70f2a1ed2c..0d70f2a1ed2c 100644 --- a/packages/Connectivity/framework/src/android/net/IpPrefix.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/IpPrefix.aidl diff --git a/packages/Connectivity/framework/src/android/net/KeepalivePacketData.aidl b/packages/Connectivity/framework/aidl-export/android/net/KeepalivePacketData.aidl index d456b53fd188..d456b53fd188 100644 --- a/packages/Connectivity/framework/src/android/net/KeepalivePacketData.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/KeepalivePacketData.aidl diff --git a/packages/Connectivity/framework/src/android/net/LinkAddress.aidl b/packages/Connectivity/framework/aidl-export/android/net/LinkAddress.aidl index 9c804db08d61..9c804db08d61 100644 --- a/packages/Connectivity/framework/src/android/net/LinkAddress.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/LinkAddress.aidl diff --git a/packages/Connectivity/framework/src/android/net/LinkProperties.aidl b/packages/Connectivity/framework/aidl-export/android/net/LinkProperties.aidl index a8b3c7b0392f..a8b3c7b0392f 100644 --- a/packages/Connectivity/framework/src/android/net/LinkProperties.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/LinkProperties.aidl diff --git a/packages/Connectivity/framework/src/android/net/MacAddress.aidl b/packages/Connectivity/framework/aidl-export/android/net/MacAddress.aidl index 48a18a7ac821..48a18a7ac821 100644 --- a/packages/Connectivity/framework/src/android/net/MacAddress.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/MacAddress.aidl diff --git a/packages/Connectivity/framework/src/android/net/Network.aidl b/packages/Connectivity/framework/aidl-export/android/net/Network.aidl index 05622025bf33..05622025bf33 100644 --- a/packages/Connectivity/framework/src/android/net/Network.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/Network.aidl diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.aidl b/packages/Connectivity/framework/aidl-export/android/net/NetworkAgentConfig.aidl index cb70bdd31260..cb70bdd31260 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/NetworkAgentConfig.aidl diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.aidl b/packages/Connectivity/framework/aidl-export/android/net/NetworkCapabilities.aidl index 01d328605de4..01d328605de4 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/NetworkCapabilities.aidl diff --git a/packages/Connectivity/framework/src/android/net/NetworkInfo.aidl b/packages/Connectivity/framework/aidl-export/android/net/NetworkInfo.aidl index f50187302966..f50187302966 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkInfo.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/NetworkInfo.aidl diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.aidl b/packages/Connectivity/framework/aidl-export/android/net/NetworkRequest.aidl index 508defc6b497..508defc6b497 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkRequest.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/NetworkRequest.aidl diff --git a/packages/Connectivity/framework/src/android/net/ProxyInfo.aidl b/packages/Connectivity/framework/aidl-export/android/net/ProxyInfo.aidl index a5d0c120e747..a5d0c120e747 100644 --- a/packages/Connectivity/framework/src/android/net/ProxyInfo.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/ProxyInfo.aidl diff --git a/packages/Connectivity/framework/src/android/net/RouteInfo.aidl b/packages/Connectivity/framework/aidl-export/android/net/RouteInfo.aidl index 7af9fdaef342..7af9fdaef342 100644 --- a/packages/Connectivity/framework/src/android/net/RouteInfo.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/RouteInfo.aidl diff --git a/packages/Connectivity/framework/src/android/net/StaticIpConfiguration.aidl b/packages/Connectivity/framework/aidl-export/android/net/StaticIpConfiguration.aidl index 8aac701fe7e1..8aac701fe7e1 100644 --- a/packages/Connectivity/framework/src/android/net/StaticIpConfiguration.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/StaticIpConfiguration.aidl diff --git a/packages/Connectivity/framework/src/android/net/TestNetworkInterface.aidl b/packages/Connectivity/framework/aidl-export/android/net/TestNetworkInterface.aidl index e1f4f9f794eb..e1f4f9f794eb 100644 --- a/packages/Connectivity/framework/src/android/net/TestNetworkInterface.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/TestNetworkInterface.aidl diff --git a/packages/Connectivity/framework/src/android/net/apf/ApfCapabilities.aidl b/packages/Connectivity/framework/aidl-export/android/net/apf/ApfCapabilities.aidl index 7c4d4c2da4bc..7c4d4c2da4bc 100644 --- a/packages/Connectivity/framework/src/android/net/apf/ApfCapabilities.aidl +++ b/packages/Connectivity/framework/aidl-export/android/net/apf/ApfCapabilities.aidl diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java index 81ca9eaf8e36..228de039fc1b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java +++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java @@ -163,8 +163,12 @@ public class RecentLocationAccesses { long locationAccessFinishTime = 0L; // Earliest time for a location access to end and still be shown in list. long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS; + // Compute the most recent access time from all op entries. for (AppOpsManager.OpEntry entry : entries) { - locationAccessFinishTime = entry.getLastAccessTime(TRUSTED_STATE_FLAGS); + long lastAccessTime = entry.getLastAccessTime(TRUSTED_STATE_FLAGS); + if (lastAccessTime > locationAccessFinishTime) { + locationAccessFinishTime = lastAccessTime; + } } // Bail out if the entry is out of date. if (locationAccessFinishTime < recentLocationCutoffTime) { diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index dbd25ceecae4..a15ceb6d8811 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -345,6 +345,12 @@ <!-- Permissions required for CTS test - AdbManagerTest --> <uses-permission android:name="android.permission.MANAGE_DEBUGGING" /> + <!-- Permission required for CTS test - CtsTelephonyTestCases --> + <uses-permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE" /> + + <!-- Permission required for CTS test - CtsTelephonyTestCases --> + <uses-permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION" /> + <!-- Permission needed for CTS test - DisplayTest --> <uses-permission android:name="android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS" /> diff --git a/packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml b/packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml index 1f6b24bb85bf..dd35dd91f5d6 100644 --- a/packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml +++ b/packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml @@ -1,5 +1,5 @@ <!-- - ~ Copyright (C) 2017 The Android Open Source Project + ~ Copyright (C) 2021 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. @@ -20,6 +20,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> <path - android:fillColor="#FF000000" + android:fillColor="?android:attr/colorBackground" android:pathData="M9,15.59L12.59,12L9,8.41L10.41,7L14,10.59L17.59,7L19,8.41L15.41,12L19,15.59L17.59,17L14,13.41L10.41,17L9,15.59zM21,6H8l-4.5,6L8,18h13V6M21,4c1.1,0 2,0.9 2,2v12c0,1.1 -0.9,2 -2,2H8c-0.63,0 -1.22,-0.3 -1.6,-0.8L1,12l5.4,-7.2C6.78,4.3 7.37,4 8,4H21L21,4z"/> </vector> diff --git a/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml b/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml index b7a9fafd0c44..604ab723da90 100644 --- a/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml +++ b/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml @@ -16,8 +16,23 @@ * limitations under the License. */ --> -<shape - xmlns:android="http://schemas.android.com/apk/res/android"> - <solid android:color="?android:attr/colorBackground" /> - <corners android:radius="10dp" /> -</shape> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/background"> + <shape> + <solid android:color="?android:attr/colorControlNormal" /> + <corners android:radius="10dp" /> + </shape> + </item> + <item android:id="@+id/ripple"> + <ripple + android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="?android:attr/colorControlNormal" /> + <corners android:radius="10dp" /> + </shape> + </item> + </ripple> + </item> +</layer-list> + diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index a928b759e1b8..2a5784a1e6e9 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -40,7 +40,7 @@ <dimen name="keyguard_security_view_top_margin">8dp</dimen> <dimen name="keyguard_security_view_lateral_margin">36dp</dimen> - <dimen name="keyguard_eca_top_margin">24dp</dimen> + <dimen name="keyguard_eca_top_margin">18dp</dimen> <!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text. Should be 0 on devices with plenty of room (e.g. tablets) --> diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index cd82b805be9c..2391803957e5 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -52,9 +52,8 @@ <item name="android:textColor">?android:attr/textColorPrimary</item> </style> <style name="NumPadKey.Delete"> - <item name="android:src">@drawable/ic_backspace_black_24dp</item> - <item name="android:tint">?android:attr/textColorSecondary</item> - <item name="android:tintMode">src_in</item> + <item name="android:colorControlNormal">?android:attr/textColorSecondary</item> + <item name="android:src">@drawable/ic_backspace_24dp</item> </style> <style name="NumPadKey.Enter"> <item name="android:colorControlNormal">?android:attr/textColorSecondary</item> diff --git a/packages/SystemUI/res/layout/people_space_notification_content_tile.xml b/packages/SystemUI/res/layout/people_space_notification_content_tile.xml index 739738ae9e35..9ea7aa3c94e5 100644 --- a/packages/SystemUI/res/layout/people_space_notification_content_tile.xml +++ b/packages/SystemUI/res/layout/people_space_notification_content_tile.xml @@ -21,7 +21,7 @@ android:orientation="vertical"> <RelativeLayout android:background="@drawable/people_space_tile_view_card" - android:id="@+id/people_tile" + android:id="@+id/item" android:layout_width="match_parent" android:layout_height="match_parent"> <include layout="@layout/punctuation_layout"/> diff --git a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml b/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml index bb4a20e4108e..33004952d947 100644 --- a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml +++ b/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml @@ -21,7 +21,7 @@ android:orientation="vertical"> <RelativeLayout android:background="@drawable/people_space_tile_view_card" - android:id="@+id/people_tile" + android:id="@+id/item" android:layout_width="match_parent" android:layout_height="match_parent"> <RelativeLayout diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml index dc34127496f6..059bda3a2376 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml @@ -54,16 +54,4 @@ android:paddingBottom="10dp" android:importantForAccessibility="yes" /> - <TextView - android:id="@+id/header_debug_info" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:fontFamily="sans-serif-condensed" - android:padding="2dp" - android:textColor="#00A040" - android:textSize="11dp" - android:textStyle="bold" - android:visibility="invisible"/> - </com.android.systemui.qs.QuickStatusBarHeader> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index 825ea2570df0..4e06491621cb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -24,14 +24,12 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; import android.content.Context; -import android.content.res.ColorStateList; import android.graphics.Rect; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.View; import com.android.internal.widget.LockscreenCredential; -import com.android.settingslib.Utils; import com.android.systemui.R; /** @@ -188,10 +186,6 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView key.reloadColors(); } mPasswordEntry.reloadColors(); - int deleteColor = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary) - .getDefaultColor(); - mDeleteButton.setImageTintList(ColorStateList.valueOf(deleteColor)); - mDeleteButton.reloadColors(); mOkButton.reloadColors(); } diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java index cdf98581e29b..97d6e9750716 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java @@ -17,14 +17,16 @@ package com.android.keyguard; import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.RippleDrawable; import android.view.ContextThemeWrapper; import android.view.ViewGroup; import androidx.annotation.StyleRes; -import com.android.internal.graphics.ColorUtils; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -34,13 +36,18 @@ import com.android.systemui.R; class NumPadAnimator { private ValueAnimator mAnimator; private GradientDrawable mBackground; + private RippleDrawable mRipple; + private GradientDrawable mRippleMask; private int mMargin; private int mNormalColor; private int mHighlightColor; private int mStyle; - NumPadAnimator(Context context, final GradientDrawable background, @StyleRes int style) { - mBackground = (GradientDrawable) background.mutate(); + NumPadAnimator(Context context, LayerDrawable drawable, @StyleRes int style) { + LayerDrawable ld = (LayerDrawable) drawable.mutate(); + mBackground = (GradientDrawable) ld.findDrawableByLayerId(R.id.background); + mRipple = (RippleDrawable) ld.findDrawableByLayerId(R.id.ripple); + mRippleMask = (GradientDrawable) mRipple.findDrawableByLayerId(android.R.id.mask); mStyle = style; reloadColors(context); @@ -49,13 +56,14 @@ class NumPadAnimator { // Actual values will be updated later, usually during an onLayout() call mAnimator = ValueAnimator.ofFloat(0f); - mAnimator.setDuration(250); - mAnimator.setInterpolator(Interpolators.LINEAR); + mAnimator.setDuration(100); + mAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); + mAnimator.setRepeatMode(ValueAnimator.REVERSE); + mAnimator.setRepeatCount(1); mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator anim) { mBackground.setCornerRadius((float) anim.getAnimatedValue()); - mBackground.setColor(ColorUtils.blendARGB(mHighlightColor, mNormalColor, - anim.getAnimatedFraction())); + mRippleMask.setCornerRadius((float) anim.getAnimatedValue()); } }); @@ -66,9 +74,9 @@ class NumPadAnimator { } void onLayout(int height) { - float startRadius = height / 10f; - float endRadius = height / 2f; - mBackground.setCornerRadius(endRadius); + float startRadius = height / 2f; + float endRadius = height / 4f; + mBackground.setCornerRadius(startRadius); mAnimator.setFloatValues(startRadius, endRadius); } @@ -91,6 +99,7 @@ class NumPadAnimator { a.recycle(); mBackground.setColor(mNormalColor); + mRipple.setColor(ColorStateList.valueOf(mHighlightColor)); } } diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java index 886c3729124b..8cb1bc4878a5 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java @@ -17,7 +17,7 @@ package com.android.keyguard; import android.content.Context; import android.content.res.ColorStateList; -import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.VectorDrawable; import android.util.AttributeSet; import android.view.ContextThemeWrapper; @@ -37,7 +37,7 @@ public class NumPadButton extends AlphaOptimizedImageButton { public NumPadButton(Context context, AttributeSet attrs) { super(context, attrs); - mAnimator = new NumPadAnimator(context, (GradientDrawable) getBackground(), + mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(), attrs.getStyleAttribute()); } diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index 01e1c632ad83..a4a781dc6ff5 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -18,7 +18,7 @@ package com.android.keyguard; import android.content.Context; import android.content.res.TypedArray; -import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; import android.os.PowerManager; import android.os.SystemClock; import android.util.AttributeSet; @@ -127,7 +127,7 @@ public class NumPadKey extends ViewGroup { setContentDescription(mDigitText.getText().toString()); - mAnimator = new NumPadAnimator(context, (GradientDrawable) getBackground(), + mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(), R.style.NumPadKey); } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt index 40c238680a45..fc89783018bc 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt @@ -32,6 +32,8 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.CustomIconCache import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.StructureInfo +import com.android.systemui.controls.ui.ControlsDialog +import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.globalactions.GlobalActionsComponent import com.android.systemui.settings.CurrentUserTracker import com.android.systemui.util.LifecycleActivity @@ -42,9 +44,10 @@ import javax.inject.Inject */ class ControlsEditingActivity @Inject constructor( private val controller: ControlsControllerImpl, - broadcastDispatcher: BroadcastDispatcher, + private val broadcastDispatcher: BroadcastDispatcher, private val globalActionsComponent: GlobalActionsComponent, - private val customIconCache: CustomIconCache + private val customIconCache: CustomIconCache, + private val uiController: ControlsUiController ) : LifecycleActivity() { companion object { @@ -59,6 +62,7 @@ class ControlsEditingActivity @Inject constructor( private lateinit var model: FavoritesModel private lateinit var subtitle: TextView private lateinit var saveButton: View + private var backToGlobalActions = true private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { private val startingUser = controller.currentUserId @@ -82,6 +86,11 @@ class ControlsEditingActivity @Inject constructor( structure = it } ?: run(this::finish) + backToGlobalActions = intent.getBooleanExtra( + ControlsUiController.BACK_TO_GLOBAL_ACTIONS, + true + ) + bindViews() bindButtons() @@ -100,7 +109,11 @@ class ControlsEditingActivity @Inject constructor( } override fun onBackPressed() { - globalActionsComponent.handleShowGlobalActionsMenu() + if (backToGlobalActions) { + globalActionsComponent.handleShowGlobalActionsMenu() + } else { + ControlsDialog(applicationContext, broadcastDispatcher).show(uiController) + } animateExitAndFinish() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index b2821579c389..1c2f17c55671 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -40,6 +40,8 @@ import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.TooltipManager import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.StructureInfo +import com.android.systemui.controls.ui.ControlsDialog +import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.globalactions.GlobalActionsComponent import com.android.systemui.settings.CurrentUserTracker @@ -53,8 +55,9 @@ class ControlsFavoritingActivity @Inject constructor( @Main private val executor: Executor, private val controller: ControlsControllerImpl, private val listingController: ControlsListingController, - broadcastDispatcher: BroadcastDispatcher, - private val globalActionsComponent: GlobalActionsComponent + private val broadcastDispatcher: BroadcastDispatcher, + private val globalActionsComponent: GlobalActionsComponent, + private val uiController: ControlsUiController ) : LifecycleActivity() { companion object { @@ -89,6 +92,7 @@ class ControlsFavoritingActivity @Inject constructor( private lateinit var comparator: Comparator<StructureContainer> private var cancelLoadRunnable: Runnable? = null private var isPagerLoaded = false + private var backToGlobalActions = true private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { private val startingUser = controller.currentUserId @@ -114,7 +118,7 @@ class ControlsFavoritingActivity @Inject constructor( override fun onBackPressed() { if (!fromProviderSelector) { - globalActionsComponent.handleShowGlobalActionsMenu() + openControlsOrigin() } animateExitAndFinish() } @@ -129,6 +133,11 @@ class ControlsFavoritingActivity @Inject constructor( component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME) fromProviderSelector = intent.getBooleanExtra(EXTRA_FROM_PROVIDER_SELECTOR, false) + backToGlobalActions = intent.getBooleanExtra( + ControlsUiController.BACK_TO_GLOBAL_ACTIONS, + true + ) + bindViews() } @@ -330,11 +339,19 @@ class ControlsFavoritingActivity @Inject constructor( ) } animateExitAndFinish() - globalActionsComponent.handleShowGlobalActionsMenu() + openControlsOrigin() } } } + private fun openControlsOrigin() { + if (backToGlobalActions) { + globalActionsComponent.handleShowGlobalActionsMenu() + } else { + ControlsDialog(applicationContext, broadcastDispatcher).show(uiController) + } + } + override fun onPause() { super.onPause() mTooltipManager?.hide(false) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt index db68d17461fa..537334aeb2f7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt @@ -67,7 +67,7 @@ class ControlsDialog @Inject constructor( val vg = requireViewById<ViewGroup>(com.android.systemui.R.id.global_actions_controls) vg.alpha = 0f - controller.show(vg, { /* do nothing */ }, false /* startedFromGlobalActions */) + controller.show(vg, { dismiss() }, false /* startedFromGlobalActions */) vg.animate() .alpha(1f) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt index 944887741721..20bdf609357e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -27,6 +27,7 @@ interface ControlsUiController { companion object { public const val TAG = "ControlsUiController" public const val EXTRA_ANIMATE = "extra_animate" + public const val BACK_TO_GLOBAL_ACTIONS = "back_to_global_actions" } fun show(parent: ViewGroup, onDismiss: Runnable, startedFromGlobalActions: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 762362cde095..c94d85aa58c4 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -266,6 +266,10 @@ class ControlsUiControllerImpl @Inject constructor ( private fun startActivity(context: Context, intent: Intent) { // Force animations when transitioning from a dialog to an activity intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true) + intent.putExtra( + ControlsUiController.BACK_TO_GLOBAL_ACTIONS, + controlActionCoordinator.startedFromGlobalActions + ) onDismiss.run() activityStarter.dismissKeyguardThenExecute({ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java index d65d16951191..2873cd36409d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.ColorStateList; import android.graphics.drawable.Drawable; +import android.text.TextUtils; import android.view.View; /** @@ -56,7 +57,7 @@ public class KeyguardIndication { /** * Message to display */ - public @NonNull CharSequence getMessage() { + public @Nullable CharSequence getMessage() { return mMessage; } @@ -88,6 +89,17 @@ public class KeyguardIndication { return mBackground; } + @Override + public String toString() { + String str = "KeyguardIndication{"; + if (!TextUtils.isEmpty(mMessage)) str += "mMessage=" + mMessage; + if (mIcon != null) str += " mIcon=" + mIcon; + if (mOnClickListener != null) str += " mOnClickListener=" + mOnClickListener; + if (mBackground != null) str += " mBackground=" + mBackground; + str += "}"; + return str; + } + /** * KeyguardIndication Builder */ @@ -101,7 +113,7 @@ public class KeyguardIndication { public Builder() { } /** - * Required field. Message to display. + * Message to display. Indication requires a non-null message or icon. */ public Builder setMessage(@NonNull CharSequence message) { this.mMessage = message; @@ -117,9 +129,9 @@ public class KeyguardIndication { } /** - * Optional. Icon to show next to the text. Icon location changes based on language - * display direction. For LTR, icon shows to the left of the message. For RTL, icon shows - * to the right of the message. + * Icon to show next to the text. Indication requires a non-null icon or message. + * Icon location changes based on language display direction. For LTR, icon shows to the + * left of the message. For RTL, icon shows to the right of the message. */ public Builder setIcon(Drawable icon) { this.mIcon = icon; @@ -146,7 +158,7 @@ public class KeyguardIndication { * Build the KeyguardIndication. */ public KeyguardIndication build() { - if (mMessage == null && mIcon == null) { + if (TextUtils.isEmpty(mMessage) && mIcon == null) { throw new IllegalStateException("message or icon must be set"); } if (mTextColor == null) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java index 2e599de1970d..d4678f39e404 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java @@ -19,7 +19,6 @@ package com.android.systemui.keyguard; import android.annotation.Nullable; import android.content.res.ColorStateList; import android.graphics.Color; -import android.text.TextUtils; import android.view.View; import androidx.annotation.IntDef; @@ -105,9 +104,7 @@ public class KeyguardIndicationRotateTextViewController extends public void updateIndication(@IndicationType int type, KeyguardIndication newIndication, boolean showImmediately) { final boolean hasPreviousIndication = mIndicationMessages.get(type) != null; - final boolean hasNewIndication = newIndication != null - && (!TextUtils.isEmpty(newIndication.getMessage()) - || newIndication.getIcon() != null); + final boolean hasNewIndication = newIndication != null; if (!hasNewIndication) { mIndicationMessages.remove(type); mIndicationQueue.removeIf(x -> x == type); @@ -289,8 +286,7 @@ public class KeyguardIndicationRotateTextViewController extends if (hasIndications()) { pw.println(" All messages:"); for (int type : mIndicationMessages.keySet()) { - pw.println(" type=" + type - + " message=" + mIndicationMessages.get(type).getMessage()); + pw.println(" type=" + type + " " + mIndicationMessages.get(type)); } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index dab4d0bb00c5..1660deabdd72 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -855,6 +855,14 @@ public class NavigationBar implements View.OnAttachStateChangeListener, @Override public void onRotationProposal(final int rotation, boolean isValid) { + if (mNavigationBarView == null) { + if (RotationContextButton.DEBUG_ROTATION) { + Log.v(TAG, "onRotationProposal proposedRotation=" + + Surface.rotationToString(rotation) + ", mNavigationBarView is null"); + } + return; + } + final int winRotation = mNavigationBarView.getDisplay().getRotation(); final boolean rotateSuggestionsDisabled = RotationButtonController .hasDisable2RotateSuggestionFlag(mDisabledFlags2); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 87252ff2b908..a0bf5846ef53 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -90,6 +90,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn private OngoingPrivacyChip mPrivacyChip; private Space mSpace; private BatteryMeterView mBatteryRemainingIcon; + private TintedIconManager mTintedIconManager; // Used for RingerModeTracker private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); @@ -144,6 +145,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn } void onAttach(TintedIconManager iconManager) { + mTintedIconManager = iconManager; int fillColor = Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorPrimary); @@ -268,6 +270,9 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn android.R.attr.textColorSecondary); mTextColorPrimary = textColor; mClockView.setTextColor(textColor); + if (mTintedIconManager != null) { + mTintedIconManager.setTint(textColor); + } mBatteryRemainingIcon.updateColors(mTextColorPrimary, textColorSecondary, mTextColorPrimary); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index c70a93b5c894..7e2d27ac728a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -208,7 +208,6 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal mLockScreenMode); updateIndication(false /* animate */); updateDisclosure(); - updateOwnerInfo(); if (mBroadcastReceiver == null) { // Update the disclosure proactively to avoid IPC on the critical path. mBroadcastReceiver = new BroadcastReceiver() { @@ -261,18 +260,21 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal } /** - * Doesn't include owner information or disclosure which get triggered separately. + * Doesn't include disclosure which gets triggered separately. */ private void updateIndications(boolean animate, int userId) { + updateOwnerInfo(); updateBattery(animate); updateUserLocked(userId); updateTransient(); updateTrust(userId, getTrustGrantedIndication(), getTrustManagedIndication()); updateAlignment(); + updateLogoutView(); updateResting(); } private void updateDisclosure() { + // avoid calling this method since it has an IPC if (whitelistIpcs(this::isOrganizationOwnedDevice)) { final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName(); final CharSequence disclosure = organizationName != null @@ -291,7 +293,34 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal } if (isKeyguardLayoutEnabled()) { - updateIndication(false); // resting indication may need to update + updateResting(); + } + } + + private void updateOwnerInfo() { + if (!isKeyguardLayoutEnabled()) { + mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO); + return; + } + String info = mLockPatternUtils.getDeviceOwnerInfo(); + if (info == null) { + // Use the current user owner information if enabled. + final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled( + KeyguardUpdateMonitor.getCurrentUser()); + if (ownerInfoEnabled) { + info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser()); + } + } + if (info != null) { + mRotateTextViewController.updateIndication( + INDICATION_TYPE_OWNER_INFO, + new KeyguardIndication.Builder() + .setMessage(info) + .setTextColor(mInitialTextColorState) + .build(), + false); + } else { + mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO); } } @@ -400,56 +429,34 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal private void updateLogoutView() { if (!isKeyguardLayoutEnabled()) { + mRotateTextViewController.hideIndication(INDICATION_TYPE_LOGOUT); return; } final boolean shouldShowLogout = mKeyguardUpdateMonitor.isLogoutEnabled() && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM; - String logoutString = shouldShowLogout ? mContext.getResources().getString( - com.android.internal.R.string.global_action_logout) : null; - mRotateTextViewController.updateIndication( - INDICATION_TYPE_LOGOUT, - new KeyguardIndication.Builder() - .setMessage(logoutString) - .setTextColor(mInitialTextColorState) - .setBackground(mContext.getDrawable( - com.android.systemui.R.drawable.logout_button_background)) - .setClickListener((view) -> { - int currentUserId = KeyguardUpdateMonitor.getCurrentUser(); - try { - mIActivityManager.switchUser(UserHandle.USER_SYSTEM); - mIActivityManager.stopUser(currentUserId, true /* force */, null); - } catch (RemoteException re) { - Log.e(TAG, "Failed to logout user", re); - } - }) - .build(), - false); - updateIndication(false); // resting indication may need to update - } - - private void updateOwnerInfo() { - if (!isKeyguardLayoutEnabled()) { - return; - } - String info = mLockPatternUtils.getDeviceOwnerInfo(); - if (info == null) { - // Use the current user owner information if enabled. - final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled( - KeyguardUpdateMonitor.getCurrentUser()); - if (ownerInfoEnabled) { - info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser()); - } - } - if (info != null) { + if (shouldShowLogout) { mRotateTextViewController.updateIndication( - INDICATION_TYPE_OWNER_INFO, + INDICATION_TYPE_LOGOUT, new KeyguardIndication.Builder() - .setMessage(info) + .setMessage(mContext.getResources().getString( + com.android.internal.R.string.global_action_logout)) .setTextColor(mInitialTextColorState) + .setBackground(mContext.getDrawable( + com.android.systemui.R.drawable.logout_button_background)) + .setClickListener((view) -> { + int currentUserId = KeyguardUpdateMonitor.getCurrentUser(); + try { + mIActivityManager.switchUser(UserHandle.USER_SYSTEM); + mIActivityManager.stopUser(currentUserId, true /* force */, + null); + } catch (RemoteException re) { + Log.e(TAG, "Failed to logout user", re); + } + }) .build(), false); } else { - updateIndication(false); // resting indication may need to update + mRotateTextViewController.hideIndication(INDICATION_TYPE_LOGOUT); } } @@ -1042,7 +1049,6 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal @Override public void onUserSwitchComplete(int userId) { if (mVisible) { - updateOwnerInfo(); updateIndication(false); } } @@ -1057,7 +1063,7 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal @Override public void onLogoutEnabledChanged() { if (mVisible) { - updateLogoutView(); + updateIndication(false); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java index d562726681f1..138c811e9084 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java @@ -24,7 +24,6 @@ import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON; import android.content.Context; import android.content.res.ColorStateList; -import android.graphics.Color; import android.graphics.Rect; import android.util.AttributeSet; import android.util.FeatureFlagUtils; @@ -238,11 +237,7 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver, @Override public void setStaticDrawableColor(int color) { ColorStateList list = ColorStateList.valueOf(color); - float intensity = color == Color.WHITE ? 0 : 1; - // We want the ability to change the theme from the one set by SignalDrawable in certain - // surfaces. In this way, we can pass a theme to the view. - mMobileDrawable.setTintList( - ColorStateList.valueOf(mDualToneHandler.getSingleColor(intensity))); + mMobileDrawable.setTintList(list); mIn.setImageTintList(list); mOut.setImageTintList(list); mMobileType.setImageTintList(list); 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 8aadef817eb0..2f9fa9e6ec41 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -68,7 +68,6 @@ import android.view.WindowInsets; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; -import android.widget.TextView; import androidx.constraintlayout.widget.ConstraintSet; @@ -405,6 +404,7 @@ public class NotificationPanelViewController extends PanelViewController { // Used for two finger gesture as well as accessibility shortcut to QS. private boolean mQsExpandImmediate; private boolean mTwoFingerQsExpandPossible; + private String mHeaderDebugInfo; /** * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still @@ -3423,8 +3423,8 @@ public class NotificationPanelViewController extends PanelViewController { return mView.getHeight(); } - public TextView getHeaderDebugInfo() { - return mView.findViewById(R.id.header_debug_info); + public void setHeaderDebugInfo(String text) { + if (DEBUG) mHeaderDebugInfo = text; } public void onThemeChanged() { @@ -4087,6 +4087,8 @@ public class NotificationPanelViewController extends PanelViewController { p.setStrokeWidth(2); p.setStyle(Paint.Style.STROKE); canvas.drawLine(0, getMaxPanelHeight(), mView.getWidth(), getMaxPanelHeight(), p); + p.setTextSize(24); + if (mHeaderDebugInfo != null) canvas.drawText(mHeaderDebugInfo, 50, 100, p); p.setColor(Color.BLUE); canvas.drawLine(0, getExpandedHeight(), mView.getWidth(), getExpandedHeight(), p); p.setColor(Color.GREEN); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 024a0b17b5cc..525f2205f784 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -29,7 +29,6 @@ import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; import android.util.Log; import android.util.Slog; -import android.view.View; import android.view.accessibility.AccessibilityManager; import android.widget.TextView; @@ -162,11 +161,6 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); - if (MULTIUSER_DEBUG) { - mNotificationPanelDebugText = mNotificationPanel.getHeaderDebugInfo(); - mNotificationPanelDebugText.setVisibility(View.VISIBLE); - } - IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService( Context.VR_SERVICE)); if (vrManager != null) { @@ -332,7 +326,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, // Begin old BaseStatusBar.userSwitched mHeadsUpManager.setUser(newUserId); // End old BaseStatusBar.userSwitched - if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId); + if (MULTIUSER_DEBUG) mNotificationPanel.setHeaderDebugInfo("USER " + newUserId); mCommandQueue.animateCollapsePanels(); if (mReinflateNotificationsOnUserSwitched) { updateNotificationsOnDensityOrFontScaleChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java index 9e78a664d35f..0a3e83326e01 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java @@ -65,6 +65,8 @@ public class ThemeOverlayApplier implements Dumpable { "android.theme.customization.accent_color"; static final String OVERLAY_CATEGORY_SYSTEM_PALETTE = "android.theme.customization.system_palette"; + static final String OVERLAY_CATEGORY_NEUTRAL_PALETTE = + "android.theme.customization.neutral_palette"; @VisibleForTesting static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font"; @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 522a42b8d4b4..1f222d80f014 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -16,6 +16,7 @@ package com.android.systemui.theme; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE; import android.annotation.Nullable; @@ -83,8 +84,9 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { protected static final String TAG = "ThemeOverlayController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - protected static final int MAIN = 0; - protected static final int ACCENT = 1; + protected static final int PRIMARY = 0; + protected static final int SECONDARY = 1; + protected static final int NEUTRAL = 1; // If lock screen wallpaper colors should also be considered when selecting the theme. // Doing this has performance impact, given that overlays would need to be swapped when @@ -111,9 +113,11 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { // Accent color extracted from wallpaper, NOT the color used on the overlay protected int mWallpaperAccentColor = Color.TRANSPARENT; // System colors overlay - private FabricatedOverlay mSystemOverlay; + private FabricatedOverlay mPrimaryOverlay; // Accent colors overlay - private FabricatedOverlay mAccentOverlay; + private FabricatedOverlay mSecondaryOverlay; + // Neutral system colors overlay + private FabricatedOverlay mNeutralOverlay; @Inject public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher, @@ -232,12 +236,13 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { mWallpaperAccentColor = accentCandidate; if (mIsMonetEnabled) { - mSystemOverlay = getOverlay(mMainWallpaperColor, MAIN); - mAccentOverlay = getOverlay(mWallpaperAccentColor, ACCENT); + mPrimaryOverlay = getOverlay(mMainWallpaperColor, PRIMARY); + mSecondaryOverlay = getOverlay(mWallpaperAccentColor, SECONDARY); + mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL); mNeedsOverlayCreation = true; if (DEBUG) { - Log.d(TAG, "fetched overlays. system: " + mSystemOverlay + " accent: " - + mAccentOverlay); + Log.d(TAG, "fetched overlays. primary: " + mPrimaryOverlay + " secondary: " + + mSecondaryOverlay + " neutral: " + mNeutralOverlay); } } @@ -296,7 +301,9 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) { try { int color = Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16); - mSystemOverlay = getOverlay(color, MAIN); + mPrimaryOverlay = getOverlay(color, PRIMARY); + // Neutral palette is always derived from primary color. + mNeutralOverlay = getOverlay(color, NEUTRAL); mNeedsOverlayCreation = true; categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE); } catch (NumberFormatException e) { @@ -309,7 +316,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { if (mIsMonetEnabled && accentPalette != null && accentPalette.getPackageName() != null) { try { int color = Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16); - mAccentOverlay = getOverlay(color, ACCENT); + mSecondaryOverlay = getOverlay(color, SECONDARY); mNeedsOverlayCreation = true; categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR); } catch (NumberFormatException e) { @@ -320,12 +327,14 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { // Compatibility with legacy themes, where full packages were defined, instead of just // colors. if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE) - && mSystemOverlay != null) { - categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, mSystemOverlay.getIdentifier()); + && mPrimaryOverlay != null) { + categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, mPrimaryOverlay.getIdentifier()); + categoryToPackage.put(OVERLAY_CATEGORY_NEUTRAL_PALETTE, + mNeutralOverlay.getIdentifier()); } if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_ACCENT_COLOR) - && mAccentOverlay != null) { - categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mAccentOverlay.getIdentifier()); + && mSecondaryOverlay != null) { + categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mSecondaryOverlay.getIdentifier()); } Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser)); @@ -342,7 +351,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { if (mNeedsOverlayCreation) { mNeedsOverlayCreation = false; mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] { - mSystemOverlay, mAccentOverlay + mPrimaryOverlay, mSecondaryOverlay, mNeutralOverlay }, userHandles); } else { mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, userHandles); @@ -356,8 +365,9 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { pw.println("mSystemColors=" + mSystemColors); pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor)); pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor)); - pw.println("mSystemOverlayColor=" + mSystemOverlay); - pw.println("mAccentOverlayColor=" + mAccentOverlay); + pw.println("mPrimaryOverlay=" + mPrimaryOverlay); + pw.println("mSecondaryOverlay=" + mSecondaryOverlay); + pw.println("mNeutralOverlay=" + mNeutralOverlay); pw.println("mIsMonetEnabled=" + mIsMonetEnabled); pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java new file mode 100644 index 000000000000..b44fb8e61c32 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java @@ -0,0 +1,110 @@ +/* + * 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.keyguard; + +import static android.graphics.Color.WHITE; + +import static org.junit.Assert.assertEquals; + +import android.content.res.ColorStateList; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.drawable.Drawable; +import android.testing.AndroidTestingRunner; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class KeyguardIndicationTest extends SysuiTestCase { + + @Test(expected = IllegalStateException.class) + public void testCannotCreateIndicationWithoutMessageOrIcon() { + new KeyguardIndication.Builder() + .setTextColor(ColorStateList.valueOf(WHITE)) + .build(); + } + + @Test(expected = IllegalStateException.class) + public void testCannotCreateIndicationWithoutColor() { + new KeyguardIndication.Builder() + .setMessage("message") + .build(); + } + + @Test(expected = IllegalStateException.class) + public void testCannotCreateIndicationWithEmptyMessage() { + new KeyguardIndication.Builder() + .setMessage("") + .setTextColor(ColorStateList.valueOf(WHITE)) + .build(); + } + + @Test + public void testCreateIndicationWithMessage() { + final String text = "regular indication"; + final KeyguardIndication indication = new KeyguardIndication.Builder() + .setMessage(text) + .setTextColor(ColorStateList.valueOf(WHITE)) + .build(); + assertEquals(text, indication.getMessage()); + } + + @Test + public void testCreateIndicationWithIcon() { + final KeyguardIndication indication = new KeyguardIndication.Builder() + .setIcon(mDrawable) + .setTextColor(ColorStateList.valueOf(WHITE)) + .build(); + assertEquals(mDrawable, indication.getIcon()); + } + + @Test + public void testCreateIndicationWithMessageAndIcon() { + final String text = "indication with msg and icon"; + final KeyguardIndication indication = new KeyguardIndication.Builder() + .setMessage(text) + .setIcon(mDrawable) + .setTextColor(ColorStateList.valueOf(WHITE)) + .build(); + assertEquals(text, indication.getMessage()); + assertEquals(mDrawable, indication.getIcon()); + } + + final Drawable mDrawable = new Drawable() { + @Override + public void draw(@NonNull Canvas canvas) { } + + @Override + public void setAlpha(int alpha) { } + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) { } + + @Override + public int getOpacity() { + return 0; + } + }; +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index f7f8d03da1c2..aa385effa931 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.theme; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR; +import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE; import static com.android.systemui.theme.ThemeOverlayController.USE_LOCK_SCREEN_WALLPAPER; @@ -147,6 +148,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) .isEqualTo(new OverlayIdentifier("ffff0000")); + assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_NEUTRAL_PALETTE)) + .isEqualTo(new OverlayIdentifier("ffff0000")); assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR)) .isEqualTo(new OverlayIdentifier("ff0000ff")); diff --git a/packages/services/CameraExtensionsProxy/Android.bp b/packages/services/CameraExtensionsProxy/Android.bp index 54b7453da585..e2e4af2a35f4 100644 --- a/packages/services/CameraExtensionsProxy/Android.bp +++ b/packages/services/CameraExtensionsProxy/Android.bp @@ -18,6 +18,7 @@ android_app { name: "CameraExtensionsProxy", srcs: ["src/**/*.java"], libs: ["androidx.camera.extensions.stub"], + optional_uses_libs: ["androidx.camera.extensions.impl"], platform_apis: true, certificate: "platform", } diff --git a/services/Android.bp b/services/Android.bp index 315462838485..8aae8e684bc5 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -6,7 +6,7 @@ filegroup { } filegroup { - name: "services-all-sources", + name: "services-non-updatable-sources", srcs: [ ":services.core-sources", ":services.core-sources-am-wm", @@ -33,12 +33,19 @@ filegroup { ":services.startop.iorap-sources", ":services.systemcaptions-sources", ":services.translation-sources", - ":services.texttospeech-sources", ":services.usage-sources", ":services.usb-sources", ":services.voiceinteraction-sources", ":services.wifi-sources", - ":service-media-s-sources", // TODO (b/177640454) + ], + visibility: ["//visibility:private"], +} + +filegroup { + name: "services-all-sources", + srcs: [ + ":services-non-updatable-sources", + ":service-media-s-sources", ":service-permission-sources", ":service-statsd-sources", ], @@ -84,7 +91,6 @@ java_library { "services.startop", "services.systemcaptions", "services.translation", - "services.texttospeech", "services.usage", "services.usb", "services.voiceinteraction", @@ -125,9 +131,8 @@ filegroup { // API stub // ============================================================= -droidstubs { - name: "services-stubs.sources", - srcs: [":services-all-sources"], +stubs_defaults { + name: "services-stubs-default", installable: false, args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\)" + " --hide-annotation android.annotation.Hide" + @@ -137,7 +142,13 @@ droidstubs { " --hide DeprecationMismatch" + " --hide HiddenTypedefConstant", visibility: ["//visibility:private"], - filter_packages: ["com.android."], + filter_packages: ["com.android."] +} + +droidstubs { + name: "services-stubs.sources", + srcs: [":services-all-sources"], + defaults: ["services-stubs-default"], check_api: { current: { api_file: "api/current.txt", @@ -183,3 +194,34 @@ java_library { dir: "apistubs/android/system-server", }, } + +droidstubs { + name: "services-non-updatable-stubs.sources", + srcs: [":services-non-updatable-sources"], + defaults: ["services-stubs-default"], + check_api: { + current: { + api_file: "api/non-updatable-current.txt", + removed_api_file: "api/non-updatable-removed.txt", + }, + api_lint: { + enabled: true, + new_since: ":android-non-updatable.api.system-server.latest", + baseline_file: "api/non-updatable-lint-baseline.txt", + }, + }, + dists: [ + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system-server/api", + dest: "android-non-updatable.txt", + tag: ".api.txt" + }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system-server/api", + dest: "android-non-updatable-removed.txt", + tag: ".removed-api.txt", + }, + ] +}
\ No newline at end of file diff --git a/services/api/non-updatable-current.txt b/services/api/non-updatable-current.txt new file mode 100644 index 000000000000..3c72d38927bc --- /dev/null +++ b/services/api/non-updatable-current.txt @@ -0,0 +1,55 @@ +// Signature format: 2.0 +package com.android.server { + + public final class LocalManagerRegistry { + method public static <T> void addManager(@NonNull Class<T>, @NonNull T); + method @Nullable public static <T> T getManager(@NonNull Class<T>); + } + + public abstract class SystemService { + ctor public SystemService(@NonNull android.content.Context); + method @NonNull public final android.content.Context getContext(); + method public boolean isUserSupported(@NonNull com.android.server.SystemService.TargetUser); + method public void onBootPhase(int); + method public abstract void onStart(); + method public void onUserStarting(@NonNull com.android.server.SystemService.TargetUser); + method public void onUserStopped(@NonNull com.android.server.SystemService.TargetUser); + method public void onUserStopping(@NonNull com.android.server.SystemService.TargetUser); + method public void onUserSwitching(@Nullable com.android.server.SystemService.TargetUser, @NonNull com.android.server.SystemService.TargetUser); + method public void onUserUnlocked(@NonNull com.android.server.SystemService.TargetUser); + method public void onUserUnlocking(@NonNull com.android.server.SystemService.TargetUser); + method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder); + method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder, boolean); + field public static final int PHASE_ACTIVITY_MANAGER_READY = 550; // 0x226 + field public static final int PHASE_BOOT_COMPLETED = 1000; // 0x3e8 + field public static final int PHASE_DEVICE_SPECIFIC_SERVICES_READY = 520; // 0x208 + field public static final int PHASE_LOCK_SETTINGS_READY = 480; // 0x1e0 + field public static final int PHASE_SYSTEM_SERVICES_READY = 500; // 0x1f4 + field public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600; // 0x258 + field public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // 0x64 + } + + public static final class SystemService.TargetUser { + method @NonNull public android.os.UserHandle getUserHandle(); + } + +} + +package com.android.server.role { + + public interface RoleServicePlatformHelper { + method @NonNull public String computePackageStateHash(int); + method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getLegacyRoleState(int); + } + +} + +package com.android.server.wifi { + + public class SupplicantManager { + method public static void start(); + method public static void stop(); + } + +} + diff --git a/services/api/non-updatable-lint-baseline.txt b/services/api/non-updatable-lint-baseline.txt new file mode 100644 index 000000000000..b46d21edd44c --- /dev/null +++ b/services/api/non-updatable-lint-baseline.txt @@ -0,0 +1,9 @@ +// Baseline format: 1.0 +NotCloseable: com.android.server.wifi.SupplicantManager: + Classes that release resources (stop()) should implement AutoClosable and CloseGuard: class com.android.server.wifi.SupplicantManager + + +ProtectedMember: com.android.server.SystemService#publishBinderService(String, android.os.IBinder): + Protected methods not allowed; must be public: method com.android.server.SystemService.publishBinderService(String,android.os.IBinder)} +ProtectedMember: com.android.server.SystemService#publishBinderService(String, android.os.IBinder, boolean): + Protected methods not allowed; must be public: method com.android.server.SystemService.publishBinderService(String,android.os.IBinder,boolean)} diff --git a/services/api/non-updatable-removed.txt b/services/api/non-updatable-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/services/api/non-updatable-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 10b00d38fb42..76c8d3001158 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -257,6 +257,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind new PackageMonitor() { @Override public void onPackageRemoved(String packageName, int uid) { + Slog.d(LOG_TAG, "onPackageRemoved(packageName = " + packageName + + ", uid = " + uid + ")"); int userId = getChangingUserId(); updateAssociations( as -> CollectionUtils.filter(as, @@ -268,6 +270,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind @Override public void onPackageModified(String packageName) { + Slog.d(LOG_TAG, "onPackageModified(packageName = " + packageName + ")"); int userId = getChangingUserId(); forEach(getAllAssociations(userId, packageName), association -> { updateSpecialAccessPermissionForAssociatedPackage(association); @@ -304,7 +307,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind mBleStateBroadcastReceiver, mBleStateBroadcastReceiver.mIntentFilter); initBleScanning(); } else { - Log.w(LOG_TAG, "No BluetoothAdapter available"); + Slog.w(LOG_TAG, "No BluetoothAdapter available"); } } } @@ -324,6 +327,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } void maybeGrantAutoRevokeExemptions() { + Slog.d(LOG_TAG, "maybeGrantAutoRevokeExemptions()"); PackageManager pm = getContext().getPackageManager(); for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) { SharedPreferences pref = getContext().getSharedPreferences( @@ -343,7 +347,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind int uid = pm.getPackageUidAsUser(a.getPackageName(), userId); exemptFromAutoRevoke(a.getPackageName(), uid); } catch (PackageManager.NameNotFoundException e) { - Log.w(LOG_TAG, "Unknown companion package: " + a.getPackageName(), e); + Slog.w(LOG_TAG, "Unknown companion package: " + a.getPackageName(), e); } } } finally { @@ -354,10 +358,13 @@ public class CompanionDeviceManagerService extends SystemService implements Bind @Override public void binderDied() { + Slog.w(LOG_TAG, "binderDied()"); mMainHandler.post(this::cleanup); } private void cleanup() { + Slog.d(LOG_TAG, "cleanup(); discovery = " + + mOngoingDeviceDiscovery + ", request = " + mRequest); synchronized (mLock) { AndroidFuture<Association> ongoingDeviceDiscovery = mOngoingDeviceDiscovery; if (ongoingDeviceDiscovery != null && !ongoingDeviceDiscovery.isDone()) { @@ -400,10 +407,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind AssociationRequest request, IFindDeviceCallback callback, String callingPackage) throws RemoteException { - if (DEBUG) { - Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback - + ", callingPackage = " + callingPackage + ")"); - } + Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback + + ", callingPackage = " + callingPackage + ")"); checkNotNull(request, "Request cannot be null"); checkNotNull(callback, "Callback cannot be null"); checkCallerIsSystemOr(callingPackage); @@ -423,9 +428,13 @@ public class CompanionDeviceManagerService extends SystemService implements Bind request.getDeviceProfile()); mOngoingDeviceDiscovery = fetchProfileDescription.thenComposeAsync(description -> { + Slog.d(LOG_TAG, "fetchProfileDescription done: " + description); + request.setDeviceProfilePrivilegesDescription(description); return mServiceConnectors.forUser(userId).postAsync(service -> { + Slog.d(LOG_TAG, "Connected to CDM service; starting discovery for " + request); + AndroidFuture<Association> future = new AndroidFuture<>(); service.startDiscovery(request, callingPackage, callback, future); return future; @@ -438,7 +447,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind if (err == null) { addAssociation(association); } else { - Log.e(LOG_TAG, "Failed to discover device(s)", err); + Slog.e(LOG_TAG, "Failed to discover device(s)", err); callback.onFailure("No devices found: " + err.getMessage()); } cleanup(); @@ -452,6 +461,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind public void stopScan(AssociationRequest request, IFindDeviceCallback callback, String callingPackage) { + Slog.d(LOG_TAG, "stopScan(request = " + request + ")"); if (Objects.equals(request, mRequest) && Objects.equals(callback, mFindDeviceCallback) && Objects.equals(callingPackage, mCallingPackage)) { @@ -712,7 +722,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind getAllAssociations(association.getUserId()), a -> !a.equals(association) && deviceProfile.equals(a.getDeviceProfile())); if (otherAssociationWithDeviceProfile != null) { - Log.i(LOG_TAG, "Not revoking " + deviceProfile + Slog.i(LOG_TAG, "Not revoking " + deviceProfile + " for " + association + " - profile still present in " + otherAssociationWithDeviceProfile); } else { @@ -726,7 +736,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind getContext().getMainExecutor(), success -> { if (!success) { - Log.e(LOG_TAG, "Failed to revoke device profile role " + Slog.e(LOG_TAG, "Failed to revoke device profile role " + association.getDeviceProfile() + " to " + association.getPackageName() + " for user " + association.getUserId()); @@ -794,7 +804,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind packageName, AppOpsManager.MODE_IGNORED); } catch (RemoteException e) { - Log.w(LOG_TAG, + Slog.w(LOG_TAG, "Error while granting auto revoke exemption for " + packageName, e); } } @@ -819,9 +829,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } private void recordAssociation(Association association) { - if (DEBUG) { - Log.i(LOG_TAG, "recordAssociation(" + association + ")"); - } + Slog.i(LOG_TAG, "recordAssociation(" + association + ")"); updateAssociations(associations -> CollectionUtils.add(associations, association)); } @@ -835,9 +843,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind final Set<Association> old = getAllAssociations(userId); Set<Association> associations = new ArraySet<>(old); associations = update.apply(associations); - if (DEBUG) { - Slog.i(LOG_TAG, "Updating associations: " + old + " --> " + associations); - } + Slog.i(LOG_TAG, "Updating associations: " + old + " --> " + associations); mCachedAssociations.put(userId, Collections.unmodifiableSet(associations)); BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage( CompanionDeviceManagerService::persistAssociations, @@ -866,9 +872,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } private void persistAssociations(Set<Association> associations, int userId) { - if (DEBUG) { - Slog.i(LOG_TAG, "Writing associations to disk: " + associations); - } + Slog.i(LOG_TAG, "Writing associations to disk: " + associations); final AtomicFile file = getStorageFileForUser(userId); synchronized (file) { file.write(out -> { @@ -919,9 +923,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind if (mCachedAssociations.get(userId) == null) { mCachedAssociations.put(userId, Collections.unmodifiableSet( emptyIfNull(readAllAssociations(userId)))); - if (DEBUG) { - Slog.i(LOG_TAG, "Read associations from disk: " + mCachedAssociations); - } + Slog.i(LOG_TAG, "Read associations from disk: " + mCachedAssociations); } return mCachedAssociations.get(userId); } @@ -1002,13 +1004,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } void onDeviceConnected(String address) { + Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")"); + mCurrentlyConnectedDevices.add(address); for (UserInfo user : getAllUsers()) { for (Association association : getAllAssociations(user.id)) { if (Objects.equals(address, association.getDeviceMacAddress())) { if (association.getDeviceProfile() != null) { - Log.i(LOG_TAG, "Granting role " + association.getDeviceProfile() + Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile() + " to " + association.getPackageName() + " due to device connected: " + association.getDeviceMacAddress()); grantDeviceProfile(association); @@ -1021,6 +1025,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } private void grantDeviceProfile(Association association) { + Slog.i(LOG_TAG, "grantDeviceProfile(association = " + association + ")"); + if (association.getDeviceProfile() != null) { mRoleManager.addRoleHolderAsUser( association.getDeviceProfile(), @@ -1030,7 +1036,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind getContext().getMainExecutor(), success -> { if (!success) { - Log.e(LOG_TAG, "Failed to grant device profile role " + Slog.e(LOG_TAG, "Failed to grant device profile role " + association.getDeviceProfile() + " to " + association.getPackageName() + " for user " + association.getUserId()); @@ -1040,6 +1046,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } void onDeviceDisconnected(String address) { + Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")"); + mCurrentlyConnectedDevices.remove(address); onDeviceDisappeared(address); @@ -1059,13 +1067,13 @@ public class CompanionDeviceManagerService extends SystemService implements Bind List<ResolveInfo> packageResolveInfos = filter(resolveInfos, info -> Objects.equals(info.serviceInfo.packageName, a.getPackageName())); if (packageResolveInfos.size() != 1) { - Log.w(LOG_TAG, "Device presence listener package must have exactly one " + Slog.w(LOG_TAG, "Device presence listener package must have exactly one " + "CompanionDeviceService, but " + a.getPackageName() + " has " + packageResolveInfos.size()); return new ServiceConnector.NoOp<>(); } ComponentName componentName = packageResolveInfos.get(0).serviceInfo.getComponentName(); - Log.i(LOG_TAG, "Initializing CompanionDeviceService binding for " + componentName); + Slog.i(LOG_TAG, "Initializing CompanionDeviceService binding for " + componentName); return new ServiceConnector.Impl<>(getContext(), new Intent(CompanionDeviceService.SERVICE_INTERFACE).setComponent(componentName), BIND_IMPORTANT, @@ -1077,7 +1085,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind @Override public void onScanResult(int callbackType, ScanResult result) { if (DEBUG) { - Log.i(LOG_TAG, "onScanResult(callbackType = " + Slog.i(LOG_TAG, "onScanResult(callbackType = " + callbackType + ", result = " + result + ")"); } @@ -1096,9 +1104,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind if (errorCode == SCAN_FAILED_ALREADY_STARTED) { // ignore - this might happen if BT tries to auto-restore scans for us in the // future - Log.i(LOG_TAG, "Ignoring BLE scan error: SCAN_FAILED_ALREADY_STARTED"); + Slog.i(LOG_TAG, "Ignoring BLE scan error: SCAN_FAILED_ALREADY_STARTED"); } else { - Log.w(LOG_TAG, "Failed to start BLE scan: error " + errorCode); + Slog.w(LOG_TAG, "Failed to start BLE scan: error " + errorCode); } } } @@ -1112,7 +1120,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind public void onReceive(Context context, Intent intent) { int previousState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, -1); int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); - Log.i(LOG_TAG, "Received BT state transition broadcast: " + Slog.d(LOG_TAG, "Received BT state transition broadcast: " + BluetoothAdapter.nameForState(previousState) + " -> " + BluetoothAdapter.nameForState(newState)); @@ -1122,7 +1130,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind if (mBluetoothAdapter.getBluetoothLeScanner() != null) { startBleScan(); } else { - Log.wtf(LOG_TAG, "BLE on, but BluetoothLeScanner == null"); + Slog.wtf(LOG_TAG, "BLE on, but BluetoothLeScanner == null"); } } } @@ -1136,6 +1144,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind @Override public void run() { + Slog.i(LOG_TAG, "UnbindDeviceListenersRunnable.run(); devicesNearby = " + + mDevicesLastNearby); int size = mDevicesLastNearby.size(); for (int i = 0; i < size; i++) { String address = mDevicesLastNearby.keyAt(i); @@ -1162,12 +1172,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } public void schedule() { + Slog.d(LOG_TAG, + "TriggerDeviceDisappearedRunnable.schedule(address = " + mAddress + ")"); mMainHandler.removeCallbacks(this); mMainHandler.postDelayed(this, this, DEVICE_DISAPPEARED_TIMEOUT_MS); } @Override public void run() { + Slog.d(LOG_TAG, "TriggerDeviceDisappearedRunnable.run(address = " + mAddress + ")"); onDeviceDisappeared(mAddress); } } @@ -1187,6 +1200,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } private void onDeviceNearby(String address) { + Slog.i(LOG_TAG, "onDeviceNearby(address = " + address + ")"); + Date timestamp = new Date(); Date oldTimestamp = mDevicesLastNearby.put(address, timestamp); @@ -1203,7 +1218,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind for (Association association : getAllAssociations(address)) { if (association.isNotifyOnDeviceNearby()) { if (DEBUG) { - Log.i(LOG_TAG, "Device " + address + Slog.i(LOG_TAG, "Device " + address + " managed by " + association.getPackageName() + " is nearby on " + timestamp); } @@ -1215,11 +1230,13 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } private void onDeviceDisappeared(String address) { + Slog.i(LOG_TAG, "onDeviceDisappeared(address = " + address + ")"); + boolean hasDeviceListeners = false; for (Association association : getAllAssociations(address)) { if (association.isNotifyOnDeviceNearby()) { if (DEBUG) { - Log.i(LOG_TAG, "Device " + address + Slog.i(LOG_TAG, "Device " + address + " managed by " + association.getPackageName() + " disappeared; last seen on " + mDevicesLastNearby.get(address)); } @@ -1245,19 +1262,19 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } private void initBleScanning() { - Log.i(LOG_TAG, "initBleScanning()"); + Slog.i(LOG_TAG, "initBleScanning()"); boolean bluetoothReady = mBluetoothAdapter.registerServiceLifecycleCallback( new BluetoothAdapter.ServiceLifecycleCallback() { @Override public void onBluetoothServiceUp() { - Log.i(LOG_TAG, "Bluetooth stack is up"); + Slog.i(LOG_TAG, "Bluetooth stack is up"); startBleScan(); } @Override public void onBluetoothServiceDown() { - Log.w(LOG_TAG, "Bluetooth stack is down"); + Slog.w(LOG_TAG, "Bluetooth stack is down"); } }); if (bluetoothReady) { @@ -1266,7 +1283,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } void startBleScan() { - Log.i(LOG_TAG, "startBleScan()"); + Slog.i(LOG_TAG, "startBleScan()"); List<ScanFilter> filters = getBleScanFilters(); if (filters.isEmpty()) { @@ -1274,7 +1291,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); if (scanner == null) { - Log.w(LOG_TAG, "scanner == null (likely BLE isn't ON yet)"); + Slog.w(LOG_TAG, "scanner == null (likely BLE isn't ON yet)"); } else { scanner.startScan( filters, @@ -1321,7 +1338,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind try { return Long.parseLong(str); } catch (NumberFormatException e) { - Log.w(LOG_TAG, "Failed to parse", e); + Slog.w(LOG_TAG, "Failed to parse", e); return def; } } @@ -1380,7 +1397,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } return 0; } catch (Throwable t) { - Log.e(LOG_TAG, "Error running a command: $ " + cmd, t); + Slog.e(LOG_TAG, "Error running a command: $ " + cmd, t); getErrPrintWriter().println(Log.getStackTraceString(t)); return 1; } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 154e1831ceee..5077cc622934 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -3721,7 +3721,12 @@ public class ConnectivityService extends IConnectivityManager.Stub // Looking up the app passed param request in mRequests isn't possible since it may return // null for a request managed by a per-app default. Therefore use getNriForAppRequest() to // do the lookup since that will also find per-app default managed requests. - final NetworkRequestInfo nri = getNriForAppRequest(request); + // Additionally, this lookup needs to be relatively fast (hence the lookup optimization) + // to avoid potential race conditions when validating a package->uid mapping when sending + // the callback on the very low-chance that an application shuts down prior to the callback + // being sent. + final NetworkRequestInfo nri = mNetworkRequests.get(request) != null + ? mNetworkRequests.get(request) : getNriForAppRequest(request); if (nri != null) { if (Process.SYSTEM_UID != callingUid && Process.NETWORK_STACK_UID != callingUid diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 27210daac241..329ab9983c90 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -22,6 +22,7 @@ import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubsc import static java.util.Objects.requireNonNull; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AppOpsManager; import android.content.Context; import android.net.ConnectivityManager; @@ -29,8 +30,10 @@ import android.net.LinkProperties; import android.net.NetworkCapabilities; import android.net.TelephonyNetworkSpecifier; import android.net.vcn.IVcnManagementService; +import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; +import android.net.vcn.VcnManager.VcnErrorCode; import android.net.vcn.VcnUnderlyingNetworkPolicy; import android.net.wifi.WifiInfo; import android.os.Binder; @@ -54,6 +57,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.internal.util.LocationPermissionChecker; import com.android.server.vcn.TelephonySubscriptionTracker; import com.android.server.vcn.Vcn; import com.android.server.vcn.VcnContext; @@ -124,6 +128,7 @@ import java.util.concurrent.TimeUnit; * * @hide */ +// TODO(b/180451994): ensure all incoming + outgoing calls have a cleared calling identity public class VcnManagementService extends IVcnManagementService.Stub { @NonNull private static final String TAG = VcnManagementService.class.getSimpleName(); @@ -147,6 +152,9 @@ public class VcnManagementService extends IVcnManagementService.Stub { @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker; @NonNull private final VcnContext mVcnContext; + /** Can only be assigned when {@link #systemReady()} is called, since it uses AppOpsManager. */ + @Nullable private LocationPermissionChecker mLocationPermissionChecker; + @GuardedBy("mLock") @NonNull private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>(); @@ -169,6 +177,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { private final Map<IBinder, PolicyListenerBinderDeath> mRegisteredPolicyListeners = new ArrayMap<>(); + @GuardedBy("mLock") + @NonNull + private final Map<IBinder, VcnStatusCallbackInfo> mRegisteredStatusCallbacks = new ArrayMap<>(); + @VisibleForTesting(visibility = Visibility.PRIVATE) VcnManagementService(@NonNull Context context, @NonNull Dependencies deps) { mContext = requireNonNull(context, "Missing context"); @@ -293,8 +305,8 @@ public class VcnManagementService extends IVcnManagementService.Stub { @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, @NonNull TelephonySubscriptionSnapshot snapshot, - @NonNull VcnSafemodeCallback safemodeCallback) { - return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safemodeCallback); + @NonNull VcnCallback vcnCallback) { + return new Vcn(vcnContext, subscriptionGroup, config, snapshot, vcnCallback); } /** Gets the subId indicated by the given {@link WifiInfo}. */ @@ -302,6 +314,11 @@ public class VcnManagementService extends IVcnManagementService.Stub { // TODO(b/178501049): use the subId indicated by WifiInfo#getSubscriptionId return SubscriptionManager.INVALID_SUBSCRIPTION_ID; } + + /** Creates a new LocationPermissionChecker for the provided Context. */ + public LocationPermissionChecker newLocationPermissionChecker(@NonNull Context context) { + return new LocationPermissionChecker(context); + } } /** Notifies the VcnManagementService that external dependencies can be set up. */ @@ -309,6 +326,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { mContext.getSystemService(ConnectivityManager.class) .registerNetworkProvider(mNetworkProvider); mTelephonySubscriptionTracker.register(); + mLocationPermissionChecker = mDeps.newLocationPermissionChecker(mVcnContext.getContext()); } private void enforcePrimaryUser() { @@ -440,12 +458,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active // VCN. - final VcnSafemodeCallbackImpl safemodeCallback = - new VcnSafemodeCallbackImpl(subscriptionGroup); + final VcnCallbackImpl vcnCallback = new VcnCallbackImpl(subscriptionGroup); final Vcn newInstance = - mDeps.newVcn( - mVcnContext, subscriptionGroup, config, mLastSnapshot, safemodeCallback); + mDeps.newVcn(mVcnContext, subscriptionGroup, config, mLastSnapshot, vcnCallback); mVcns.put(subscriptionGroup, newInstance); // Now that a new VCN has started, notify all registered listeners to refresh their @@ -551,6 +567,14 @@ public class VcnManagementService extends IVcnManagementService.Stub { } } + /** Get current VcnStatusCallbacks for testing purposes. */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public Map<IBinder, VcnStatusCallbackInfo> getAllStatusCallbacks() { + synchronized (mLock) { + return Collections.unmodifiableMap(mRegisteredStatusCallbacks); + } + } + /** Binder death recipient used to remove a registered policy listener. */ private class PolicyListenerBinderDeath implements Binder.DeathRecipient { @NonNull private final IVcnUnderlyingNetworkPolicyListener mListener; @@ -672,22 +696,136 @@ public class VcnManagementService extends IVcnManagementService.Stub { return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities); } - /** Callback for signalling when a Vcn has entered Safemode. */ - public interface VcnSafemodeCallback { - /** Called by a Vcn to signal that it has entered Safemode. */ - void onEnteredSafemode(); + /** Binder death recipient used to remove registered VcnStatusCallbacks. */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + class VcnStatusCallbackInfo implements Binder.DeathRecipient { + @NonNull final ParcelUuid mSubGroup; + @NonNull final IVcnStatusCallback mCallback; + @NonNull final String mPkgName; + final int mUid; + + private VcnStatusCallbackInfo( + @NonNull ParcelUuid subGroup, + @NonNull IVcnStatusCallback callback, + @NonNull String pkgName, + int uid) { + mSubGroup = subGroup; + mCallback = callback; + mPkgName = pkgName; + mUid = uid; + } + + @Override + public void binderDied() { + Log.e(TAG, "app died without unregistering VcnStatusCallback"); + unregisterVcnStatusCallback(mCallback); + } + } + + /** Registers the provided callback for receiving VCN status updates. */ + @Override + public void registerVcnStatusCallback( + @NonNull ParcelUuid subGroup, + @NonNull IVcnStatusCallback callback, + @NonNull String opPkgName) { + final int callingUid = mDeps.getBinderCallingUid(); + final long identity = Binder.clearCallingIdentity(); + try { + requireNonNull(subGroup, "subGroup must not be null"); + requireNonNull(callback, "callback must not be null"); + requireNonNull(opPkgName, "opPkgName must not be null"); + + mContext.getSystemService(AppOpsManager.class).checkPackage(callingUid, opPkgName); + + final IBinder cbBinder = callback.asBinder(); + final VcnStatusCallbackInfo cbInfo = + new VcnStatusCallbackInfo( + subGroup, callback, opPkgName, mDeps.getBinderCallingUid()); + + try { + cbBinder.linkToDeath(cbInfo, 0 /* flags */); + } catch (RemoteException e) { + // Remote binder already died - don't add to mRegisteredStatusCallbacks and exit + return; + } + + synchronized (mLock) { + if (mRegisteredStatusCallbacks.containsKey(cbBinder)) { + throw new IllegalStateException( + "Attempting to register a callback that is already in use"); + } + + mRegisteredStatusCallbacks.put(cbBinder, cbInfo); + } + } finally { + Binder.restoreCallingIdentity(identity); + } } - /** VcnSafemodeCallback is used by Vcns to notify VcnManagementService on entering Safemode. */ - private class VcnSafemodeCallbackImpl implements VcnSafemodeCallback { + /** Unregisters the provided callback from receiving future VCN status updates. */ + @Override + public void unregisterVcnStatusCallback(@NonNull IVcnStatusCallback callback) { + final long identity = Binder.clearCallingIdentity(); + try { + requireNonNull(callback, "callback must not be null"); + + final IBinder cbBinder = callback.asBinder(); + synchronized (mLock) { + VcnStatusCallbackInfo cbInfo = mRegisteredStatusCallbacks.remove(cbBinder); + + if (cbInfo != null) { + cbBinder.unlinkToDeath(cbInfo, 0 /* flags */); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + // TODO(b/180452282): Make name more generic and implement directly with VcnManagementService + /** Callback for Vcn signals sent up to VcnManagementService. */ + public interface VcnCallback { + /** Called by a Vcn to signal that it has entered safe mode. */ + void onEnteredSafeMode(); + + /** Called by a Vcn to signal that an error occurred. */ + void onGatewayConnectionError( + @NonNull int[] networkCapabilities, + @VcnErrorCode int errorCode, + @Nullable String exceptionClass, + @Nullable String exceptionMessage); + } + + /** VcnCallbackImpl for Vcn signals sent up to VcnManagementService. */ + private class VcnCallbackImpl implements VcnCallback { @NonNull private final ParcelUuid mSubGroup; - private VcnSafemodeCallbackImpl(@NonNull final ParcelUuid subGroup) { + private VcnCallbackImpl(@NonNull final ParcelUuid subGroup) { mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup"); } + private boolean isCallbackPermissioned(@NonNull VcnStatusCallbackInfo cbInfo) { + if (!mSubGroup.equals(cbInfo.mSubGroup)) { + return false; + } + + if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup( + mSubGroup, cbInfo.mPkgName)) { + return false; + } + + if (!mLocationPermissionChecker.checkLocationPermission( + cbInfo.mPkgName, + "VcnStatusCallback" /* featureId */, + cbInfo.mUid, + null /* message */)) { + return false; + } + return true; + } + @Override - public void onEnteredSafemode() { + public void onEnteredSafeMode() { synchronized (mLock) { // Ignore if this subscription group doesn't exist anymore if (!mVcns.containsKey(mSubGroup)) { @@ -695,6 +833,40 @@ public class VcnManagementService extends IVcnManagementService.Stub { } notifyAllPolicyListenersLocked(); + + // Notify all registered StatusCallbacks for this subGroup + for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) { + if (isCallbackPermissioned(cbInfo)) { + Binder.withCleanCallingIdentity(() -> cbInfo.mCallback.onEnteredSafeMode()); + } + } + } + } + + @Override + public void onGatewayConnectionError( + @NonNull int[] networkCapabilities, + @VcnErrorCode int errorCode, + @Nullable String exceptionClass, + @Nullable String exceptionMessage) { + synchronized (mLock) { + // Ignore if this subscription group doesn't exist anymore + if (!mVcns.containsKey(mSubGroup)) { + return; + } + + // Notify all registered StatusCallbacks for this subGroup + for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) { + if (isCallbackPermissioned(cbInfo)) { + Binder.withCleanCallingIdentity( + () -> + cbInfo.mCallback.onGatewayConnectionError( + networkCapabilities, + errorCode, + exceptionClass, + exceptionMessage)); + } + } } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 0b1c1154ba75..5ee0e040019c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -130,6 +130,7 @@ import static com.android.server.wm.ActivityTaskManagerService.DUMP_LASTANR_TRAC import static com.android.server.wm.ActivityTaskManagerService.DUMP_RECENTS_CMD; import static com.android.server.wm.ActivityTaskManagerService.DUMP_RECENTS_SHORT_CMD; import static com.android.server.wm.ActivityTaskManagerService.DUMP_STARTER_CMD; +import static com.android.server.wm.ActivityTaskManagerService.DUMP_TOP_RESUMED_ACTIVITY; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; import static com.android.server.wm.ActivityTaskManagerService.relaunchReasonToString; @@ -353,6 +354,7 @@ import com.android.server.contentcapture.ContentCaptureManagerInternal; import com.android.server.firewall.IntentFirewall; import com.android.server.graphics.fonts.FontManagerInternal; import com.android.server.job.JobSchedulerInternal; +import com.android.server.os.NativeTombstoneManager; import com.android.server.pm.Installer; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.uri.GrantUri; @@ -8273,6 +8275,9 @@ public class ActivityManagerService extends IActivityManager.Stub mUserController.handleIncomingUser(callingPid, callingUid, userId, true, ALLOW_NON_FULL, "getHistoricalProcessExitReasons", null); + NativeTombstoneManager tombstoneService = LocalServices.getService( + NativeTombstoneManager.class); + final ArrayList<ApplicationExitInfo> results = new ArrayList<ApplicationExitInfo>(); if (!TextUtils.isEmpty(packageName)) { final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid, @@ -8280,11 +8285,13 @@ public class ActivityManagerService extends IActivityManager.Stub if (uid != Process.INVALID_UID) { mProcessList.mAppExitInfoTracker.getExitInfo( packageName, uid, pid, maxNum, results); + tombstoneService.collectTombstones(results, uid, pid, maxNum); } } else { // If no package name is given, use the caller's uid as the filter uid. mProcessList.mAppExitInfoTracker.getExitInfo( packageName, callingUid, pid, maxNum, results); + tombstoneService.collectTombstones(results, callingUid, pid, maxNum); } return new ParceledListSlice<ApplicationExitInfo>(results); @@ -8668,7 +8675,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (DUMP_ACTIVITIES_CMD.equals(cmd) || DUMP_ACTIVITIES_SHORT_CMD.equals(cmd) || DUMP_LASTANR_CMD.equals(cmd) || DUMP_LASTANR_TRACES_CMD.equals(cmd) || DUMP_STARTER_CMD.equals(cmd) || DUMP_CONTAINERS_CMD.equals(cmd) - || DUMP_RECENTS_CMD.equals(cmd) || DUMP_RECENTS_SHORT_CMD.equals(cmd)) { + || DUMP_RECENTS_CMD.equals(cmd) || DUMP_RECENTS_SHORT_CMD.equals(cmd) + || DUMP_TOP_RESUMED_ACTIVITY.equals(cmd)) { mAtmInternal.dump( cmd, fd, pw, args, opti, true /* dumpAll */, dumpClient, dumpPackage); } else if ("binder-proxies".equals(cmd)) { diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java index 17be2100414f..b85d7292e738 100644 --- a/services/core/java/com/android/server/am/AppExitInfoTracker.java +++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java @@ -63,8 +63,10 @@ import com.android.internal.app.ProcessMap; import com.android.internal.util.ArrayUtils; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.IoThread; +import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemServiceManager; +import com.android.server.os.NativeTombstoneManager; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -78,6 +80,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; @@ -762,6 +765,10 @@ public final class AppExitInfoTracker { * Helper function for shell command */ void clearHistoryProcessExitInfo(String packageName, int userId) { + NativeTombstoneManager tombstoneService = LocalServices.getService( + NativeTombstoneManager.class); + Optional<Integer> appId = Optional.empty(); + if (TextUtils.isEmpty(packageName)) { synchronized (mLock) { removeByUserIdLocked(userId); @@ -769,10 +776,13 @@ public final class AppExitInfoTracker { } else { final int uid = mService.mPackageManagerInt.getPackageUid(packageName, PackageManager.MATCH_ALL, userId); + appId = Optional.of(UserHandle.getAppId(uid)); synchronized (mLock) { removePackageLocked(packageName, uid, true, userId); } } + + tombstoneService.purge(Optional.of(userId), appId); schedulePersistProcessExitInfo(true); } diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index 52bb55f12d79..fc28bfbea710 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -50,8 +50,6 @@ import com.android.server.LocalServices; import libcore.util.EmptyArray; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -149,7 +147,6 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { * Maps an {@link EnergyConsumerType} to it's corresponding {@link EnergyConsumer#id}s, * unless it is of {@link EnergyConsumer#type}=={@link EnergyConsumerType#OTHER} */ - // TODO(b/180029015): Hook this up (it isn't used yet) @GuardedBy("mWorkerLock") private @Nullable SparseArray<int[]> mEnergyConsumerTypeToIdMap = null; @@ -209,11 +206,22 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { = populateEnergyConsumerSubsystemMapsLocked(); if (idToConsumer != null) { mMeasuredEnergySnapshot = new MeasuredEnergySnapshot(idToConsumer); - final EnergyConsumerResult[] initialEcrs = getEnergyConsumptionData(); - // According to spec, initialEcrs will include 0s for consumers that haven't - // used any energy yet, as long as they are supported; however, attributed uid - // energies will be absent if their energy is 0. - mMeasuredEnergySnapshot.updateAndGetDelta(initialEcrs); + try { + final EnergyConsumerResult[] initialEcrs = getEnergyConsumptionData().get( + EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + // According to spec, initialEcrs will include 0s for consumers that haven't + // used any energy yet, as long as they are supported; however, + // attributed uid energies will be absent if their energy is 0. + mMeasuredEnergySnapshot.updateAndGetDelta(initialEcrs); + } catch (TimeoutException | InterruptedException e) { + Slog.w(TAG, "timeout or interrupt reading initial getEnergyConsumedAsync: " + + e); + // Continue running, later attempts to query may be successful. + } catch (ExecutionException e) { + Slog.wtf(TAG, "exception reading initial getEnergyConsumedAsync: " + + e.getCause()); + // Continue running, later attempts to query may be successful. + } numCustomBuckets = mMeasuredEnergySnapshot.getNumOtherOrdinals(); supportedStdBuckets = getSupportedEnergyBuckets(idToConsumer); } @@ -498,6 +506,8 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { CompletableFuture<ModemActivityInfo> modemFuture = CompletableFuture.completedFuture(null); boolean railUpdated = false; + CompletableFuture<EnergyConsumerResult[]> futureECRs = getMeasuredEnergyLocked(updateFlags); + if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) { // We were asked to fetch WiFi data. // Only fetch WiFi power data if it is supported. @@ -574,9 +584,23 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { Slog.w(TAG, "exception reading modem stats: " + e.getCause()); } - final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas = - mMeasuredEnergySnapshot == null ? null : - mMeasuredEnergySnapshot.updateAndGetDelta(getMeasuredEnergyLocked(updateFlags)); + final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas; + if (mMeasuredEnergySnapshot == null || futureECRs == null) { + measuredEnergyDeltas = null; + } else { + EnergyConsumerResult[] ecrs; + try { + ecrs = futureECRs.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + } catch (TimeoutException | InterruptedException e) { + // TODO (b/180519623): Invalidate the MeasuredEnergy derived data until next reset. + Slog.w(TAG, "timeout or interrupt reading getEnergyConsumedAsync: " + e); + ecrs = null; + } catch (ExecutionException e) { + Slog.wtf(TAG, "exception reading getEnergyConsumedAsync: " + e.getCause()); + ecrs = null; + } + measuredEnergyDeltas = mMeasuredEnergySnapshot.updateAndGetDelta(ecrs); + } final long elapsedRealtime = SystemClock.elapsedRealtime(); final long uptime = SystemClock.uptimeMillis(); @@ -786,22 +810,29 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { return buckets; } - /** Get {@link EnergyConsumerResult}s with the latest energy usage since boot. */ + /** Get all {@link EnergyConsumerResult}s with the latest energy usage since boot. */ @GuardedBy("mWorkerLock") - private @Nullable EnergyConsumerResult[] getEnergyConsumptionData() { - try { - return mPowerStatsInternal.getEnergyConsumedAsync(new int[0]) - .get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - } catch (Exception e) { - Slog.e(TAG, "Failed to getEnergyConsumedAsync", e); - return null; - } + @Nullable + private CompletableFuture<EnergyConsumerResult[]> getEnergyConsumptionData() { + return getEnergyConsumptionData(new int[0]); + } + + /** + * Get {@link EnergyConsumerResult}s of the specified {@link EnergyConsumer} ids with the latest + * energy usage since boot. + */ + @GuardedBy("mWorkerLock") + @Nullable + private CompletableFuture<EnergyConsumerResult[]> getEnergyConsumptionData(int[] consumerIds) { + return mPowerStatsInternal.getEnergyConsumedAsync(consumerIds); } /** Fetch EnergyConsumerResult[] for supported subsystems based on the given updateFlags. */ + @VisibleForTesting @GuardedBy("mWorkerLock") - private @Nullable EnergyConsumerResult[] getMeasuredEnergyLocked(@ExternalUpdateFlag int flags) - { + @Nullable + public CompletableFuture<EnergyConsumerResult[]> getMeasuredEnergyLocked( + @ExternalUpdateFlag int flags) { if (mMeasuredEnergySnapshot == null || mPowerStatsInternal == null) return null; if (flags == UPDATE_ALL) { @@ -809,24 +840,27 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { return getEnergyConsumptionData(); } - final List<Integer> energyConsumerIds = new ArrayList<>(); + final IntArray energyConsumerIds = new IntArray(); + if ((flags & UPDATE_CPU) != 0) { + addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.CPU_CLUSTER); + } if ((flags & UPDATE_DISPLAY) != 0) { addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY); } // TODO: Wifi, Bluetooth, etc., go here - if (energyConsumerIds.isEmpty()) { + if (energyConsumerIds.size() == 0) { return null; } - // TODO(b/180029015): Query specific subsystems from HAL based on energyConsumerIds.toArray - return getEnergyConsumptionData(); + return getEnergyConsumptionData(energyConsumerIds.toArray()); } @GuardedBy("mWorkerLock") private void addEnergyConsumerIdLocked( - List<Integer> energyConsumerIds, @EnergyConsumerType int type) { - final int consumerId = 0; // TODO(b/180029015): Use mEnergyConsumerTypeToIdMap to get this - energyConsumerIds.add(consumerId); + IntArray energyConsumerIds, @EnergyConsumerType int type) { + final int[] consumerIds = mEnergyConsumerTypeToIdMap.get(type); + if (consumerIds == null) return; + energyConsumerIds.addAll(consumerIds); } /** Populates the cached type->ids map, and returns the (inverse) id->EnergyConsumer map. */ @@ -840,12 +874,10 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { return null; } - // TODO(b/180029015): Initialize typeToIds - // Maps type -> {ids} (1:n map, since multiple ids might have the same type) - // final SparseArray<SparseIntArray> typeToIds = new SparseArray<>(); - // Maps id -> EnergyConsumer (1:1 map) final SparseArray<EnergyConsumer> idToConsumer = new SparseArray<>(energyConsumers.length); + // Maps type -> {ids} (1:n map, since multiple ids might have the same type) + final SparseArray<IntArray> tempTypeToId = new SparseArray<>(); // Add all expected EnergyConsumers to the maps for (final EnergyConsumer consumer : energyConsumers) { @@ -862,9 +894,23 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } } idToConsumer.put(consumer.id, consumer); - // TODO(b/180029015): Also populate typeToIds map + + IntArray ids = tempTypeToId.get(consumer.type); + if (ids == null) { + ids = new IntArray(); + tempTypeToId.put(consumer.type, ids); + } + ids.add(consumer.id); + } + + mEnergyConsumerTypeToIdMap = new SparseArray<>(tempTypeToId.size()); + // Populate mEnergyConsumerTypeToIdMap with EnergyConsumer type to ids mappings + final int size = tempTypeToId.size(); + for (int i = 0; i < size; i++) { + final int consumerType = tempTypeToId.keyAt(i); + final int[] consumerIds = tempTypeToId.valueAt(i).toArray(); + mEnergyConsumerTypeToIdMap.put(consumerType, consumerIds); } - // TODO(b/180029015): Store typeToIds in mEnergyConsumerTypeToIdMap. return idToConsumer; } } diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index b15a8869b22a..e19745e5c578 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -40,6 +40,7 @@ import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.face.IFaceService; @@ -144,13 +145,14 @@ public class AuthService extends SystemService { private final class AuthServiceImpl extends IAuthService.Stub { @Override - public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) - throws RemoteException { + public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, + @NonNull String opPackageName) throws RemoteException { Utils.checkPermission(getContext(), TEST_BIOMETRIC); final long identity = Binder.clearCallingIdentity(); try { - return mInjector.getBiometricService().createTestSession(sensorId, opPackageName); + return mInjector.getBiometricService() + .createTestSession(sensorId, callback, opPackageName); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 614c5f1b65bf..00a4e43f347d 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -44,6 +44,7 @@ import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.fingerprint.FingerprintManager; @@ -570,13 +571,13 @@ public class BiometricService extends SystemService { */ private final class BiometricServiceWrapper extends IBiometricService.Stub { @Override // Binder call - public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) - throws RemoteException { + public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, + @NonNull String opPackageName) throws RemoteException { checkInternalPermission(); for (BiometricSensor sensor : mSensors) { if (sensor.id == sensorId) { - return sensor.impl.createTestSession(opPackageName); + return sensor.impl.createTestSession(callback, opPackageName); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java index e0626952fac3..16f82af93856 100644 --- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java @@ -37,18 +37,16 @@ public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier, private static final String TAG = "Biometrics/RemovalClient"; - protected final int mBiometricId; private final BiometricUtils<S> mBiometricUtils; private final Map<Integer, Long> mAuthenticatorIds; public RemovalClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, - int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<S> utils, - int sensorId, @NonNull Map<Integer, Long> authenticatorIds, int statsModality) { + int userId, @NonNull String owner, @NonNull BiometricUtils<S> utils, int sensorId, + @NonNull Map<Integer, Long> authenticatorIds, int statsModality) { super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, statsModality, BiometricsProtoEnums.ACTION_REMOVE, BiometricsProtoEnums.CLIENT_UNKNOWN); - mBiometricId = biometricId; mBiometricUtils = utils; mAuthenticatorIds = authenticatorIds; } @@ -68,6 +66,7 @@ public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier, @Override public void onRemoved(@Nullable BiometricAuthenticator.Identifier identifier, int remaining) { + Slog.d(TAG, "onRemoved: " + identifier.getBiometricId() + " remaining: " + remaining); if (identifier != null) { mBiometricUtils.removeBiometricForUser(getContext(), getTargetUserId(), identifier.getBiometricId()); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java index f37cf18a1320..06b049be4501 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java @@ -21,6 +21,7 @@ import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.face.IFaceService; import android.os.IBinder; @@ -41,8 +42,9 @@ public final class FaceAuthenticator extends IBiometricAuthenticator.Stub { } @Override - public ITestSession createTestSession(@NonNull String opPackageName) throws RemoteException { - return mFaceService.createTestSession(mSensorId, opPackageName); + public ITestSession createTestSession(@NonNull ITestSessionCallback callback, + @NonNull String opPackageName) throws RemoteException { + return mFaceService.createTestSession(mSensorId, callback, opPackageName); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 825392762c8c..6dbd590df851 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -31,6 +31,7 @@ import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.SensorProps; import android.hardware.face.Face; @@ -133,7 +134,8 @@ public class FaceService extends SystemService implements BiometricServiceCallba */ private final class FaceServiceWrapper extends IFaceService.Stub { @Override - public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) { + public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, + @NonNull String opPackageName) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); final ServiceProvider provider = getProviderForSensor(sensorId); @@ -143,7 +145,7 @@ public class FaceService extends SystemService implements BiometricServiceCallba return null; } - return provider.createTestSession(sensorId, opPackageName); + return provider.createTestSession(sensorId, callback, opPackageName); } @Override @@ -386,7 +388,22 @@ public class FaceService extends SystemService implements BiometricServiceCallba opPackageName); } - @Override + @Override // Binder call + public void removeAll(final IBinder token, final int userId, + final IFaceServiceReceiver receiver, final String opPackageName) { + Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); + + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for removeAll"); + return; + } + + provider.second.scheduleRemoveAll(provider.first, token, userId, receiver, + opPackageName); + } + + @Override // Binder call public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback, final String opPackageName) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java index cc24b8960e75..88edfbf12df1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.face.Face; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorPropertiesInternal; @@ -28,6 +29,7 @@ import android.os.IBinder; import android.os.NativeHandle; import android.util.proto.ProtoOutputStream; +import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutTracker; @@ -109,6 +111,9 @@ public interface ServiceProvider { void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName); + void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName); + void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken); void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature, @@ -120,7 +125,8 @@ public interface ServiceProvider { void startPreparedClient(int sensorId, int cookie); - void scheduleInternalCleanup(int sensorId, int userId); + void scheduleInternalCleanup(int sensorId, int userId, + @Nullable BaseClientMonitor.Callback callback); void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto, boolean clearSchedulerBuffer); @@ -130,7 +136,8 @@ public interface ServiceProvider { void dumpInternal(int sensorId, @NonNull PrintWriter pw); @NonNull - ITestSession createTestSession(int sensorId, @NonNull String opPackageName); + ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, + @NonNull String opPackageName); void dumpHal(int sensorId, @NonNull FileDescriptor fd, @NonNull String[] args); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java index 897ebd719da4..a5e6ddb81669 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java @@ -21,6 +21,7 @@ import static android.Manifest.permission.TEST_BIOMETRIC; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.face.AuthenticationFrame; import android.hardware.biometrics.face.BaseFrame; import android.hardware.face.Face; @@ -28,10 +29,12 @@ import android.hardware.face.FaceAuthenticationFrame; import android.hardware.face.FaceEnrollFrame; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; +import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.face.FaceUtils; import java.util.HashSet; @@ -49,6 +52,7 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @NonNull private final Context mContext; private final int mSensorId; + @NonNull private final ITestSessionCallback mCallback; @NonNull private final FaceProvider mProvider; @NonNull private final Sensor mSensor; @NonNull private final Set<Integer> mEnrollmentIds; @@ -132,9 +136,11 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { }; BiometricTestSessionImpl(@NonNull Context context, int sensorId, + @NonNull ITestSessionCallback callback, @NonNull FaceProvider provider, @NonNull Sensor sensor) { mContext = context; mSensorId = sensorId; + mCallback = callback; mProvider = provider; mSensor = sensor; mEnrollmentIds = new HashSet<>(); @@ -224,6 +230,25 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { public void cleanupInternalState(int userId) { Utils.checkPermission(mContext, TEST_BIOMETRIC); - mProvider.scheduleInternalCleanup(mSensorId, userId); + mProvider.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + try { + mCallback.onCleanupStarted(clientMonitor.getTargetUserId()); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + try { + mCallback.onCleanupFinished(clientMonitor.getTargetUserId()); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + }); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java index 9680e4e1841e..c6696aed6520 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java @@ -61,7 +61,7 @@ class FaceInternalCleanupClient extends InternalCleanupClient<Face, ISession> { // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove) // is all done internally. return new FaceRemovalClient(context, lazyDaemon, token, - null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils, - sensorId, authenticatorIds); + null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner, + utils, sensorId, authenticatorIds); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index 1b6b9d70d5ac..1d8f210b394e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.pm.UserInfo; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.SensorProps; import android.hardware.face.Face; @@ -177,7 +178,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { for (int i = 0; i < mSensors.size(); i++) { final int sensorId = mSensors.keyAt(i); scheduleLoadAuthenticatorIds(sensorId); - scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser()); + scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(), + null /* callback */); } return mDaemon; @@ -468,6 +470,25 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @Override public void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + scheduleRemoveSpecifiedIds(sensorId, token, new int[] {faceId}, userId, receiver, + opPackageName); + } + + @Override + public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + final List<Face> faces = FaceUtils.getInstance(sensorId) + .getBiometricsForUser(mContext, userId); + final int[] faceIds = new int[faces.size()]; + for (int i = 0; i < faces.size(); i++) { + faceIds[i] = faces.get(i).getBiometricId(); + } + + scheduleRemoveSpecifiedIds(sensorId, token, faceIds, userId, receiver, opPackageName); + } + + private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token, int[] faceIds, + int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { mHandler.post(() -> { final IFace daemon = getHalInstance(); if (daemon == null) { @@ -485,7 +506,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { final FaceRemovalClient client = new FaceRemovalClient(mContext, mSensors.get(sensorId).getLazySession(), token, - new ClientMonitorCallbackConverter(receiver), faceId, userId, + new ClientMonitorCallbackConverter(receiver), faceIds, userId, opPackageName, FaceUtils.getInstance(sensorId), sensorId, mSensors.get(sensorId).getAuthenticatorIds()); @@ -543,7 +564,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } @Override - public void scheduleInternalCleanup(int sensorId, int userId) { + public void scheduleInternalCleanup(int sensorId, int userId, + @Nullable BaseClientMonitor.Callback callback) { mHandler.post(() -> { final IFace daemon = getHalInstance(); if (daemon == null) { @@ -564,7 +586,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { FaceUtils.getInstance(sensorId), mSensors.get(sensorId).getAuthenticatorIds()); - mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client); + mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback); } catch (RemoteException e) { Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e); } @@ -627,8 +649,9 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @NonNull @Override - public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) { - return mSensors.get(sensorId).createTestSession(); + public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, + @NonNull String opPackageName) { + return mSensors.get(sensorId).createTestSession(callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java index 1cb5031374ec..48796c173dd8 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java @@ -38,19 +38,22 @@ import java.util.Map; class FaceRemovalClient extends RemovalClient<Face, ISession> { private static final String TAG = "FaceRemovalClient"; + final int[] mBiometricIds; + FaceRemovalClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, - int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils, - int sensorId, @NonNull Map<Integer, Long> authenticatorIds) { - super(context, lazyDaemon, token, listener, biometricId, userId, owner, utils, sensorId, + int[] biometricIds, int userId, @NonNull String owner, + @NonNull BiometricUtils<Face> utils, int sensorId, + @NonNull Map<Integer, Long> authenticatorIds) { + super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, authenticatorIds, BiometricsProtoEnums.MODALITY_FACE); + mBiometricIds = biometricIds; } @Override protected void startHalOperation() { try { - final int[] ids = new int[]{mBiometricId}; - getFreshDaemon().removeEnrollments(mSequentialId, ids); + getFreshDaemon().removeEnrollments(mSequentialId, mBiometricIds); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting remove", e); mCallback.onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 4925ce0bb2b1..3434acbf73cc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.face.AuthenticationFrame; import android.hardware.biometrics.face.EnrollmentFrame; import android.hardware.biometrics.face.Error; @@ -459,8 +460,9 @@ public class Sensor { } } - @NonNull ITestSession createTestSession() { - return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, mProvider, this); + @NonNull ITestSession createTestSession(@NonNull ITestSessionCallback callback) { + return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback, + mProvider, this); } void createNewSession(@NonNull IFace daemon, int sensorId, int userId) diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java index d519d60881c0..e8668ed1b6c5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java @@ -21,14 +21,17 @@ import static android.Manifest.permission.TEST_BIOMETRIC; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.face.Face; import android.hardware.face.FaceAuthenticationFrame; import android.hardware.face.FaceEnrollFrame; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; +import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.face.FaceUtils; import java.util.ArrayList; @@ -43,6 +46,7 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @NonNull private final Context mContext; private final int mSensorId; + @NonNull private final ITestSessionCallback mCallback; @NonNull private final Face10 mFace10; @NonNull private final Face10.HalResultController mHalResultController; @NonNull private final Set<Integer> mEnrollmentIds; @@ -120,10 +124,12 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { } }; - BiometricTestSessionImpl(@NonNull Context context, int sensorId, @NonNull Face10 face10, + BiometricTestSessionImpl(@NonNull Context context, int sensorId, + @NonNull ITestSessionCallback callback, @NonNull Face10 face10, @NonNull Face10.HalResultController halResultController) { mContext = context; mSensorId = sensorId; + mCallback = callback; mFace10 = face10; mHalResultController = halResultController; mEnrollmentIds = new HashSet<>(); @@ -201,6 +207,25 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { public void cleanupInternalState(int userId) { Utils.checkPermission(mContext, TEST_BIOMETRIC); - mFace10.scheduleInternalCleanup(mSensorId, userId); + mFace10.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + try { + mCallback.onCleanupStarted(clientMonitor.getTargetUserId()); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + try { + mCallback.onCleanupFinished(clientMonitor.getTargetUserId()); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + }); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index e46661a5e985..ee8823e041bc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -29,6 +29,7 @@ import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback; import android.hardware.face.Face; @@ -123,7 +124,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() { @Override public void onUserSwitching(int newUserId) { - scheduleInternalCleanup(newUserId); + scheduleInternalCleanup(newUserId, null /* callback */); scheduleGetFeature(mSensorId, new Binder(), newUserId, BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION, null, mContext.getOpPackageName()); @@ -437,7 +438,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { Slog.d(TAG, "Face HAL ready, HAL ID: " + halId); if (halId != 0) { scheduleLoadAuthenticatorIds(); - scheduleInternalCleanup(ActivityManager.getCurrentUser()); + scheduleInternalCleanup(ActivityManager.getCurrentUser(), null /* callback */); scheduleGetFeature(mSensorId, new Binder(), ActivityManager.getCurrentUser(), BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION, null, @@ -671,6 +672,20 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { }); } + @Override + public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId, + @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { + mHandler.post(() -> { + scheduleUpdateActiveUserWithoutHandler(userId); + + // For IBiometricsFace@1.0, remove(0) means remove all enrollments + final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, + new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId, + opPackageName, + FaceUtils.getLegacyInstance(mSensorId), mSensorId, mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client); + }); + } @Override public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) { @@ -742,7 +757,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { }); } - private void scheduleInternalCleanup(int userId) { + private void scheduleInternalCleanup(int userId, + @Nullable BaseClientMonitor.Callback callback) { mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); @@ -750,13 +766,14 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext, mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, enrolledList, FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client); + mScheduler.scheduleClientMonitor(client, callback); }); } @Override - public void scheduleInternalCleanup(int sensorId, int userId) { - scheduleInternalCleanup(userId); + public void scheduleInternalCleanup(int sensorId, int userId, + @Nullable BaseClientMonitor.Callback callback) { + scheduleInternalCleanup(userId, callback); } @Override @@ -930,7 +947,9 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { @NonNull @Override - public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) { - return new BiometricTestSessionImpl(mContext, mSensorId, this, mHalResultController); + public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, + @NonNull String opPackageName) { + return new BiometricTestSessionImpl(mContext, mSensorId, callback, this, + mHalResultController); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java index d63791c99dd4..3ae201134deb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java @@ -38,12 +38,15 @@ import java.util.Map; class FaceRemovalClient extends RemovalClient<Face, IBiometricsFace> { private static final String TAG = "FaceRemovalClient"; + private final int mBiometricId; + FaceRemovalClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils, int sensorId, @NonNull Map<Integer, Long> authenticatorIds) { - super(context, lazyDaemon, token, listener, biometricId, userId, owner, utils, sensorId, + super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, authenticatorIds, BiometricsProtoEnums.MODALITY_FACE); + mBiometricId = biometricId; } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java index 34a909908b5a..32e9409de4b2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java @@ -21,6 +21,7 @@ import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintService; import android.os.IBinder; @@ -42,8 +43,9 @@ public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub } @Override - public ITestSession createTestSession(@NonNull String opPackageName) throws RemoteException { - return mFingerprintService.createTestSession(mSensorId, opPackageName); + public ITestSession createTestSession(@NonNull ITestSessionCallback callback, + @NonNull String opPackageName) throws RemoteException { + return mFingerprintService.createTestSession(mSensorId, callback, opPackageName); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index b0e42cd137eb..396dd5f42d4d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -43,6 +43,7 @@ import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.SensorProps; import android.hardware.fingerprint.Fingerprint; @@ -109,7 +110,8 @@ public class FingerprintService extends SystemService implements BiometricServic */ private final class FingerprintServiceWrapper extends IFingerprintService.Stub { @Override - public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) { + public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, + @NonNull String opPackageName) { Utils.checkPermission(getContext(), TEST_BIOMETRIC); final ServiceProvider provider = getProviderForSensor(sensorId); @@ -119,7 +121,7 @@ public class FingerprintService extends SystemService implements BiometricServic return null; } - return provider.createTestSession(sensorId, opPackageName); + return provider.createTestSession(sensorId, callback, opPackageName); } @Override @@ -499,7 +501,21 @@ public class FingerprintService extends SystemService implements BiometricServic opPackageName); } - @Override + @Override // Binder call + public void removeAll(final IBinder token, final int userId, + final IFingerprintServiceReceiver receiver, final String opPackageName) { + Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); + + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for removeAll"); + return; + } + provider.second.scheduleRemoveAll(provider.first, token, receiver, userId, + opPackageName); + } + + @Override // Binder call public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback, final String opPackageName) { Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java index f672ae56e020..dfec2e3e308f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; @@ -28,6 +29,7 @@ import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; import android.util.proto.ProtoOutputStream; +import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutTracker; @@ -98,7 +100,12 @@ public interface ServiceProvider { @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId, @NonNull String opPackageName); - void scheduleInternalCleanup(int sensorId, int userId); + void scheduleRemoveAll(int sensorId, @NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, int userId, + @NonNull String opPackageName); + + void scheduleInternalCleanup(int sensorId, int userId, + @Nullable BaseClientMonitor.Callback callback); boolean isHardwareDetected(int sensorId); @@ -133,5 +140,6 @@ public interface ServiceProvider { void dumpInternal(int sensorId, @NonNull PrintWriter pw); @NonNull - ITestSession createTestSession(int sensorId, @NonNull String opPackageName); + ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, + @NonNull String opPackageName); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java index ea9c709ec79f..20b32543f7a0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java @@ -21,14 +21,17 @@ import static android.Manifest.permission.TEST_BIOMETRIC; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.os.Binder; +import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import java.util.HashSet; @@ -46,6 +49,7 @@ class BiometricTestSessionImpl extends ITestSession.Stub { @NonNull private final Context mContext; private final int mSensorId; + @NonNull private final ITestSessionCallback mCallback; @NonNull private final FingerprintProvider mProvider; @NonNull private final Sensor mSensor; @NonNull private final Set<Integer> mEnrollmentIds; @@ -110,9 +114,11 @@ class BiometricTestSessionImpl extends ITestSession.Stub { }; BiometricTestSessionImpl(@NonNull Context context, int sensorId, - @NonNull FingerprintProvider provider, @NonNull Sensor sensor) { + @NonNull ITestSessionCallback callback, @NonNull FingerprintProvider provider, + @NonNull Sensor sensor) { mContext = context; mSensorId = sensorId; + mCallback = callback; mProvider = provider; mSensor = sensor; mEnrollmentIds = new HashSet<>(); @@ -192,6 +198,25 @@ class BiometricTestSessionImpl extends ITestSession.Stub { public void cleanupInternalState(int userId) { Utils.checkPermission(mContext, TEST_BIOMETRIC); - mProvider.scheduleInternalCleanup(mSensorId, userId); + mProvider.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + try { + mCallback.onCleanupStarted(clientMonitor.getTargetUserId()); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + try { + mCallback.onCleanupFinished(clientMonitor.getTargetUserId()); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + }); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java index 2a0e984e5933..0de3f4f8cce2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java @@ -60,7 +60,7 @@ class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint String owner, BiometricUtils<Fingerprint> utils, int sensorId, Map<Integer, Long> authenticatorIds) { return new FingerprintRemovalClient(context, lazyDaemon, token, - null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils, - sensorId, authenticatorIds); + null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner, + utils, sensorId, authenticatorIds); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 0bd2f241ed8d..598cc8992c2d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.pm.UserInfo; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.SensorProps; import android.hardware.fingerprint.Fingerprint; @@ -185,7 +186,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi for (int i = 0; i < mSensors.size(); i++) { final int sensorId = mSensors.keyAt(i); scheduleLoadAuthenticatorIds(sensorId); - scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser()); + scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(), + null /* callback */); } return mDaemon; @@ -490,6 +492,27 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi public void scheduleRemove(int sensorId, @NonNull IBinder token, @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId, @NonNull String opPackageName) { + scheduleRemoveSpecifiedIds(sensorId, token, new int[] {fingerId}, userId, receiver, + opPackageName); + } + + @Override + public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, int userId, + @NonNull String opPackageName) { + final List<Fingerprint> fingers = FingerprintUtils.getInstance(sensorId) + .getBiometricsForUser(mContext, userId); + final int[] fingerIds = new int[fingers.size()]; + for (int i = 0; i < fingers.size(); i++) { + fingerIds[i] = fingers.get(i).getBiometricId(); + } + + scheduleRemoveSpecifiedIds(sensorId, token, fingerIds, userId, receiver, opPackageName); + } + + private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token, + int[] fingerprintIds, int userId, @NonNull IFingerprintServiceReceiver receiver, + @NonNull String opPackageName) { mHandler.post(() -> { final IFingerprint daemon = getHalInstance(); if (daemon == null) { @@ -507,7 +530,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, mSensors.get(sensorId).getLazySession(), token, - new ClientMonitorCallbackConverter(receiver), fingerId, userId, + new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId, opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, mSensors.get(sensorId).getAuthenticatorIds()); mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client); @@ -518,7 +541,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } @Override - public void scheduleInternalCleanup(int sensorId, int userId) { + public void scheduleInternalCleanup(int sensorId, int userId, + @Nullable BaseClientMonitor.Callback callback) { mHandler.post(() -> { final IFingerprint daemon = getHalInstance(); if (daemon == null) { @@ -538,7 +562,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mContext.getOpPackageName(), sensorId, enrolledList, FingerprintUtils.getInstance(sensorId), mSensors.get(sensorId).getAuthenticatorIds()); - mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client); + mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback); } catch (RemoteException e) { Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e); } @@ -683,8 +707,9 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @NonNull @Override - public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) { - return mSensors.get(sensorId).createTestSession(); + public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, + @NonNull String opPackageName) { + return mSensors.get(sensorId).createTestSession(callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java index 4a99a7b29638..c622208262e0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java @@ -39,20 +39,22 @@ import java.util.Map; class FingerprintRemovalClient extends RemovalClient<Fingerprint, ISession> { private static final String TAG = "FingerprintRemovalClient"; + private final int[] mBiometricIds; + FingerprintRemovalClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, - @Nullable ClientMonitorCallbackConverter listener, int biometricId, int userId, + @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, @NonNull Map<Integer, Long> authenticatorIds) { - super(context, lazyDaemon, token, listener, biometricId, userId, owner, utils, sensorId, + super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT); + mBiometricIds = biometricIds; } @Override protected void startHalOperation() { try { - final int[] ids = new int[] {mBiometricId}; - getFreshDaemon().removeEnrollments(mSequentialId, ids); + getFreshDaemon().removeEnrollments(mSequentialId, mBiometricIds); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting remove", e); mCallback.onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index c83c0fba0133..a98e7db43f79 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.fingerprint.Error; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.ISession; @@ -439,8 +440,9 @@ class Sensor { } } - @NonNull ITestSession createTestSession() { - return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, mProvider, this); + @NonNull ITestSession createTestSession(@NonNull ITestSessionCallback callback) { + return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback, + mProvider, this); } void createNewSession(@NonNull IFingerprint daemon, int sensorId, int userId) diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java index 312ee0a267ac..766a8829e968 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java @@ -21,13 +21,16 @@ import static android.Manifest.permission.TEST_BIOMETRIC; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.os.Binder; +import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import java.util.ArrayList; @@ -47,6 +50,7 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @NonNull private final Context mContext; private final int mSensorId; + @NonNull private final ITestSessionCallback mCallback; @NonNull private final Fingerprint21 mFingerprint21; @NonNull private final Fingerprint21.HalResultController mHalResultController; @NonNull private final Set<Integer> mEnrollmentIds; @@ -111,10 +115,12 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { }; BiometricTestSessionImpl(@NonNull Context context, int sensorId, + @NonNull ITestSessionCallback callback, @NonNull Fingerprint21 fingerprint21, @NonNull Fingerprint21.HalResultController halResultController) { mContext = context; mSensorId = sensorId; + mCallback = callback; mFingerprint21 = fingerprint21; mHalResultController = halResultController; mEnrollmentIds = new HashSet<>(); @@ -191,6 +197,25 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { public void cleanupInternalState(int userId) { Utils.checkPermission(mContext, TEST_BIOMETRIC); - mFingerprint21.scheduleInternalCleanup(mSensorId, userId); + mFingerprint21.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() { + @Override + public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + try { + mCallback.onCleanupStarted(clientMonitor.getTargetUserId()); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + try { + mCallback.onCleanupFinished(clientMonitor.getTargetUserId()); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + }); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index 7a74c6a39aa1..6e22a797b435 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -30,6 +30,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback; import android.hardware.fingerprint.Fingerprint; @@ -158,7 +159,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() { @Override public void onUserSwitching(int newUserId) { - scheduleInternalCleanup(newUserId); + scheduleInternalCleanup(newUserId, null /* callback */); } }; @@ -437,7 +438,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider Slog.d(TAG, "Fingerprint HAL ready, HAL ID: " + halId); if (halId != 0) { scheduleLoadAuthenticatorIds(); - scheduleInternalCleanup(ActivityManager.getCurrentUser()); + scheduleInternalCleanup(ActivityManager.getCurrentUser(), null /* callback */); } else { Slog.e(TAG, "Unable to set callback"); mDaemon = null; @@ -463,26 +464,33 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider for (UserInfo user : UserManager.get(mContext).getAliveUsers()) { final int targetUserId = user.id; if (!mAuthenticatorIds.containsKey(targetUserId)) { - scheduleUpdateActiveUserWithoutHandler(targetUserId); + scheduleUpdateActiveUserWithoutHandler(targetUserId, true /* force */); } } }); } + private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) { + scheduleUpdateActiveUserWithoutHandler(targetUserId, false /* force */); + } + /** * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the * handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser" * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule * this operation on the same lambda/runnable as those operations so that the ordering is * correct. + * + * @param targetUserId Switch to this user, and update their authenticatorId + * @param force Always retrieve the authenticatorId, even if we are already the targetUserId */ - private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) { + private void scheduleUpdateActiveUserWithoutHandler(int targetUserId, boolean force) { final boolean hasEnrolled = !getEnrolledFingerprints(mSensorProperties.sensorId, targetUserId).isEmpty(); final FingerprintUpdateActiveUserClient client = new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorProperties.sensorId, mCurrentUserId, - hasEnrolled, mAuthenticatorIds); + hasEnrolled, mAuthenticatorIds, force); mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, @@ -563,7 +571,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider boolean success) { if (success) { // Update authenticatorIds - scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId()); + scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(), + true /* force */); } } }); @@ -636,7 +645,25 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider }); } - private void scheduleInternalCleanup(int userId) { + @Override + public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, + @NonNull IFingerprintServiceReceiver receiver, int userId, + @NonNull String opPackageName) { + mHandler.post(() -> { + scheduleUpdateActiveUserWithoutHandler(userId); + + // For IBiometricsFingerprint@2.1, remove(0) means remove all enrollments + final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, + mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), + 0 /* fingerprintId */, userId, opPackageName, + FingerprintUtils.getLegacyInstance(mSensorId), + mSensorProperties.sensorId, mAuthenticatorIds); + mScheduler.scheduleClientMonitor(client); + }); + } + + private void scheduleInternalCleanup(int userId, + @Nullable BaseClientMonitor.Callback callback) { mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); @@ -646,13 +673,14 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mContext, mLazyDaemon, userId, mContext.getOpPackageName(), mSensorProperties.sensorId, enrolledList, FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); - mScheduler.scheduleClientMonitor(client); + mScheduler.scheduleClientMonitor(client, callback); }); } @Override - public void scheduleInternalCleanup(int sensorId, int userId) { - scheduleInternalCleanup(userId); + public void scheduleInternalCleanup(int sensorId, int userId, + @Nullable BaseClientMonitor.Callback callback) { + scheduleInternalCleanup(userId, callback); } @Override @@ -832,8 +860,9 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @NonNull @Override - public ITestSession createTestSession(int sensorId, @NonNull String opPackageName) { - return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, this, + public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, + @NonNull String opPackageName) { + return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback, this, mHalResultController); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java index f6a22f581f1a..2f360f31a325 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java @@ -39,13 +39,16 @@ import java.util.Map; class FingerprintRemovalClient extends RemovalClient<Fingerprint, IBiometricsFingerprint> { private static final String TAG = "FingerprintRemovalClient"; + private final int mBiometricId; + FingerprintRemovalClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, @NonNull Map<Integer, Long> authenticatorIds) { - super(context, lazyDaemon, token, listener, biometricId, userId, owner, utils, sensorId, + super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT); + mBiometricId = biometricId; } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java index db7f4bcbec9d..6776810c2e52 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java @@ -41,6 +41,7 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr private static final String FP_DATA_DIR = "fpdata"; private final int mCurrentUserId; + private final boolean mForceUpdateAuthenticatorId; private final boolean mHasEnrolledBiometrics; private final Map<Integer, Long> mAuthenticatorIds; private File mDirectory; @@ -48,11 +49,12 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr FingerprintUpdateActiveUserClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, int userId, @NonNull String owner, int sensorId, int currentUserId, boolean hasEnrolledBiometrics, - @NonNull Map<Integer, Long> authenticatorIds) { + @NonNull Map<Integer, Long> authenticatorIds, boolean forceUpdateAuthenticatorId) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); mCurrentUserId = currentUserId; + mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId; mHasEnrolledBiometrics = hasEnrolledBiometrics; mAuthenticatorIds = authenticatorIds; } @@ -61,7 +63,7 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr public void start(@NonNull Callback callback) { super.start(callback); - if (mCurrentUserId == getTargetUserId()) { + if (mCurrentUserId == getTargetUserId() && !mForceUpdateAuthenticatorId) { Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning"); callback.onClientFinished(this, true /* success */); return; diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java index 8e84613b2d64..f44e0691bb9d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java @@ -21,6 +21,7 @@ import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; +import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.iris.IIrisService; import android.os.IBinder; @@ -39,7 +40,8 @@ public final class IrisAuthenticator extends IBiometricAuthenticator.Stub { } @Override - public ITestSession createTestSession(@NonNull String opPackageName) throws RemoteException { + public ITestSession createTestSession(@NonNull ITestSessionCallback callback, + @NonNull String opPackageName) throws RemoteException { return null; } diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java index 09c0937802a5..01e839dae07a 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java @@ -41,7 +41,6 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.security.FileIntegrityService; import com.android.server.security.VerityUtils; import java.io.File; @@ -226,7 +225,7 @@ public final class FontManagerService extends IFontManager.Stub { @Nullable private static UpdatableFontDir createUpdatableFontDir() { // If apk verity is supported, fs-verity should be available. - if (!FileIntegrityService.isApkVeritySupported()) return null; + if (!VerityUtils.isFsVeritySupported()) return null; return new UpdatableFontDir(new File(FONT_FILES_DIR), Arrays.asList(new File(SystemFonts.SYSTEM_FONT_DIR), new File(SystemFonts.OEM_FONT_DIR)), diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index fa1fb48ce124..d014f149e831 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -774,8 +774,14 @@ public class HdmiControlService extends SystemService { private void initializeCec(int initiatedBy) { mAddressAllocated = false; - mCecVersion = getHdmiCecConfig().getIntValue( + int settingsCecVersion = getHdmiCecConfig().getIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION); + int supportedCecVersion = mCecController.getVersion(); + + // Limit the used CEC version to the highest supported version by HAL and selected + // version in settings (but at least v1.4b). + mCecVersion = Math.max(HdmiControlManager.HDMI_CEC_VERSION_1_4_B, + Math.min(settingsCecVersion, supportedCecVersion)); mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true); mCecController.setLanguage(mMenuLanguage); @@ -2184,6 +2190,7 @@ public class HdmiControlService extends SystemService { pw.println("mProhibitMode: " + mProhibitMode); pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus()); + pw.println("mCecVersion: " + mCecVersion); // System settings pw.println("System_settings:"); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java b/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java index ee3427f0a383..a1e613635116 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java @@ -70,6 +70,10 @@ final class HdmiControlShellCommand extends ShellCommand { pw.println(" --args <vendor specific arguments>"); pw.println(" [--id <true if vendor command should be sent with vendor id>]"); pw.println(" Send a Vendor Command to the given target device"); + pw.println(" cec_setting get <setting name>"); + pw.println(" Get the current value of a CEC setting"); + pw.println(" cec_setting set <setting name> <value>"); + pw.println(" Set the value of a CEC setting"); } private int handleShellCommand(String cmd) throws RemoteException { @@ -81,6 +85,8 @@ final class HdmiControlShellCommand extends ShellCommand { return oneTouchPlay(pw); case "vendorcommand": return vendorCommand(pw); + case "cec_setting": + return cecSetting(pw); } getErrPrintWriter().println("Unhandled command: " + cmd); @@ -157,4 +163,39 @@ final class HdmiControlShellCommand extends ShellCommand { mBinderService.sendVendorCommand(deviceType, destination, params, hasVendorId); return 0; } + + private int cecSetting(PrintWriter pw) throws RemoteException { + if (getRemainingArgsCount() < 1) { + throw new IllegalArgumentException("Expected at least 1 argument (operation)."); + } + String operation = getNextArgRequired(); + switch (operation) { + case "get": { + String setting = getNextArgRequired(); + try { + String value = mBinderService.getCecSettingStringValue(setting); + pw.println(setting + " = " + value); + } catch (IllegalArgumentException e) { + int intValue = mBinderService.getCecSettingIntValue(setting); + pw.println(setting + " = " + intValue); + } + return 0; + } + case "set": { + String setting = getNextArgRequired(); + String value = getNextArgRequired(); + try { + mBinderService.setCecSettingStringValue(setting, value); + pw.println(setting + " = " + value); + } catch (IllegalArgumentException e) { + int intValue = Integer.parseInt(value); + mBinderService.setCecSettingIntValue(setting, intValue); + pw.println(setting + " = " + intValue); + } + return 0; + } + default: + throw new IllegalArgumentException("Unknown operation: " + operation); + } + } } diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java index bd577f35f0a5..c4c0f688bd67 100644 --- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java +++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java @@ -371,9 +371,6 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem int durationMs) { Slog.i(mTag, "setTemporaryService(" + userId + ") to " + componentName + " for " + durationMs + "ms"); - if (mServiceNameResolver == null) { - return; - } enforceCallingPermissionForManagement(); Objects.requireNonNull(componentName); @@ -407,9 +404,6 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem enforceCallingPermissionForManagement(); synchronized (mLock) { - if (mServiceNameResolver == null) { - return false; - } final boolean changed = mServiceNameResolver.setDefaultServiceEnabled(userId, enabled); if (!changed) { if (verbose) { @@ -440,10 +434,6 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem public final boolean isDefaultServiceEnabled(@UserIdInt int userId) { enforceCallingPermissionForManagement(); - if (mServiceNameResolver == null) { - return false; - } - synchronized (mLock) { return mServiceNameResolver.isDefaultServiceEnabled(userId); } @@ -968,10 +958,6 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem public void onPackageModified(String packageName) { if (verbose) Slog.v(mTag, "onPackageModified(): " + packageName); - if (mServiceNameResolver == null) { - return; - } - final int userId = getChangingUserId(); final String serviceName = mServiceNameResolver.getDefaultServiceName(userId); if (serviceName == null) { diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 5eec315aa51c..6deb19a53629 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -1424,6 +1424,8 @@ public class LocationManagerService extends ILocationManager.Stub { mAppForegroundHelper.onSystemReady(); mLocationPowerSaveModeHelper.onSystemReady(); mScreenInteractiveHelper.onSystemReady(); + mDeviceStationaryHelper.onSystemReady(); + mDeviceIdleHelper.onSystemReady(); if (mEmergencyCallHelper != null) { mEmergencyCallHelper.onSystemReady(); diff --git a/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java b/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java new file mode 100644 index 000000000000..85de4bb1eefc --- /dev/null +++ b/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java @@ -0,0 +1,105 @@ +/* + * 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.location.contexthub; + +import static java.util.concurrent.TimeUnit.SECONDS; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; + +/** + * A class that manages a timer used to keep track of how much time is left before a + * {@link ContextHubClientBroker} has its authorization state changed with a nanoapp from + * DENIED_GRACE_PERIOD to DENIED. Much of this implementation is copied from + * {@link android.os.CountDownTimer} while adding the ability to specify the provided looper. + * + * @hide + */ +public class AuthStateDenialTimer { + private static final long TIMEOUT_MS = SECONDS.toMillis(60); + + private final ContextHubClientBroker mClient; + private final long mNanoAppId; + private final Handler mHandler; + + /** + * Indicates when the timer should stop in the future. + */ + private long mStopTimeInFuture; + + /** + * boolean representing if the timer was cancelled + */ + private boolean mCancelled = false; + + public AuthStateDenialTimer(ContextHubClientBroker client, long nanoAppId, Looper looper) { + mClient = client; + mNanoAppId = nanoAppId; + mHandler = new CountDownHandler(looper); + } + + /** + * Cancel the countdown. + */ + public synchronized void cancel() { + mCancelled = true; + mHandler.removeMessages(MSG); + } + + /** + * Start the countdown. + */ + public synchronized void start() { + mCancelled = false; + mStopTimeInFuture = SystemClock.elapsedRealtime() + TIMEOUT_MS; + mHandler.sendMessage(mHandler.obtainMessage(MSG)); + } + + /** + * Called when the timer has expired. + */ + public void onFinish() { + mClient.handleAuthStateTimerExpiry(mNanoAppId); + } + + // Message type used to trigger the timer. + private static final int MSG = 1; + + private class CountDownHandler extends Handler { + + CountDownHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + synchronized (AuthStateDenialTimer.this) { + if (mCancelled) { + return; + } + final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime(); + if (millisLeft <= 0) { + onFinish(); + } else { + sendMessageDelayed(obtainMessage(MSG), millisLeft); + } + } + } + }; +} diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java index d3c853da9b02..6249a068f591 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java @@ -17,9 +17,13 @@ package com.android.server.location.contexthub; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED; +import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED_GRACE_PERIOD; +import static android.hardware.location.ContextHubManager.AUTHORIZATION_GRANTED; import android.Manifest; import android.annotation.Nullable; +import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -30,19 +34,21 @@ import android.hardware.location.ContextHubManager; import android.hardware.location.ContextHubTransaction; import android.hardware.location.IContextHubClient; import android.hardware.location.IContextHubClientCallback; +import android.hardware.location.IContextHubTransactionCallback; import android.hardware.location.NanoAppMessage; +import android.hardware.location.NanoAppState; import android.os.Binder; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.util.proto.ProtoOutputStream; import com.android.server.location.ClientBrokerProto; -import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; -import java.util.Set; +import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; @@ -52,14 +58,53 @@ import java.util.function.Supplier; * notification callbacks. This class implements the IContextHubClient object, and the implemented * APIs must be thread-safe. * + * Additionally, this class is responsible for enforcing permissions usage and attribution are + * handled appropriately for a given client. In general, this works as follows: + * + * Client sending a message to a nanoapp: + * 1) When initially sending a message to nanoapps, clients are by default in a grace period state + * which allows them to always send their first message to nanoapps. This is done to allow + * clients (especially callback clients) to reset their conection to the nanoapp if they are + * killed / restarted (e.g. following a permission revocation). + * 2) After the initial message is sent, a check of permissions state is performed. If the + * client doesn't have permissions to communicate, it is placed into the denied grace period + * state and notified so that it can clean up its communication before it is completely denied + * access. + * 3) For subsequent messages, the auth state is checked synchronously and messages are denied if + * the client is denied authorization + * + * Client receiving a message from a nanoapp: + * 1) If a nanoapp sends a message to the client, the authentication state is checked synchronously. + * If there has been no message between the two before, the auth state is assumed granted. + * 2) The broker then checks that the client has all permissions the nanoapp requires and attributes + * all permissions required to consume the message being sent. If both of those checks pass, then + * the message is delivered. Otherwise, it's dropped. + * + * Client losing or gaining permissions (callback client): + * 1) Clients are killed when they lose permissions. This will cause callback clients to completely + * disconnect from the service. When they are restarted, their initial message will still be + * be allowed through and their permissions will be rechecked at that time. + * 2) If they gain a permission, the broker will notify them if that permission allows them to + * communicate with a nanoapp again. + * + * Client losing or gaining permissions (PendingIntent client): + * 1) Unlike callback clients, PendingIntent clients are able to maintain their connection to the + * service when they are killed. In their case, they will receive notifications of the broker + * that they have been denied required permissions or gain required permissions. + * * TODO: Consider refactoring this class via inheritance * * @hide */ public class ContextHubClientBroker extends IContextHubClient.Stub - implements IBinder.DeathRecipient { + implements IBinder.DeathRecipient, AppOpsManager.OnOpChangedListener { private static final String TAG = "ContextHubClientBroker"; + /** + * Message used by noteOp when this client receives a message from a nanoapp. + */ + private static final String RECEIVE_MSG_NOTE = "NanoappMessageDelivery "; + /* * The context of the service. */ @@ -119,6 +164,26 @@ public class ContextHubClientBroker extends IContextHubClient.Stub */ private final String mPackage; + /** + * The PID associated with this client. + */ + private final int mPid; + + /** + * The UID associated with this client. + */ + private final int mUid; + + /** + * Manager used for noting permissions usage of this broker. + */ + private final AppOpsManager mAppOpsManager; + + /** + * Manager used to queue transactions to the context hub. + */ + private final ContextHubTransactionManager mTransactionManager; + /* * True if a PendingIntent has been cancelled. */ @@ -130,11 +195,44 @@ public class ContextHubClientBroker extends IContextHubClient.Stub private final boolean mHasAccessContextHubPermission; /* - * The set of nanoapp IDs that represent the group of nanoapps this client has a messaging - * channel with, i.e. has sent or received messages from this particular nanoapp. + * Map containing all nanoapps this client has a messaging channel with and whether it is + * allowed to communicate over that channel. A channel is defined to have been opened if the + * client has sent or received messages from the particular nanoapp. */ - private final Set<Long> mMessageChannelNanoappIdSet = - Collections.newSetFromMap(new ConcurrentHashMap<Long, Boolean>()); + private final Map<Long, Integer> mMessageChannelNanoappIdMap = + new ConcurrentHashMap<Long, Integer>(); + + /** + * Map containing all nanoapps that have active auth state denial timers. + */ + private final Map<Long, AuthStateDenialTimer> mNappToAuthTimerMap = + new ConcurrentHashMap<Long, AuthStateDenialTimer>(); + + /** + * Callback used to obtain the latest set of nanoapp permissions and verify this client has + * each nanoapps permissions granted. + */ + private final IContextHubTransactionCallback mQueryPermsCallback = + new IContextHubTransactionCallback.Stub() { + @Override + public void onTransactionComplete(int result) { + } + + @Override + public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) { + if (result != ContextHubTransaction.RESULT_SUCCESS && nanoAppStateList != null) { + Log.e(TAG, "Permissions query failed, but still received nanoapp state"); + } else if (nanoAppStateList != null) { + for (NanoAppState state : nanoAppStateList) { + if (mMessageChannelNanoappIdMap.containsKey(state.getNanoAppId())) { + List<String> permissions = state.getNanoAppPermissions(); + updateNanoAppAuthState(state.getNanoAppId(), + hasPermissions(permissions), false /* gracePeriodExpired */); + } + } + } + } + }; /* * Helper class to manage registered PendingIntent requests from the client. @@ -182,40 +280,57 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } } - /* package */ ContextHubClientBroker( - Context context, IContextHubWrapper contextHubProxy, + private ContextHubClientBroker(Context context, IContextHubWrapper contextHubProxy, ContextHubClientManager clientManager, ContextHubInfo contextHubInfo, - short hostEndPointId, IContextHubClientCallback callback, String attributionTag) { + short hostEndPointId, IContextHubClientCallback callback, String attributionTag, + ContextHubTransactionManager transactionManager, PendingIntent pendingIntent, + long nanoAppId, String packageName) { mContext = context; mContextHubProxy = contextHubProxy; mClientManager = clientManager; mAttachedContextHubInfo = contextHubInfo; mHostEndPointId = hostEndPointId; mCallbackInterface = callback; - mPendingIntentRequest = new PendingIntentRequest(); - mPackage = mContext.getPackageManager().getNameForUid(Binder.getCallingUid()); + if (pendingIntent == null) { + mPendingIntentRequest = new PendingIntentRequest(); + } else { + mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId); + } + mPackage = packageName; mAttributionTag = attributionTag; + mTransactionManager = transactionManager; + mPid = Binder.getCallingPid(); + mUid = Binder.getCallingUid(); mHasAccessContextHubPermission = context.checkCallingPermission( Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED; + mAppOpsManager = context.getSystemService(AppOpsManager.class); + + startMonitoringOpChanges(); + } + + /* package */ ContextHubClientBroker( + Context context, IContextHubWrapper contextHubProxy, + ContextHubClientManager clientManager, ContextHubInfo contextHubInfo, + short hostEndPointId, IContextHubClientCallback callback, String attributionTag, + ContextHubTransactionManager transactionManager, String packageName) { + this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId, callback, + attributionTag, transactionManager, null /* pendingIntent */, 0 /* nanoAppId */, + packageName); } /* package */ ContextHubClientBroker( Context context, IContextHubWrapper contextHubProxy, ContextHubClientManager clientManager, ContextHubInfo contextHubInfo, short hostEndPointId, PendingIntent pendingIntent, long nanoAppId, - String attributionTag) { - mContext = context; - mContextHubProxy = contextHubProxy; - mClientManager = clientManager; - mAttachedContextHubInfo = contextHubInfo; - mHostEndPointId = hostEndPointId; - mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId); - mPackage = pendingIntent.getCreatorPackage(); - mAttributionTag = attributionTag; + String attributionTag, ContextHubTransactionManager transactionManager) { + this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId, + null /* callback */, attributionTag, transactionManager, pendingIntent, nanoAppId, + pendingIntent.getCreatorPackage()); + } - mHasAccessContextHubPermission = context.checkCallingPermission( - Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED; + private void startMonitoringOpChanges() { + mAppOpsManager.startWatchingMode(AppOpsManager.OP_NONE, mPackage, this); } /** @@ -229,18 +344,44 @@ public class ContextHubClientBroker extends IContextHubClient.Stub public int sendMessageToNanoApp(NanoAppMessage message) { ContextHubServiceUtil.checkPermissions(mContext); + int authState; + synchronized (mMessageChannelNanoappIdMap) { + // Default to the granted auth state. The true auth state will be checked async if it's + // not denied. + authState = mMessageChannelNanoappIdMap.getOrDefault( + message.getNanoAppId(), AUTHORIZATION_GRANTED); + if (authState == AUTHORIZATION_DENIED) { + return ContextHubTransaction.RESULT_FAILED_PERMISSION_DENIED; + } + } + int result; if (isRegistered()) { - mMessageChannelNanoappIdSet.add(message.getNanoAppId()); + // Even though the auth state is currently not denied, query the nanoapp permissions + // async and verify that the host app currently holds all the requisite permissions. + // This can't be done synchronously due to the async query that needs to be performed to + // obtain the nanoapp permissions. + boolean initialNanoappMessage = false; + synchronized (mMessageChannelNanoappIdMap) { + if (mMessageChannelNanoappIdMap.get(message.getNanoAppId()) == null) { + mMessageChannelNanoappIdMap.put(message.getNanoAppId(), AUTHORIZATION_GRANTED); + initialNanoappMessage = true; + } + } + + if (initialNanoappMessage) { + // Only check permissions the first time a nanoapp is queried since nanoapp + // permissions don't currently change at runtime. If the host permission changes + // later, that'll be checked by onOpChanged. + checkNanoappPermsAsync(); + } + ContextHubMsg messageToNanoApp = ContextHubServiceUtil.createHidlContextHubMessage(mHostEndPointId, message); int contextHubId = mAttachedContextHubInfo.getId(); try { - // TODO(166846988): Fill in host permissions before sending a message. - result = mContextHubProxy.sendMessageToHub( - contextHubId, messageToNanoApp, - new ArrayList<String>() /* hostPermissions */); + result = mContextHubProxy.sendMessageToHub(contextHubId, messageToNanoApp); } catch (RemoteException e) { Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = " + contextHubId + ")", e); @@ -275,6 +416,19 @@ public class ContextHubClientBroker extends IContextHubClient.Stub onClientExit(); } + @Override + public void onOpChanged(String op, String packageName) { + if (packageName.equals(mPackage)) { + if (!mMessageChannelNanoappIdMap.isEmpty()) { + checkNanoappPermsAsync(); + } + } + } + + /* package */ String getPackageName() { + return mPackage; + } + /** * Used to override the attribution tag with a newer value if a PendingIntent broker is * retrieved. @@ -308,15 +462,39 @@ public class ContextHubClientBroker extends IContextHubClient.Stub * Sends a message to the client associated with this object. * * @param message the message that came from a nanoapp - */ - /* package */ void sendMessageToClient(NanoAppMessage message) { - mMessageChannelNanoappIdSet.add(message.getNanoAppId()); + * @param nanoappPermissions permissions required to communicate with the nanoapp sending this + * message + * @param messagePermissions permissions required to consume the message being delivered. These + * permissions are what will be attributed to the client through noteOp. + */ + /* package */ void sendMessageToClient( + NanoAppMessage message, List<String> nanoappPermissions, + List<String> messagePermissions) { + long nanoAppId = message.getNanoAppId(); + + int authState = mMessageChannelNanoappIdMap.getOrDefault(nanoAppId, AUTHORIZATION_GRANTED); + + // If in the grace period, the host may not receive any messages containing permissions + // covered data. + if (authState == AUTHORIZATION_DENIED_GRACE_PERIOD && !messagePermissions.isEmpty()) { + Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage + + " in grace period and napp msg has permissions"); + return; + } + + if (authState == AUTHORIZATION_DENIED || !hasPermissions(nanoappPermissions) + || !notePermissions(messagePermissions, RECEIVE_MSG_NOTE + nanoAppId)) { + Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage + + " doesn't have permission"); + return; + } + invokeCallback(callback -> callback.onMessageFromNanoApp(message)); Supplier<Intent> supplier = - () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, message.getNanoAppId()) + () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, nanoAppId) .putExtra(ContextHubManager.EXTRA_MESSAGE, message); - sendPendingIntent(supplier, message.getNanoAppId()); + sendPendingIntent(supplier, nanoAppId); } /** @@ -325,6 +503,10 @@ public class ContextHubClientBroker extends IContextHubClient.Stub * @param nanoAppId the ID of the nanoapp that was loaded. */ /* package */ void onNanoAppLoaded(long nanoAppId) { + // Check the latest state to see if the loaded nanoapp's permissions changed such that the + // host app can communicate with it again. + checkNanoappPermsAsync(); + invokeCallback(callback -> callback.onNanoAppLoaded(nanoAppId)); sendPendingIntent( () -> createIntent(ContextHubManager.EVENT_NANOAPP_LOADED, nanoAppId), nanoAppId); @@ -392,6 +574,43 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** + * Checks that this client has all of the provided permissions. + * + * @param permissions list of permissions to check + * @return true if the client has all of the permissions granted + */ + /* package */ boolean hasPermissions(List<String> permissions) { + for (String permission : permissions) { + if (mContext.checkPermission(permission, mPid, mUid) != PERMISSION_GRANTED) { + return false; + } + } + return true; + } + + /** + * Attributes the provided permissions to the package of this client. + * + * @param permissions list of permissions covering data the client is about to receive + * @param noteMessage message that should be noted alongside permissions attribution to + * facilitate debugging + * @return true if client has ability to use all of the provided permissions + */ + /* package */ boolean notePermissions(List<String> permissions, String noteMessage) { + for (String permission : permissions) { + int opCode = mAppOpsManager.permissionToOpCode(permission); + if (opCode != AppOpsManager.OP_NONE) { + if (mAppOpsManager.noteOp(opCode, mUid, mPackage, mAttributionTag, noteMessage) + != AppOpsManager.MODE_ALLOWED) { + return false; + } + } + } + + return true; + } + + /** * @return true if the client is a PendingIntent client that has been cancelled. */ /* package */ boolean isPendingIntentCancelled() { @@ -399,6 +618,101 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** + * Handles timer expiry for a client whose auth state with a nanoapp was previously in the grace + * period. + */ + /* package */ void handleAuthStateTimerExpiry(long nanoAppId) { + AuthStateDenialTimer timer; + synchronized (mMessageChannelNanoappIdMap) { + timer = mNappToAuthTimerMap.remove(nanoAppId); + } + + if (timer != null) { + updateNanoAppAuthState( + nanoAppId, false /* hasPermissions */, true /* gracePeriodExpired */); + } + } + + /** + * Verifies this client has the permissions to communicate with all of the nanoapps it has + * communicated with in the past. + */ + private void checkNanoappPermsAsync() { + ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction( + mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage); + mTransactionManager.addTransaction(transaction); + } + + /** + * Updates the latest authentication state for this client to be able to communicate with the + * given nanoapp. + */ + private void updateNanoAppAuthState( + long nanoAppId, boolean hasPermissions, boolean gracePeriodExpired) { + updateNanoAppAuthState( + nanoAppId, hasPermissions, gracePeriodExpired, false /* forceDenied */); + } + + /* package */ void updateNanoAppAuthState( + long nanoAppId, boolean hasPermissions, boolean gracePeriodExpired, + boolean forceDenied) { + int curAuthState; + int newAuthState; + synchronized (mMessageChannelNanoappIdMap) { + curAuthState = mMessageChannelNanoappIdMap.getOrDefault( + nanoAppId, AUTHORIZATION_GRANTED); + newAuthState = curAuthState; + // The below logic ensures that only the following transitions are possible: + // GRANTED -> DENIED_GRACE_PERIOD only if permissions have been lost + // DENIED_GRACE_PERIOD -> DENIED only if the grace period expires + // DENIED/DENIED_GRACE_PERIOD -> GRANTED only if permissions are granted again + // any state -> DENIED if "forceDenied" is true + if (forceDenied) { + newAuthState = AUTHORIZATION_DENIED; + } else if (gracePeriodExpired) { + if (curAuthState == AUTHORIZATION_DENIED_GRACE_PERIOD) { + newAuthState = AUTHORIZATION_DENIED; + } + } else { + if (curAuthState == AUTHORIZATION_GRANTED && !hasPermissions) { + newAuthState = AUTHORIZATION_DENIED_GRACE_PERIOD; + } else if (curAuthState != AUTHORIZATION_GRANTED && hasPermissions) { + newAuthState = AUTHORIZATION_GRANTED; + } + } + + if (newAuthState != AUTHORIZATION_DENIED_GRACE_PERIOD) { + AuthStateDenialTimer timer = mNappToAuthTimerMap.remove(nanoAppId); + if (timer != null) { + timer.cancel(); + } + } else if (curAuthState == AUTHORIZATION_GRANTED) { + AuthStateDenialTimer timer = + new AuthStateDenialTimer(this, nanoAppId, Looper.getMainLooper()); + mNappToAuthTimerMap.put(nanoAppId, timer); + timer.start(); + } + + if (curAuthState != newAuthState) { + mMessageChannelNanoappIdMap.put(nanoAppId, newAuthState); + } + } + if (curAuthState != newAuthState) { + // Don't send the callback in the synchronized block or it could end up in a deadlock. + sendAuthStateCallback(nanoAppId, newAuthState); + } + } + + private void sendAuthStateCallback(long nanoAppId, int authState) { + invokeCallback(callback -> callback.onClientAuthorizationChanged(nanoAppId, authState)); + + Supplier<Intent> supplier = + () -> createIntent(ContextHubManager.EVENT_CLIENT_AUTHORIZATION, nanoAppId) + .putExtra(ContextHubManager.EXTRA_CLIENT_AUTHORIZATION_STATE, authState); + sendPendingIntent(supplier, nanoAppId); + } + + /** * Helper function to invoke a specified client callback, if the connection is open. * * @param consumer the consumer specifying the callback to invoke @@ -507,6 +821,20 @@ public class ContextHubClientBroker extends IContextHubClient.Stub mClientManager.unregisterClient(mHostEndPointId); mRegistered = false; } + mAppOpsManager.stopWatchingMode(this); + } + + private String authStateToString(@ContextHubManager.AuthorizationState int state) { + switch (state) { + case AUTHORIZATION_DENIED: + return "DENIED"; + case AUTHORIZATION_DENIED_GRACE_PERIOD: + return "DENIED_GRACE_PERIOD"; + case AUTHORIZATION_GRANTED: + return "GRANTED"; + default: + return "UNKNOWN"; + } } /** @@ -545,11 +873,14 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } else { out += "package: " + mPackage; } - if (mMessageChannelNanoappIdSet.size() > 0) { + if (mMessageChannelNanoappIdMap.size() > 0) { out += " messageChannelNanoappSet: ("; - Iterator<Long> it = mMessageChannelNanoappIdSet.iterator(); + Iterator<Map.Entry<Long, Integer>> it = + mMessageChannelNanoappIdMap.entrySet().iterator(); while (it.hasNext()) { - out += "0x" + Long.toHexString(it.next()); + Map.Entry<Long, Integer> entry = it.next(); + out += "0x" + Long.toHexString(entry.getKey()) + " auth state: " + + authStateToString(entry.getValue()); if (it.hasNext()) { out += ","; } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java index 0351edb8f218..e3522f6d487c 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java @@ -36,6 +36,7 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Iterator; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; @@ -136,8 +137,7 @@ import java.util.function.Consumer; } } - /* package */ ContextHubClientManager( - Context context, IContextHubWrapper contextHubProxy) { + /* package */ ContextHubClientManager(Context context, IContextHubWrapper contextHubProxy) { mContext = context; mContextHubProxy = contextHubProxy; } @@ -155,13 +155,15 @@ import java.util.function.Consumer; */ /* package */ IContextHubClient registerClient( ContextHubInfo contextHubInfo, IContextHubClientCallback clientCallback, - String attributionTag) { + String attributionTag, ContextHubTransactionManager transactionManager, + String packageName) { ContextHubClientBroker broker; synchronized (this) { short hostEndPointId = getHostEndPointId(); broker = new ContextHubClientBroker( mContext, mContextHubProxy, this /* clientManager */, contextHubInfo, - hostEndPointId, clientCallback, attributionTag); + hostEndPointId, clientCallback, attributionTag, transactionManager, + packageName); mHostEndPointIdToClientMap.put(hostEndPointId, broker); mRegistrationRecordDeque.add( new RegistrationRecord(broker.toString(), ACTION_REGISTERED)); @@ -194,7 +196,7 @@ import java.util.function.Consumer; */ /* package */ IContextHubClient registerClient( ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId, - String attributionTag) { + String attributionTag, ContextHubTransactionManager transactionManager) { ContextHubClientBroker broker; String registerString = "Regenerated"; synchronized (this) { @@ -204,7 +206,8 @@ import java.util.function.Consumer; short hostEndPointId = getHostEndPointId(); broker = new ContextHubClientBroker( mContext, mContextHubProxy, this /* clientManager */, contextHubInfo, - hostEndPointId, pendingIntent, nanoAppId, attributionTag); + hostEndPointId, pendingIntent, nanoAppId, attributionTag, + transactionManager); mHostEndPointIdToClientMap.put(hostEndPointId, broker); registerString = "Registered"; mRegistrationRecordDeque.add( @@ -224,9 +227,14 @@ import java.util.function.Consumer; * Handles a message sent from a nanoapp. * * @param contextHubId the ID of the hub where the nanoapp sent the message from - * @param message the message send by a nanoapp + * @param message the message send by a nanoapp + * @param nanoappPermissions the set of permissions the nanoapp holds + * @param messagePermissions the set of permissions that should be used for attributing + * permissions when this message is consumed by a client */ - /* package */ void onMessageFromNanoApp(int contextHubId, ContextHubMsg message) { + /* package */ void onMessageFromNanoApp( + int contextHubId, ContextHubMsg message, List<String> nanoappPermissions, + List<String> messagePermissions) { NanoAppMessage clientMessage = ContextHubServiceUtil.createNanoAppMessage(message); if (DEBUG_LOG_ENABLED) { @@ -234,11 +242,19 @@ import java.util.function.Consumer; } if (clientMessage.isBroadcastMessage()) { - broadcastMessage(contextHubId, clientMessage); + // Broadcast messages shouldn't be sent with any permissions tagged per CHRE API + // requirements. + if (!messagePermissions.isEmpty()) { + Log.wtf(TAG, "Received broadcast message with permissions from " + message.appName); + } + + broadcastMessage( + contextHubId, clientMessage, nanoappPermissions, messagePermissions); } else { ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(message.hostEndPoint); if (proxy != null) { - proxy.sendMessageToClient(clientMessage); + proxy.sendMessageToClient( + clientMessage, nanoappPermissions, messagePermissions); } else { Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = " + message.hostEndPoint + ")"); @@ -303,6 +319,21 @@ import java.util.function.Consumer; } /** + * Runs a command for each client that is attached to a hub with the given ID. + * + * @param contextHubId the ID of the hub + * @param callback the command to invoke for the client + */ + /* package */ void forEachClientOfHub( + int contextHubId, Consumer<ContextHubClientBroker> callback) { + for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) { + if (broker.getAttachedContextHubId() == contextHubId) { + callback.accept(broker); + } + } + } + + /** * Returns an available host endpoint ID. * * @returns an available host endpoint ID @@ -333,22 +364,12 @@ import java.util.function.Consumer; * @param contextHubId the ID of the hub where the nanoapp sent the message from * @param message the message send by a nanoapp */ - private void broadcastMessage(int contextHubId, NanoAppMessage message) { - forEachClientOfHub(contextHubId, client -> client.sendMessageToClient(message)); - } - - /** - * Runs a command for each client that is attached to a hub with the given ID. - * - * @param contextHubId the ID of the hub - * @param callback the command to invoke for the client - */ - private void forEachClientOfHub(int contextHubId, Consumer<ContextHubClientBroker> callback) { - for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) { - if (broker.getAttachedContextHubId() == contextHubId) { - callback.accept(broker); - } - } + private void broadcastMessage( + int contextHubId, NanoAppMessage message, List<String> nanoappPermissions, + List<String> messagePermissions) { + forEachClientOfHub(contextHubId, + client -> client.sendMessageToClient( + message, nanoappPermissions, messagePermissions)); } /** diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 2eafe6ad41a0..0737db7b8358 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -50,6 +50,8 @@ import android.net.wifi.WifiManager; import android.os.Binder; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; @@ -64,6 +66,7 @@ import java.io.PrintWriter; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -98,6 +101,7 @@ public class ContextHubService extends IContextHubService.Stub { private final Context mContext; private final Map<Integer, ContextHubInfo> mContextHubIdToInfoMap; + private final List<String> mSupportedContextHubPerms; private final List<ContextHubInfo> mContextHubInfoList; private final RemoteCallbackList<IContextHubCallback> mCallbacksList = new RemoteCallbackList<>(); @@ -140,7 +144,7 @@ public class ContextHubService extends IContextHubService.Stub { public void handleClientMsg(ContextHubMsg message) { handleClientMessageCallback(mContextHubId, message, Collections.emptyList() /* nanoappPermissions */, - Collections.emptyList() /* messageContentPermissions */); + Collections.emptyList() /* messagePermissions */); } @Override @@ -167,9 +171,9 @@ public class ContextHubService extends IContextHubService.Stub { @Override public void handleClientMsg_1_2(android.hardware.contexthub.V1_2.ContextHubMsg message, - ArrayList<String> messageContentPermissions) { + ArrayList<String> messagePermissions) { handleClientMessageCallback(mContextHubId, message.msg_1_0, message.permissions, - messageContentPermissions); + messagePermissions); } @Override @@ -187,14 +191,11 @@ public class ContextHubService extends IContextHubService.Stub { mClientManager = null; mDefaultClientMap = Collections.emptyMap(); mContextHubIdToInfoMap = Collections.emptyMap(); + mSupportedContextHubPerms = Collections.emptyList(); mContextHubInfoList = Collections.emptyList(); return; } - mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper); - mTransactionManager = new ContextHubTransactionManager( - mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager); - Pair<List<ContextHub>, List<String>> hubInfo; try { hubInfo = mContextHubWrapper.getHubs(); @@ -202,16 +203,21 @@ public class ContextHubService extends IContextHubService.Stub { Log.e(TAG, "RemoteException while getting Context Hub info", e); hubInfo = new Pair(Collections.emptyList(), Collections.emptyList()); } + mContextHubIdToInfoMap = Collections.unmodifiableMap( ContextHubServiceUtil.createContextHubInfoMap(hubInfo.first)); + mSupportedContextHubPerms = hubInfo.second; mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values()); + mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper); + mTransactionManager = new ContextHubTransactionManager( + mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager); HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>(); for (int contextHubId : mContextHubIdToInfoMap.keySet()) { ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId); IContextHubClient client = mClientManager.registerClient( contextHubInfo, createDefaultClientCallback(contextHubId), - null /* attributionTag */); + null /* attributionTag */, mTransactionManager, mContext.getPackageName()); defaultClientMap.put(contextHubId, client); try { @@ -362,6 +368,12 @@ public class ContextHubService extends IContextHubService.Stub { } @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback callback, ResultReceiver result) { + new ContextHubShellCommand(mContext, this).exec(this, in, out, err, args, callback, result); + } + + @Override public int registerCallback(IContextHubCallback callback) throws RemoteException { checkPermissions(); mCallbacksList.register(callback); @@ -611,13 +623,15 @@ public class ContextHubService extends IContextHubService.Stub { /** * Handles a unicast or broadcast message from a nanoapp. * - * @param contextHubId the ID of the hub the message came from - * @param message the message contents + * @param contextHubId the ID of the hub the message came from + * @param message the message contents + * @param reqPermissions the permissions required to consume this message */ private void handleClientMessageCallback( int contextHubId, ContextHubMsg message, List<String> nanoappPermissions, - List<String> messageContentPermissions) { - mClientManager.onMessageFromNanoApp(contextHubId, message); + List<String> messagePermissions) { + mClientManager.onMessageFromNanoApp( + contextHubId, message, nanoappPermissions, messagePermissions); } /** @@ -723,6 +737,7 @@ public class ContextHubService extends IContextHubService.Stub { * @param contextHubId the ID of the hub this client is attached to * @param clientCallback the client interface to register with the service * @param attributionTag an optional attribution tag within the given package + * @param packageName the name of the package creating this client * @return the generated client interface, null if registration was unsuccessful * @throws IllegalArgumentException if contextHubId is not a valid ID * @throws IllegalStateException if max number of clients have already registered @@ -731,7 +746,7 @@ public class ContextHubService extends IContextHubService.Stub { @Override public IContextHubClient createClient( int contextHubId, IContextHubClientCallback clientCallback, - @Nullable String attributionTag) throws RemoteException { + @Nullable String attributionTag, String packageName) throws RemoteException { checkPermissions(); if (!isValidContextHubId(contextHubId)) { throw new IllegalArgumentException("Invalid context hub ID " + contextHubId); @@ -741,7 +756,8 @@ public class ContextHubService extends IContextHubService.Stub { } ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId); - return mClientManager.registerClient(contextHubInfo, clientCallback, attributionTag); + return mClientManager.registerClient( + contextHubInfo, clientCallback, attributionTag, mTransactionManager, packageName); } /** @@ -766,7 +782,7 @@ public class ContextHubService extends IContextHubService.Stub { ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId); return mClientManager.registerClient( - contextHubInfo, pendingIntent, nanoAppId, attributionTag); + contextHubInfo, pendingIntent, nanoAppId, attributionTag, mTransactionManager); } /** @@ -907,6 +923,8 @@ public class ContextHubService extends IContextHubService.Stub { for (ContextHubInfo hubInfo : mContextHubIdToInfoMap.values()) { pw.println(hubInfo); } + pw.println("Supported permissions: " + + Arrays.toString(mSupportedContextHubPerms.toArray())); pw.println(""); pw.println("=================== NANOAPPS ===================="); // Dump nanoAppHash @@ -923,6 +941,16 @@ public class ContextHubService extends IContextHubService.Stub { // dump eventLog } + /* package */ void denyClientAuthState(int contextHubId, String packageName, long nanoAppId) { + mClientManager.forEachClientOfHub(contextHubId, client -> { + if (client.getPackageName().equals(packageName)) { + client.updateNanoAppAuthState( + nanoAppId, false /* hasPermissions */, false /* gracePeriodExpired */, + true /* forceDenied */); + } + }); + } + private void dump(ProtoOutputStream proto) { mContextHubIdToInfoMap.values().forEach(hubInfo -> { long token = proto.start(ContextHubServiceProto.CONTEXT_HUB_INFO); diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubShellCommand.java b/services/core/java/com/android/server/location/contexthub/ContextHubShellCommand.java new file mode 100644 index 000000000000..5ec85e661b98 --- /dev/null +++ b/services/core/java/com/android/server/location/contexthub/ContextHubShellCommand.java @@ -0,0 +1,71 @@ +/* + * 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.location.contexthub; + +import android.content.Context; +import android.os.ShellCommand; + +import java.io.PrintWriter; + +/** + * ShellCommands for ContextHubService. + * + * Use with {@code adb shell cmd contexthub ...}. + * + * @hide + */ +public class ContextHubShellCommand extends ShellCommand { + + // Internal service impl -- must perform security checks before touching. + private final ContextHubService mInternal; + + public ContextHubShellCommand(Context context, ContextHubService service) { + mInternal = service; + + context.enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_CONTEXT_HUB, "ContextHubShellCommand"); + } + + @Override + public int onCommand(String cmd) { + if ("deny".equals(cmd)) { + return runDisableAuth(); + } + + return handleDefaultCommands(cmd); + } + + private int runDisableAuth() { + int contextHubId = Integer.decode(getNextArgRequired()); + String packageName = getNextArgRequired(); + long nanoAppId = Long.decode(getNextArgRequired()); + + mInternal.denyClientAuthState(contextHubId, packageName, nanoAppId); + return 0; + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("ContextHub commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(" deny [contextHubId] [packageName] [nanoAppId]"); + pw.println(" Immediately transitions the package's authentication state to denied so"); + pw.println(" can no longer communciate with the nanoapp."); + } +} diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index c1d63dd2fbc9..3a5c220eeeae 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -106,9 +106,8 @@ public abstract class IContextHubWrapper { /** * Calls the appropriate sendMessageToHub function depending on the HAL version. */ - public abstract int sendMessageToHub( - int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message, - ArrayList<String> hostPermissions) throws RemoteException; + public abstract int sendMessageToHub(int hubId, + android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException; /** * @return A valid instance of Contexthub HAL 1.0. @@ -181,9 +180,8 @@ public abstract class IContextHubWrapper { mHub.registerCallback(hubId, callback); } - public int sendMessageToHub( - int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message, - ArrayList<String> hostPermissions) throws RemoteException { + public int sendMessageToHub(int hubId, + android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException { return mHub.sendMessageToHub(hubId, message); } @@ -236,9 +234,8 @@ public abstract class IContextHubWrapper { mHub.registerCallback(hubId, callback); } - public int sendMessageToHub( - int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message, - ArrayList<String> hostPermissions) throws RemoteException { + public int sendMessageToHub(int hubId, + android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException { return mHub.sendMessageToHub(hubId, message); } @@ -307,13 +304,11 @@ public abstract class IContextHubWrapper { mHub.registerCallback_1_2(hubId, callback); } - public int sendMessageToHub( - int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message, - ArrayList<String> hostPermissions) throws RemoteException { + public int sendMessageToHub(int hubId, + android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException { android.hardware.contexthub.V1_2.ContextHubMsg newMessage = new android.hardware.contexthub.V1_2.ContextHubMsg(); newMessage.msg_1_0 = message; - newMessage.permissions = hostPermissions; return mHub.sendMessageToHub_1_2(hubId, newMessage); } diff --git a/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java index 6a89079d81ba..736b65484de6 100644 --- a/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java +++ b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java @@ -21,50 +21,80 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Binder; import android.os.PowerManager; +import com.android.internal.util.Preconditions; import com.android.server.FgThread; +import java.util.Objects; + /** * Provides accessors and listeners for device stationary state. */ public class SystemDeviceIdleHelper extends DeviceIdleHelper { private final Context mContext; - private final PowerManager mPowerManager; + private PowerManager mPowerManager; + + private boolean mSystemReady; + private boolean mRegistrationRequired; private @Nullable BroadcastReceiver mReceiver; public SystemDeviceIdleHelper(Context context) { mContext = context; - mPowerManager = context.getSystemService(PowerManager.class); } - @Override - protected void registerInternal() { - if (mReceiver == null) { - mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - notifyDeviceIdleChanged(); - } - }; + public synchronized void onSystemReady() { + mSystemReady = true; + mPowerManager = Objects.requireNonNull(mContext.getSystemService(PowerManager.class)); + onRegistrationStateChanged(); + } - mContext.registerReceiver(mReceiver, - new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED), null, - FgThread.getHandler()); - } + @Override + protected synchronized void registerInternal() { + mRegistrationRequired = true; + onRegistrationStateChanged(); } @Override - protected void unregisterInternal() { - if (mReceiver != null) { - mContext.unregisterReceiver(mReceiver); + protected synchronized void unregisterInternal() { + mRegistrationRequired = false; + onRegistrationStateChanged(); + } + + private void onRegistrationStateChanged() { + if (!mSystemReady) { + return; + } + + final long identity = Binder.clearCallingIdentity(); + try { + if (mRegistrationRequired && mReceiver == null) { + BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + notifyDeviceIdleChanged(); + } + }; + mContext.registerReceiver(receiver, + new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED), null, + FgThread.getHandler()); + mReceiver = receiver; + } else if (!mRegistrationRequired && mReceiver != null) { + BroadcastReceiver receiver = mReceiver; + mReceiver = null; + mContext.unregisterReceiver(receiver); + } + } finally { + Binder.restoreCallingIdentity(identity); } } @Override public boolean isDeviceIdle() { + Preconditions.checkState(mPowerManager != null); return mPowerManager.isDeviceIdleMode(); } } diff --git a/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java index 6f0e681b808f..9874ecfdefdf 100644 --- a/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java +++ b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java @@ -16,6 +16,9 @@ package com.android.server.location.injector; +import android.os.Binder; + +import com.android.internal.util.Preconditions; import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; @@ -26,19 +29,35 @@ import java.util.Objects; */ public class SystemDeviceStationaryHelper extends DeviceStationaryHelper { - private final DeviceIdleInternal mDeviceIdle; + private DeviceIdleInternal mDeviceIdle; + + public SystemDeviceStationaryHelper() {} - public SystemDeviceStationaryHelper() { + public void onSystemReady() { mDeviceIdle = Objects.requireNonNull(LocalServices.getService(DeviceIdleInternal.class)); } @Override public void addListener(DeviceIdleInternal.StationaryListener listener) { - mDeviceIdle.registerStationaryListener(listener); + Preconditions.checkState(mDeviceIdle != null); + + long identity = Binder.clearCallingIdentity(); + try { + mDeviceIdle.registerStationaryListener(listener); + } finally { + Binder.restoreCallingIdentity(identity); + } } @Override public void removeListener(DeviceIdleInternal.StationaryListener listener) { - mDeviceIdle.unregisterStationaryListener(listener); + Preconditions.checkState(mDeviceIdle != null); + + long identity = Binder.clearCallingIdentity(); + try { + mDeviceIdle.unregisterStationaryListener(listener); + } finally { + Binder.restoreCallingIdentity(identity); + } } } diff --git a/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java b/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java index 0d7bcb049f15..03ade5f2abd8 100644 --- a/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java +++ b/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java @@ -68,7 +68,7 @@ public class SystemScreenInteractiveHelper extends ScreenInteractiveHelper { mReady = true; } - private void onScreenInteractiveChanged(boolean interactive) { + void onScreenInteractiveChanged(boolean interactive) { if (interactive == mIsInteractive) { return; } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 685e9e6ad420..8da2d67d6691 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -2902,11 +2902,8 @@ public class LockSettingsService extends ILockSettings.Stub { FingerprintManager mFingerprintManager = mInjector.getFingerprintManager(); if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()) { if (mFingerprintManager.hasEnrolledFingerprints(userId)) { - CountDownLatch latch = new CountDownLatch(1); - // For the purposes of M and N, groupId is the same as userId. - Fingerprint finger = new Fingerprint(null, userId, 0, 0); - mFingerprintManager.remove(finger, userId, - fingerprintManagerRemovalCallback(latch)); + final CountDownLatch latch = new CountDownLatch(1); + mFingerprintManager.removeAll(userId, fingerprintManagerRemovalCallback(latch)); try { latch.await(10000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { @@ -2920,9 +2917,8 @@ public class LockSettingsService extends ILockSettings.Stub { FaceManager mFaceManager = mInjector.getFaceManager(); if (mFaceManager != null && mFaceManager.isHardwareDetected()) { if (mFaceManager.hasEnrolledTemplates(userId)) { - CountDownLatch latch = new CountDownLatch(1); - Face face = new Face(null, 0, 0); - mFaceManager.remove(face, userId, faceManagerRemovalCallback(latch)); + final CountDownLatch latch = new CountDownLatch(1); + mFaceManager.removeAll(userId, faceManagerRemovalCallback(latch)); try { latch.await(10000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { @@ -2936,10 +2932,8 @@ public class LockSettingsService extends ILockSettings.Stub { CountDownLatch latch) { return new FingerprintManager.RemovalCallback() { @Override - public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence err) { - Slog.e(TAG, String.format( - "Can't remove fingerprint %d in group %d. Reason: %s", - fp.getBiometricId(), fp.getGroupId(), err)); + public void onRemovalError(@Nullable Fingerprint fp, int errMsgId, CharSequence err) { + Slog.e(TAG, "Unable to remove fingerprint, error: " + err); latch.countDown(); } @@ -2955,9 +2949,8 @@ public class LockSettingsService extends ILockSettings.Stub { private FaceManager.RemovalCallback faceManagerRemovalCallback(CountDownLatch latch) { return new FaceManager.RemovalCallback() { @Override - public void onRemovalError(Face face, int errMsgId, CharSequence err) { - Slog.e(TAG, String.format("Can't remove face %d. Reason: %s", - face.getBiometricId(), err)); + public void onRemovalError(@Nullable Face face, int errMsgId, CharSequence err) { + Slog.e(TAG, "Unable to remove face, error: " + err); latch.countDown(); } 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 4f4b4f259762..639dda6f8981 100644 --- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java +++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java @@ -121,8 +121,8 @@ public final class MediaMetricsManagerService extends SystemService { StatsEvent statsEvent = StatsEvent.newBuilder() .setAtomId(321) .writeString(sessionId) - .writeInt(event.getType()) - .writeLong(event.getTimeSincePlaybackCreatedMillis()) + .writeInt(event.getNetworkType()) + .writeLong(event.getTimeSinceCreatedMillis()) .usePooledBuffer() .build(); StatsLog.write(statsEvent); @@ -132,7 +132,7 @@ public final class MediaMetricsManagerService extends SystemService { public void reportTrackChangeEvent( String sessionId, TrackChangeEvent event, int userId) { StatsEvent statsEvent = StatsEvent.newBuilder() - .setAtomId(321) + .setAtomId(324) .writeString(sessionId) .writeInt(event.getTrackState()) .writeInt(event.getTrackChangeReason()) @@ -140,7 +140,7 @@ public final class MediaMetricsManagerService extends SystemService { .writeString(event.getSampleMimeType()) .writeString(event.getCodecName()) .writeInt(event.getBitrate()) - .writeLong(event.getTimeSincePlaybackCreatedMillis()) + .writeLong(event.getTimeSinceCreatedMillis()) .writeInt(event.getTrackType()) .writeString(event.getLanguage()) .writeString(event.getLanguageRegion()) diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java index 2d540de69660..c4b6485d2a2f 100644 --- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java +++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java @@ -23,6 +23,7 @@ import android.net.Uri; import android.os.Process; import android.text.TextUtils; import android.util.Pair; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; @@ -42,9 +43,6 @@ import java.util.Map; */ public class OverlayActorEnforcer { - // By default, the reason is not logged to prevent leaks of why it failed - private static final boolean DEBUG_REASON = false; - private final PackageManagerHelper mPackageManager; /** @@ -85,17 +83,18 @@ public class OverlayActorEnforcer { void enforceActor(@NonNull OverlayInfo overlayInfo, @NonNull String methodName, int callingUid, int userId) throws SecurityException { - ActorState actorState = isAllowedActor(methodName, overlayInfo, callingUid, userId); + final ActorState actorState = isAllowedActor(methodName, overlayInfo, callingUid, userId); if (actorState == ActorState.ALLOWED) { return; } - String targetOverlayableName = overlayInfo.targetOverlayableName; - throw new SecurityException("UID" + callingUid + " is not allowed to call " - + methodName + " for " + final String targetOverlayableName = overlayInfo.targetOverlayableName; + final String errorMessage = "UID" + callingUid + " is not allowed to call " + methodName + + " for " + (TextUtils.isEmpty(targetOverlayableName) ? "" : (targetOverlayableName + " in ")) - + overlayInfo.targetPackageName + (DEBUG_REASON ? (" because " + actorState) : "") - ); + + overlayInfo.targetPackageName + " for user " + userId; + Slog.w(OverlayManagerService.TAG, errorMessage + " because " + actorState); + throw new SecurityException(errorMessage); } /** diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java index a83edb75badb..9984bfae30fd 100644 --- a/services/core/java/com/android/server/os/NativeTombstoneManager.java +++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java @@ -16,17 +16,29 @@ package com.android.server.os; +import static android.app.ApplicationExitInfo.REASON_CRASH_NATIVE; +import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import android.annotation.AppIdInt; +import android.annotation.CurrentTimeMillisLong; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.app.ApplicationExitInfo; +import android.app.IParcelFileDescriptorRetriever; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.os.FileObserver; import android.os.Handler; import android.os.ParcelFileDescriptor; import android.os.UserHandle; +import android.system.ErrnoException; +import android.system.Os; +import android.system.StructStat; import android.util.Slog; import android.util.SparseArray; import android.util.proto.ProtoInputStream; @@ -34,6 +46,7 @@ import android.util.proto.ProtoInputStream; import com.android.internal.annotations.GuardedBy; import com.android.server.BootReceiver; import com.android.server.ServiceThread; +import com.android.server.os.TombstoneProtos.Cause; import com.android.server.os.TombstoneProtos.Tombstone; import libcore.io.IoUtils; @@ -42,7 +55,11 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; /** * A class to manage native tombstones. @@ -75,6 +92,9 @@ public final class NativeTombstoneManager { } void onSystemReady() { + registerForUserRemoval(); + registerForPackageRemoval(); + // Scan existing tombstones. mHandler.post(() -> { final File[] tombstoneFiles = TOMBSTONE_DIR.listFiles(); @@ -94,8 +114,9 @@ public final class NativeTombstoneManager { if (filename.endsWith(".pb")) { handleProtoTombstone(path); + BootReceiver.addTombstoneToDropBox(mContext, path, true); } else { - BootReceiver.addTombstoneToDropBox(mContext, path); + BootReceiver.addTombstoneToDropBox(mContext, path, false); } } @@ -145,18 +166,164 @@ public final class NativeTombstoneManager { } } + /** + * Remove native tombstones matching a user and/or app. + * + * @param userId user id to filter by, selects all users if empty + * @param appId app id to filter by, selects all users if empty + */ + public void purge(Optional<Integer> userId, Optional<Integer> appId) { + mHandler.post(() -> { + synchronized (mLock) { + for (int i = mTombstones.size() - 1; i >= 0; --i) { + TombstoneFile tombstone = mTombstones.valueAt(i); + if (tombstone.matches(userId, appId)) { + tombstone.purge(); + mTombstones.removeAt(i); + } + } + } + }); + } + + private void purgePackage(int uid, boolean allUsers) { + final int appId = UserHandle.getAppId(uid); + Optional<Integer> userId; + if (allUsers) { + userId = Optional.empty(); + } else { + userId = Optional.of(UserHandle.getUserId(uid)); + } + purge(userId, Optional.of(appId)); + } + + private void purgeUser(int uid) { + purge(Optional.of(uid), Optional.empty()); + } + + private void registerForPackageRemoval() { + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); + filter.addDataScheme("package"); + mContext.registerReceiverForAllUsers(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final int uid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL); + if (uid == UserHandle.USER_NULL) return; + + final boolean allUsers = intent.getBooleanExtra( + Intent.EXTRA_REMOVED_FOR_ALL_USERS, false); + + purgePackage(uid, allUsers); + } + }, filter, null, mHandler); + } + + private void registerForUserRemoval() { + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiverForAllUsers(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userId < 1) return; + + purgeUser(userId); + } + }, filter, null, mHandler); + } + + /** + * Collect native tombstones. + * + * @param output list to append to + * @param callingUid POSIX uid to filter by + * @param pid pid to filter by, ignored if zero + * @param maxNum maximum number of elements in output + */ + public void collectTombstones(ArrayList<ApplicationExitInfo> output, int callingUid, int pid, + int maxNum) { + CompletableFuture<Object> future = new CompletableFuture<>(); + + if (!UserHandle.isApp(callingUid)) { + return; + } + + final int userId = UserHandle.getUserId(callingUid); + final int appId = UserHandle.getAppId(callingUid); + + mHandler.post(() -> { + boolean appendedTombstones = false; + + synchronized (mLock) { + final int tombstonesSize = mTombstones.size(); + + tombstoneIter: + for (int i = 0; i < tombstonesSize; ++i) { + TombstoneFile tombstone = mTombstones.valueAt(i); + if (tombstone.matches(Optional.of(userId), Optional.of(appId))) { + if (pid != 0 && tombstone.mPid != pid) { + continue; + } + + // Try to attach to an existing REASON_CRASH_NATIVE. + final int outputSize = output.size(); + for (int j = 0; j < outputSize; ++j) { + ApplicationExitInfo exitInfo = output.get(j); + if (tombstone.matches(exitInfo)) { + exitInfo.setNativeTombstoneRetriever(tombstone.getPfdRetriever()); + continue tombstoneIter; + } + } + + if (output.size() < maxNum) { + appendedTombstones = true; + output.add(tombstone.toAppExitInfo()); + } + } + } + } + + if (appendedTombstones) { + Collections.sort(output, (lhs, rhs) -> { + // Reports should be ordered with newest reports first. + long diff = rhs.getTimestamp() - lhs.getTimestamp(); + if (diff < 0) { + return -1; + } else if (diff == 0) { + return 0; + } else { + return 1; + } + }); + } + future.complete(null); + }); + + try { + future.get(); + } catch (ExecutionException | InterruptedException ex) { + throw new RuntimeException(ex); + } + } + static class TombstoneFile { final ParcelFileDescriptor mPfd; - final @UserIdInt int mUserId; - final @AppIdInt int mAppId; + @UserIdInt int mUserId; + @AppIdInt int mAppId; + + int mPid; + int mUid; + String mProcessName; + @CurrentTimeMillisLong long mTimestampMs; + String mCrashReason; boolean mPurged = false; + final IParcelFileDescriptorRetriever mRetriever = new ParcelFileDescriptorRetriever(); - TombstoneFile(ParcelFileDescriptor pfd, @UserIdInt int userId, @AppIdInt int appId) { + TombstoneFile(ParcelFileDescriptor pfd) { mPfd = pfd; - mUserId = userId; - mAppId = appId; } public boolean matches(Optional<Integer> userId, Optional<Integer> appId) { @@ -175,24 +342,90 @@ public final class NativeTombstoneManager { return true; } + public boolean matches(ApplicationExitInfo exitInfo) { + if (exitInfo.getReason() != REASON_CRASH_NATIVE) { + return false; + } + + if (exitInfo.getPid() != mPid) { + return false; + } + + if (exitInfo.getRealUid() != mUid) { + return false; + } + + if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 1000) { + return false; + } + + return true; + } + public void dispose() { IoUtils.closeQuietly(mPfd); } + public void purge() { + if (!mPurged) { + // There's no way to atomically unlink a specific file for which we have an fd from + // a path, which means that we can't safely delete a tombstone without coordination + // with tombstoned (which has a risk of deadlock if for example, system_server hangs + // with a flock). Do the next best thing, and just truncate the file. + // + // We don't have to worry about inflicting a SIGBUS on a process that has the + // tombstone mmaped, because we only clear if the package has been removed, which + // means no one with access to the tombstone should be left. + try { + Os.ftruncate(mPfd.getFileDescriptor(), 0); + } catch (ErrnoException ex) { + Slog.e(TAG, "Failed to truncate tombstone", ex); + } + mPurged = true; + } + } + static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) { final FileInputStream is = new FileInputStream(pfd.getFileDescriptor()); final ProtoInputStream stream = new ProtoInputStream(is); + int pid = 0; int uid = 0; + String processName = ""; + String crashReason = ""; String selinuxLabel = ""; try { while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { switch (stream.getFieldNumber()) { + case (int) Tombstone.PID: + pid = stream.readInt(Tombstone.PID); + break; + case (int) Tombstone.UID: uid = stream.readInt(Tombstone.UID); break; + case (int) Tombstone.PROCESS_NAME: + processName = stream.readString(Tombstone.PROCESS_NAME); + break; + + case (int) Tombstone.CAUSE: + long token = stream.start(Tombstone.CAUSE); + cause: + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (stream.getFieldNumber()) { + case (int) Cause.HUMAN_READABLE: + crashReason = stream.readString(Cause.HUMAN_READABLE); + break cause; + + default: + break; + } + } + stream.end(token); + + case (int) Tombstone.SELINUX_LABEL: selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL); break; @@ -211,6 +444,14 @@ public final class NativeTombstoneManager { return Optional.empty(); } + long timestampMs = 0; + try { + StructStat stat = Os.fstat(pfd.getFileDescriptor()); + timestampMs = stat.st_atim.tv_sec * 1000 + stat.st_atim.tv_nsec / 1000000; + } catch (ErrnoException ex) { + Slog.e(TAG, "Failed to get timestamp of tombstone", ex); + } + final int userId = UserHandle.getUserId(uid); final int appId = UserHandle.getAppId(uid); @@ -219,7 +460,74 @@ public final class NativeTombstoneManager { return Optional.empty(); } - return Optional.of(new TombstoneFile(pfd, userId, appId)); + TombstoneFile result = new TombstoneFile(pfd); + + result.mUserId = userId; + result.mAppId = appId; + result.mPid = pid; + result.mUid = uid; + result.mProcessName = processName; + result.mTimestampMs = timestampMs; + result.mCrashReason = crashReason; + + return Optional.of(result); + } + + public IParcelFileDescriptorRetriever getPfdRetriever() { + return mRetriever; + } + + public ApplicationExitInfo toAppExitInfo() { + ApplicationExitInfo info = new ApplicationExitInfo(); + info.setPid(mPid); + info.setRealUid(mUid); + info.setPackageUid(mUid); + info.setDefiningUid(mUid); + info.setProcessName(mProcessName); + info.setReason(ApplicationExitInfo.REASON_CRASH_NATIVE); + + // Signal numbers are architecture-specific! + // We choose to provide nothing here, to avoid leading users astray. + info.setStatus(0); + + // No way for us to find out. + info.setImportance(RunningAppProcessInfo.IMPORTANCE_GONE); + info.setPackageName(""); + info.setProcessStateSummary(null); + + // We could find out, but they didn't get OOM-killed... + info.setPss(0); + info.setRss(0); + + info.setTimestamp(mTimestampMs); + info.setDescription(mCrashReason); + + info.setSubReason(ApplicationExitInfo.SUBREASON_UNKNOWN); + info.setNativeTombstoneRetriever(mRetriever); + + return info; + } + + + class ParcelFileDescriptorRetriever extends IParcelFileDescriptorRetriever.Stub { + ParcelFileDescriptorRetriever() {} + + public @Nullable ParcelFileDescriptor getPfd() { + if (mPurged) { + return null; + } + + // Reopen the file descriptor as read-only. + try { + final String path = "/proc/self/fd/" + mPfd.getFd(); + ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(path), + MODE_READ_ONLY); + return pfd; + } catch (FileNotFoundException ex) { + Slog.e(TAG, "failed to reopen file descriptor as read-only", ex); + return null; + } + } } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 0ce26739b51c..5cb9d8ff3f31 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -70,6 +70,7 @@ import android.content.pm.IDataLoaderStatusListener; import android.content.pm.IPackageInstallObserver2; import android.content.pm.IPackageInstallerSession; import android.content.pm.IPackageInstallerSessionFileSystemConnector; +import android.content.pm.IPackageLoadingProgressCallback; import android.content.pm.InstallationFile; import android.content.pm.InstallationFileParcel; import android.content.pm.PackageInfo; @@ -321,6 +322,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private float mProgress = 0; @GuardedBy("mLock") private float mReportedProgress = -1; + @GuardedBy("mLock") + private float mIncrementalProgress = 0; /** State of the session. */ @GuardedBy("mLock") @@ -3770,7 +3773,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mIncrementalFileStorages = IncrementalFileStorages.initialize(mContext, stageDir, inheritedDir, params, statusListener, healthCheckParams, healthListener, - addedFiles, perUidReadTimeouts); + addedFiles, perUidReadTimeouts, + new IPackageLoadingProgressCallback.Stub() { + @Override + public void onPackageLoadingProgressChanged(float progress) { + synchronized (mLock) { + mIncrementalProgress = progress; + } + } + }); return false; } catch (IOException e) { throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE, e.getMessage(), diff --git a/services/core/java/com/android/server/pm/SettingsXml.java b/services/core/java/com/android/server/pm/SettingsXml.java index 9588a279ecec..ec643f598041 100644 --- a/services/core/java/com/android/server/pm/SettingsXml.java +++ b/services/core/java/com/android/server/pm/SettingsXml.java @@ -181,7 +181,10 @@ public class SettingsXml { } private void moveToFirstTag() throws IOException, XmlPullParserException { - // Move to first tag + if (mParser.getEventType() == XmlPullParser.START_TAG) { + return; + } + int type; //noinspection StatementWithEmptyBody while ((type = mParser.next()) != XmlPullParser.START_TAG diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 753f22da2b9d..24c27bedb9f7 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1522,6 +1522,14 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public boolean isUserForeground() { + int callingUserId = Binder.getCallingUserHandle().getIdentifier(); + int currentUser = Binder.withCleanCallingIdentity(() -> ActivityManager.getCurrentUser()); + // TODO(b/179163496): should return true for profile users of the current user as well + return currentUser == callingUserId; + } + + @Override public String getUserName() { if (!hasManageUsersOrPermission(android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED)) { throw new SecurityException("You need MANAGE_USERS or GET_ACCOUNTS_PRIVILEGED " diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index 86a92d792026..e5ed774fcaa8 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -1210,7 +1210,8 @@ public class DomainVerificationService extends SystemService // Filter to highest, non-zero packages ArraySet<String> approvedPackages = new ArraySet<>(); - for (int index = 0; index < infosSize; index++) { + int approvalsSize = packageApprovals.size(); + for (int index = 0; index < approvalsSize; index++) { if (packageApprovals.valueAt(index) == highestApproval) { approvedPackages.add(packageApprovals.keyAt(index)); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 6a441f1830d1..4ccd57ddc977 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -56,13 +56,11 @@ import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION; import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION; import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; @@ -2292,25 +2290,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { return attrs.type == TYPE_NOTIFICATION_SHADE; } - @Override - public boolean canBeHiddenByKeyguardLw(WindowState win) { - - // Keyguard visibility of window from activities are determined over activity visibility. - if (win.getAppToken() != null) { - return false; - } - switch (win.getAttrs().type) { - case TYPE_NOTIFICATION_SHADE: - case TYPE_STATUS_BAR: - case TYPE_NAVIGATION_BAR: - case TYPE_WALLPAPER: - return false; - default: - // Hide only windows below the keyguard host window. - return getWindowLayerLw(win) < getWindowLayerFromTypeLw(TYPE_NOTIFICATION_SHADE); - } - } - /** {@inheritDoc} */ @Override public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme, diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index db33e750d803..b5a9acacec83 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -672,7 +672,22 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { /** * @return whether {@param win} can be hidden by Keyguard */ - public boolean canBeHiddenByKeyguardLw(WindowState win); + default boolean canBeHiddenByKeyguardLw(WindowState win) { + // Keyguard visibility of window from activities are determined over activity visibility. + if (win.getAppToken() != null) { + return false; + } + switch (win.getAttrs().type) { + case TYPE_NOTIFICATION_SHADE: + case TYPE_STATUS_BAR: + case TYPE_NAVIGATION_BAR: + case TYPE_WALLPAPER: + return false; + default: + // Hide only windows below the keyguard host window. + return getWindowLayerLw(win) < getWindowLayerFromTypeLw(TYPE_NOTIFICATION_SHADE); + } + } /** * Called when the system would like to show a UI to indicate that an diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java index 6d9cb7522bef..2a95416747a6 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java @@ -47,7 +47,8 @@ public class PowerStatsDataStorage { private static final long DELETE_AGE_MILLIS = 48 * MILLISECONDS_PER_HOUR; private final ReentrantLock mLock = new ReentrantLock(); - private File mDataStorageDir; + private final File mDataStorageDir; + private final String mDataStorageFilename; private final FileRotator mFileRotator; private static class DataElement { @@ -168,6 +169,7 @@ public class PowerStatsDataStorage { public PowerStatsDataStorage(Context context, File dataStoragePath, String dataStorageFilename) { mDataStorageDir = dataStoragePath; + mDataStorageFilename = dataStorageFilename; if (!mDataStorageDir.exists() && !mDataStorageDir.mkdirs()) { Slog.wtf(TAG, "mDataStorageDir does not exist: " + mDataStorageDir.getPath()); @@ -177,33 +179,35 @@ public class PowerStatsDataStorage { // filename, so any files that don't match the current version number can be deleted. File[] files = mDataStorageDir.listFiles(); for (int i = 0; i < files.length; i++) { - // Meter and model files are stored in the same directory. + // Meter, model, and residency files are stored in the same directory. // // The format of filenames on disk is: // log.powerstats.meter.version.timestamp // log.powerstats.model.version.timestamp + // log.powerstats.residency.version.timestamp // // The format of dataStorageFilenames is: // log.powerstats.meter.version // log.powerstats.model.version + // log.powerstats.residency.version // - // A PowerStatsDataStorage object is created for meter and model data. Strip off - // the version and check that the current file we're checking starts with the stem - // (log.powerstats.meter or log.powerstats.model). If the stem matches and the - // version number is different, delete the old file. - int versionDot = dataStorageFilename.lastIndexOf('.'); - String beforeVersionDot = dataStorageFilename.substring(0, versionDot); + // A PowerStatsDataStorage object is created for meter, model, and residency data. + // Strip off the version and check that the current file we're checking starts with + // the stem (log.powerstats.meter, log.powerstats.model, log.powerstats.residency). + // If the stem matches and the version number is different, delete the old file. + int versionDot = mDataStorageFilename.lastIndexOf('.'); + String beforeVersionDot = mDataStorageFilename.substring(0, versionDot); // Check that the stems match. if (files[i].getName().startsWith(beforeVersionDot)) { // Check that the version number matches. If not, delete the old file. - if (!files[i].getName().startsWith(dataStorageFilename)) { + if (!files[i].getName().startsWith(mDataStorageFilename)) { files[i].delete(); } } } mFileRotator = new FileRotator(mDataStorageDir, - dataStorageFilename, + mDataStorageFilename, ROTATE_AGE_MILLIS, DELETE_AGE_MILLIS); } @@ -242,4 +246,19 @@ public class PowerStatsDataStorage { public void read(DataElementReadCallback callback) throws IOException { mFileRotator.readMatching(new DataReader(callback), Long.MIN_VALUE, Long.MAX_VALUE); } + + /** + * Deletes all stored log data. + */ + public void deleteLogs() { + File[] files = mDataStorageDir.listFiles(); + for (int i = 0; i < files.length; i++) { + int versionDot = mDataStorageFilename.lastIndexOf('.'); + String beforeVersionDot = mDataStorageFilename.substring(0, versionDot); + // Check that the stems before the version match. + if (files[i].getName().startsWith(beforeVersionDot)) { + files[i].delete(); + } + } + } } diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java index 37fc5a0f68fa..c4f29ea69218 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java @@ -26,6 +26,7 @@ import android.hardware.power.stats.StateResidencyResult; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.util.AtomicFile; import android.util.Slog; import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; @@ -41,14 +42,17 @@ import com.android.server.powerstats.ProtoStreamUtils.StateResidencyResultUtils; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.util.Arrays; /** - * PowerStatsLogger is responsible for logging model and meter energy data to on-device storage. - * Messages are sent to its message handler to request that energy data be logged, at which time it - * queries the PowerStats HAL and logs the data to on-device storage. The on-device storage is - * dumped to file by calling writeModelDataToFile, writeMeterDataToFile, or writeResidencyDataToFile - * with a file descriptor that points to the output file. + * PowerStatsLogger is responsible for logging model, meter, and residency data to on-device + * storage. Messages are sent to its message handler to request that energy data be logged, at + * which time it queries the PowerStats HAL and logs the data to on-device storage. The on-device + * storage is dumped to file by calling writeModelDataToFile, writeMeterDataToFile, or + * writeResidencyDataToFile with a file descriptor that points to the output file. */ public final class PowerStatsLogger extends Handler { private static final String TAG = PowerStatsLogger.class.getSimpleName(); @@ -61,6 +65,10 @@ public final class PowerStatsLogger extends Handler { private final PowerStatsDataStorage mPowerStatsModelStorage; private final PowerStatsDataStorage mPowerStatsResidencyStorage; private final IPowerStatsHALWrapper mPowerStatsHALWrapper; + private File mDataStoragePath; + private boolean mDeleteMeterDataOnBoot; + private boolean mDeleteModelDataOnBoot; + private boolean mDeleteResidencyDataOnBoot; @Override public void handleMessage(Message msg) { @@ -230,16 +238,99 @@ public final class PowerStatsLogger extends Handler { pos.flush(); } - public PowerStatsLogger(Context context, File dataStoragePath, String meterFilename, - String modelFilename, String residencyFilename, + private boolean dataChanged(String cachedFilename, byte[] dataCurrent) { + boolean dataChanged = false; + + if (mDataStoragePath.exists() || mDataStoragePath.mkdirs()) { + final File cachedFile = new File(mDataStoragePath, cachedFilename); + + if (cachedFile.exists()) { + // Get the byte array for the cached data. + final byte[] dataCached = new byte[(int) cachedFile.length()]; + + // Get the cached data from file. + try { + final FileInputStream fis = new FileInputStream(cachedFile.getPath()); + fis.read(dataCached); + } catch (IOException e) { + Slog.e(TAG, "Failed to read cached data from file"); + } + + // If the cached and current data are different, delete the data store. + dataChanged = !Arrays.equals(dataCached, dataCurrent); + } else { + // Either the cached file was somehow deleted, or this is the first + // boot of the device and we're creating the file for the first time. + // In either case, delete the log files. + dataChanged = true; + } + } + + return dataChanged; + } + + private void updateCacheFile(String cacheFilename, byte[] data) { + try { + final AtomicFile atomicCachedFile = + new AtomicFile(new File(mDataStoragePath, cacheFilename)); + final FileOutputStream fos = atomicCachedFile.startWrite(); + fos.write(data); + atomicCachedFile.finishWrite(fos); + } catch (IOException e) { + Slog.e(TAG, "Failed to write current data to cached file"); + } + } + + public boolean getDeleteMeterDataOnBoot() { + return mDeleteMeterDataOnBoot; + } + + public boolean getDeleteModelDataOnBoot() { + return mDeleteModelDataOnBoot; + } + + public boolean getDeleteResidencyDataOnBoot() { + return mDeleteResidencyDataOnBoot; + } + + public PowerStatsLogger(Context context, File dataStoragePath, + String meterFilename, String meterCacheFilename, + String modelFilename, String modelCacheFilename, + String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { super(Looper.getMainLooper()); mPowerStatsHALWrapper = powerStatsHALWrapper; - mPowerStatsMeterStorage = new PowerStatsDataStorage(context, dataStoragePath, + mDataStoragePath = dataStoragePath; + + mPowerStatsMeterStorage = new PowerStatsDataStorage(context, mDataStoragePath, meterFilename); - mPowerStatsModelStorage = new PowerStatsDataStorage(context, dataStoragePath, + mPowerStatsModelStorage = new PowerStatsDataStorage(context, mDataStoragePath, modelFilename); - mPowerStatsResidencyStorage = new PowerStatsDataStorage(context, dataStoragePath, + mPowerStatsResidencyStorage = new PowerStatsDataStorage(context, mDataStoragePath, residencyFilename); + + final Channel[] channels = mPowerStatsHALWrapper.getEnergyMeterInfo(); + final byte[] channelBytes = ChannelUtils.getProtoBytes(channels); + mDeleteMeterDataOnBoot = dataChanged(meterCacheFilename, channelBytes); + if (mDeleteMeterDataOnBoot) { + mPowerStatsMeterStorage.deleteLogs(); + updateCacheFile(meterCacheFilename, channelBytes); + } + + final EnergyConsumer[] energyConsumers = mPowerStatsHALWrapper.getEnergyConsumerInfo(); + final byte[] energyConsumerBytes = EnergyConsumerUtils.getProtoBytes(energyConsumers); + mDeleteModelDataOnBoot = dataChanged(modelCacheFilename, energyConsumerBytes); + if (mDeleteModelDataOnBoot) { + mPowerStatsModelStorage.deleteLogs(); + updateCacheFile(modelCacheFilename, energyConsumerBytes); + } + + final PowerEntity[] powerEntities = mPowerStatsHALWrapper.getPowerEntityInfo(); + final byte[] powerEntityBytes = PowerEntityUtils.getProtoBytes(powerEntities); + mDeleteResidencyDataOnBoot = dataChanged(residencyCacheFilename, powerEntityBytes); + if (mDeleteResidencyDataOnBoot) { + mPowerStatsResidencyStorage.deleteLogs(); + updateCacheFile(residencyCacheFilename, powerEntityBytes); + } } } diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java index b7285d58af4b..bb52c1dc1a29 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsService.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java @@ -61,8 +61,12 @@ public class PowerStatsService extends SystemService { private static final String MODEL_FILENAME = "log.powerstats.model." + DATA_STORAGE_VERSION; private static final String RESIDENCY_FILENAME = "log.powerstats.residency." + DATA_STORAGE_VERSION; + private static final String METER_CACHE_FILENAME = "meterCache"; + private static final String MODEL_CACHE_FILENAME = "modelCache"; + private static final String RESIDENCY_CACHE_FILENAME = "residencyCache"; private final Injector mInjector; + private File mDataStoragePath; private Context mContext; @Nullable @@ -98,6 +102,18 @@ public class PowerStatsService extends SystemService { return RESIDENCY_FILENAME; } + String createMeterCacheFilename() { + return METER_CACHE_FILENAME; + } + + String createModelCacheFilename() { + return MODEL_CACHE_FILENAME; + } + + String createResidencyCacheFilename() { + return RESIDENCY_CACHE_FILENAME; + } + IPowerStatsHALWrapper createPowerStatsHALWrapperImpl() { return PowerStatsHALWrapper.getPowerStatsHalImpl(); } @@ -112,10 +128,15 @@ public class PowerStatsService extends SystemService { } PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath, - String meterFilename, String modelFilename, String residencyFilename, + String meterFilename, String meterCacheFilename, + String modelFilename, String modelCacheFilename, + String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { - return new PowerStatsLogger(context, dataStoragePath, meterFilename, - modelFilename, residencyFilename, powerStatsHALWrapper); + return new PowerStatsLogger(context, dataStoragePath, + meterFilename, meterCacheFilename, + modelFilename, modelCacheFilename, + residencyFilename, residencyCacheFilename, + powerStatsHALWrapper); } BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) { @@ -187,14 +208,31 @@ public class PowerStatsService extends SystemService { mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, mPowerStatsInternal); } + @VisibleForTesting + public boolean getDeleteMeterDataOnBoot() { + return mPowerStatsLogger.getDeleteMeterDataOnBoot(); + } + + @VisibleForTesting + public boolean getDeleteModelDataOnBoot() { + return mPowerStatsLogger.getDeleteModelDataOnBoot(); + } + + @VisibleForTesting + public boolean getDeleteResidencyDataOnBoot() { + return mPowerStatsLogger.getDeleteResidencyDataOnBoot(); + } + private void onBootCompleted() { if (getPowerStatsHal().isInitialized()) { if (DEBUG) Slog.d(TAG, "Starting PowerStatsService loggers"); + mDataStoragePath = mInjector.createDataStoragePath(); // Only start logger and triggers if initialization is successful. - mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, - mInjector.createDataStoragePath(), mInjector.createMeterFilename(), - mInjector.createModelFilename(), mInjector.createResidencyFilename(), + mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, mDataStoragePath, + mInjector.createMeterFilename(), mInjector.createMeterCacheFilename(), + mInjector.createModelFilename(), mInjector.createModelCacheFilename(), + mInjector.createResidencyFilename(), mInjector.createResidencyCacheFilename(), getPowerStatsHal()); mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger); mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger); diff --git a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java index bd003d3ac2dd..11b22a574476 100644 --- a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java +++ b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java @@ -49,6 +49,12 @@ public class ProtoStreamUtils { private static final String TAG = ProtoStreamUtils.class.getSimpleName(); static class PowerEntityUtils { + public static byte[] getProtoBytes(PowerEntity[] powerEntity) { + ProtoOutputStream pos = new ProtoOutputStream(); + packProtoMessage(powerEntity, pos); + return pos.getBytes(); + } + public static void packProtoMessage(PowerEntity[] powerEntity, ProtoOutputStream pos) { if (powerEntity == null) return; @@ -260,6 +266,12 @@ public class ProtoStreamUtils { } static class ChannelUtils { + public static byte[] getProtoBytes(Channel[] channel) { + ProtoOutputStream pos = new ProtoOutputStream(); + packProtoMessage(channel, pos); + return pos.getBytes(); + } + public static void packProtoMessage(Channel[] channel, ProtoOutputStream pos) { if (channel == null) return; @@ -396,6 +408,12 @@ public class ProtoStreamUtils { } static class EnergyConsumerUtils { + public static byte[] getProtoBytes(EnergyConsumer[] energyConsumer) { + ProtoOutputStream pos = new ProtoOutputStream(); + packProtoMessage(energyConsumer, pos); + return pos.getBytes(); + } + public static void packProtoMessage(EnergyConsumer[] energyConsumer, ProtoOutputStream pos) { if (energyConsumer == null) return; @@ -410,6 +428,72 @@ public class ProtoStreamUtils { } } + public static EnergyConsumer[] unpackProtoMessage(byte[] data) throws IOException { + final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data)); + List<EnergyConsumer> energyConsumerList = new ArrayList<EnergyConsumer>(); + + while (true) { + try { + int nextField = pis.nextField(); + EnergyConsumer energyConsumer = new EnergyConsumer(); + + if (nextField == (int) PowerStatsServiceModelProto.ENERGY_CONSUMER) { + long token = pis.start(PowerStatsServiceModelProto.ENERGY_CONSUMER); + energyConsumerList.add(unpackEnergyConsumerProto(pis)); + pis.end(token); + } else if (nextField == ProtoInputStream.NO_MORE_FIELDS) { + return energyConsumerList.toArray( + new EnergyConsumer[energyConsumerList.size()]); + } else { + Slog.e(TAG, "Unhandled field in proto: " + + ProtoUtils.currentFieldToString(pis)); + } + } catch (WireTypeMismatchException wtme) { + Slog.e(TAG, "Wire Type mismatch in proto: " + + ProtoUtils.currentFieldToString(pis)); + } + } + } + + private static EnergyConsumer unpackEnergyConsumerProto(ProtoInputStream pis) + throws IOException { + final EnergyConsumer energyConsumer = new EnergyConsumer(); + + while (true) { + try { + switch (pis.nextField()) { + case (int) EnergyConsumerProto.ID: + energyConsumer.id = pis.readInt(EnergyConsumerProto.ID); + break; + + case (int) EnergyConsumerProto.ORDINAL: + energyConsumer.ordinal = pis.readInt(EnergyConsumerProto.ORDINAL); + break; + + case (int) EnergyConsumerProto.TYPE: + energyConsumer.type = (byte) pis.readInt(EnergyConsumerProto.TYPE); + break; + + case (int) EnergyConsumerProto.NAME: + energyConsumer.name = pis.readString(EnergyConsumerProto.NAME); + break; + + case ProtoInputStream.NO_MORE_FIELDS: + return energyConsumer; + + default: + Slog.e(TAG, "Unhandled field in EnergyConsumerProto: " + + ProtoUtils.currentFieldToString(pis)); + break; + + } + } catch (WireTypeMismatchException wtme) { + Slog.e(TAG, "Wire Type mismatch in EnergyConsumerProto: " + + ProtoUtils.currentFieldToString(pis)); + } + } + } + public static void print(EnergyConsumer[] energyConsumer) { if (energyConsumer == null) return; diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java index 6ec71b717ec6..74bb99351a6d 100644 --- a/services/core/java/com/android/server/security/FileIntegrityService.java +++ b/services/core/java/com/android/server/security/FileIntegrityService.java @@ -23,10 +23,8 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.Binder; -import android.os.Build; import android.os.Environment; import android.os.IBinder; -import android.os.SystemProperties; import android.os.UserHandle; import android.security.IFileIntegrityService; import android.util.Slog; @@ -60,7 +58,7 @@ public class FileIntegrityService extends SystemService { private final IBinder mService = new IFileIntegrityService.Stub() { @Override public boolean isApkVeritySupported() { - return FileIntegrityService.isApkVeritySupported(); + return VerityUtils.isFsVeritySupported(); } @Override @@ -69,7 +67,7 @@ public class FileIntegrityService extends SystemService { checkCallerPermission(packageName); try { - if (!isApkVeritySupported()) { + if (!VerityUtils.isFsVeritySupported()) { return false; } if (certificateBytes == null) { @@ -110,11 +108,6 @@ public class FileIntegrityService extends SystemService { } }; - public static boolean isApkVeritySupported() { - return Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R - || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2; - } - public FileIntegrityService(final Context context) { super(context); try { diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java index 09ee001a5544..48a60387fee7 100644 --- a/services/core/java/com/android/server/security/VerityUtils.java +++ b/services/core/java/com/android/server/security/VerityUtils.java @@ -17,7 +17,9 @@ package com.android.server.security; import android.annotation.NonNull; +import android.os.Build; import android.os.SharedMemory; +import android.os.SystemProperties; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; @@ -57,6 +59,11 @@ abstract public class VerityUtils { private static final boolean DEBUG = false; + public static boolean isFsVeritySupported() { + return Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.R + || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2; + } + /** Returns true if the given file looks like containing an fs-verity signature. */ public static boolean isFsveritySignatureFile(File file) { return file.getName().endsWith(FSVERITY_SIGNATURE_FILE_EXTENSION); diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java index 96248c3711e3..0974537e2584 100644 --- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java +++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java @@ -18,22 +18,65 @@ package com.android.server.speech; import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS; +import android.annotation.Nullable; +import android.app.AppOpsManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.os.Binder; +import android.os.Bundle; import android.os.RemoteException; import android.speech.IRecognitionListener; import android.speech.IRecognitionService; import android.speech.RecognitionService; +import android.speech.SpeechRecognizer; +import android.util.Log; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.infra.ServiceConnector; final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecognitionService> { private static final String TAG = RemoteSpeechRecognitionService.class.getSimpleName(); - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; - RemoteSpeechRecognitionService(Context context, ComponentName serviceName, int userId) { + private static final String APP_OP_MESSAGE = "Recording audio for speech recognition"; + private static final String RECORD_AUDIO_APP_OP = + AppOpsManager.permissionToOp(android.Manifest.permission.RECORD_AUDIO); + + private final Object mLock = new Object(); + + private boolean mConnected = false; + + @Nullable + private IRecognitionListener mListener; + + @Nullable + @GuardedBy("mLock") + private String mPackageName; + + @Nullable + @GuardedBy("mLock") + private String mFeatureId; + + @Nullable + @GuardedBy("mLock") + private DelegatingListener mDelegatingListener; + + // Makes sure we can block startListening() if session is still in progress. + @GuardedBy("mLock") + private boolean mSessionInProgress = false; + + // Makes sure we call startProxyOp / finishProxyOp at right times and only once per session. + @GuardedBy("mLock") + private boolean mRecordingInProgress = false; + + private final int mCallingUid; + private final AppOpsManager mAppOpsManager; + private final ComponentName mComponentName; + + RemoteSpeechRecognitionService( + Context context, ComponentName serviceName, int userId, int callingUid) { super(context, new Intent(RecognitionService.SERVICE_INTERFACE).setComponent(serviceName), Context.BIND_AUTO_CREATE @@ -43,46 +86,197 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn userId, IRecognitionService.Stub::asInterface); + mCallingUid = callingUid; + mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + mComponentName = serviceName; + if (DEBUG) { Slog.i(TAG, "Bound to recognition service at: " + serviceName.flattenToString()); } } + ComponentName getServiceComponentName() { + return mComponentName; + } + void startListening(Intent recognizerIntent, IRecognitionListener listener, String packageName, - String featureId) throws RemoteException { + String featureId) { if (DEBUG) { - Slog.i(TAG, "#startListening for package: " + packageName + ", feature=" + featureId); + Slog.i(TAG, String.format("#startListening for package: %s, feature=%s, callingUid=%d", + packageName, featureId, mCallingUid)); + } + + if (listener == null) { + Log.w(TAG, "#startListening called with no preceding #setListening - ignoring"); + return; + } + + if (!mConnected) { + tryRespondWithError(listener, SpeechRecognizer.ERROR_SERVER_DISCONNECTED); + return; + } + + synchronized (mLock) { + if (mSessionInProgress) { + Slog.i(TAG, "#startListening called while listening is in progress."); + tryRespondWithError(listener, SpeechRecognizer.ERROR_RECOGNIZER_BUSY); + return; + } + + if (startProxyOp(packageName, featureId) != AppOpsManager.MODE_ALLOWED) { + tryRespondWithError(listener, SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS); + return; + } + mSessionInProgress = true; + mRecordingInProgress = true; + + mListener = listener; + mDelegatingListener = new DelegatingListener(listener, () -> { + // To be invoked in terminal calls of the callback: results() or error() + if (DEBUG) { + Slog.i(TAG, "Recognition session complete"); + } + + synchronized (mLock) { + resetStateLocked(); + } + }); + mPackageName = packageName; + mFeatureId = featureId; + + run(service -> + service.startListening( + recognizerIntent, + mDelegatingListener, + packageName, + featureId, + mCallingUid)); } - run(service -> service.startListening(recognizerIntent, listener, packageName, featureId)); } - void stopListening(IRecognitionListener listener, String packageName, String featureId) - throws RemoteException { + void stopListening( + IRecognitionListener listener, String packageName, String featureId) { if (DEBUG) { Slog.i(TAG, "#stopListening for package: " + packageName + ", feature=" + featureId); } - run(service -> service.stopListening(listener, packageName, featureId)); + + if (!mConnected) { + tryRespondWithError(listener, SpeechRecognizer.ERROR_SERVER_DISCONNECTED); + return; + } + + synchronized (mLock) { + if (mListener == null) { + Log.w(TAG, "#stopListening called with no preceding #startListening - ignoring"); + tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT); + return; + } + + if (mListener.asBinder() != listener.asBinder()) { + Log.w(TAG, "#stopListening called with an unexpected listener"); + tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT); + return; + } + + if (!mRecordingInProgress) { + Slog.i(TAG, "#stopListening called while listening isn't in progress, ignoring."); + return; + } + mRecordingInProgress = false; + + finishProxyOp(packageName, featureId); + + run(service -> service.stopListening(mDelegatingListener, packageName, featureId)); + } } - void cancel(IRecognitionListener listener, String packageName, String featureId) - throws RemoteException { + void cancel( + IRecognitionListener listener, + String packageName, + String featureId, + boolean isShutdown) { if (DEBUG) { Slog.i(TAG, "#cancel for package: " + packageName + ", feature=" + featureId); } - run(service -> service.cancel(listener, packageName, featureId)); + + if (!mConnected) { + tryRespondWithError(listener, SpeechRecognizer.ERROR_SERVER_DISCONNECTED); + } + + synchronized (mLock) { + if (mListener == null) { + if (DEBUG) { + Log.w(TAG, "#cancel called with no preceding #startListening - ignoring"); + } + return; + } + + if (mListener.asBinder() != listener.asBinder()) { + Log.w(TAG, "#cancel called with an unexpected listener"); + tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT); + return; + } + + // Temporary reference to allow for resetting the hard link mDelegatingListener to null. + IRecognitionListener delegatingListener = mDelegatingListener; + + run(service -> service.cancel(delegatingListener, packageName, featureId, isShutdown)); + + if (mRecordingInProgress) { + finishProxyOp(packageName, featureId); + } + mRecordingInProgress = false; + mSessionInProgress = false; + + mDelegatingListener = null; + mListener = null; + + // Schedule to unbind after cancel is delivered. + if (isShutdown) { + run(service -> unbind()); + } + } + } + + void shutdown() { + synchronized (mLock) { + if (this.mListener == null) { + if (DEBUG) { + Slog.i(TAG, "Package died, but session wasn't initialized. " + + "Not invoking #cancel"); + } + return; + } + } + + cancel(mListener, mPackageName, mFeatureId, true /* isShutdown */); } @Override // from ServiceConnector.Impl protected void onServiceConnectionStatusChanged( IRecognitionService service, boolean connected) { - if (!DEBUG) { - return; + mConnected = connected; + + if (DEBUG) { + if (connected) { + Slog.i(TAG, "Connected to speech recognition service"); + } else { + Slog.w(TAG, "Disconnected from speech recognition service"); + } } - if (connected) { - Slog.i(TAG, "Connected to ASR service"); - } else { - Slog.w(TAG, "Disconnected from ASR service"); + synchronized (mLock) { + if (!connected) { + if (mListener == null) { + Slog.i(TAG, "Connection to speech recognition service lost, but no " + + "#startListening has been invoked yet."); + return; + } + + tryRespondWithError(mListener, SpeechRecognizer.ERROR_SERVER_DISCONNECTED); + + resetStateLocked(); + } } } @@ -90,4 +284,119 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn protected long getAutoDisconnectTimeoutMs() { return PERMANENT_BOUND_TIMEOUT_MS; } + + private void resetStateLocked() { + if (mRecordingInProgress && mPackageName != null && mFeatureId != null) { + finishProxyOp(mPackageName, mFeatureId); + } + + mListener = null; + mDelegatingListener = null; + mSessionInProgress = false; + mRecordingInProgress = false; + } + + private int startProxyOp(String packageName, String featureId) { + final long identity = Binder.clearCallingIdentity(); + try { + return mAppOpsManager.startProxyOp( + RECORD_AUDIO_APP_OP, + mCallingUid, + packageName, + featureId, + APP_OP_MESSAGE); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private void finishProxyOp(String packageName, String featureId) { + final long identity = Binder.clearCallingIdentity(); + try { + mAppOpsManager.finishProxyOp( + RECORD_AUDIO_APP_OP, mCallingUid, packageName, featureId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private static void tryRespondWithError(IRecognitionListener listener, int errorCode) { + if (DEBUG) { + Slog.i(TAG, "Responding with error " + errorCode); + } + + try { + if (listener != null) { + listener.onError(errorCode); + } + } catch (RemoteException e) { + Slog.w(TAG, + String.format("Failed to respond with an error %d to the client", errorCode), + e); + } + } + + private static class DelegatingListener extends IRecognitionListener.Stub { + + private final IRecognitionListener mRemoteListener; + private final Runnable mOnSessionComplete; + + DelegatingListener(IRecognitionListener listener, Runnable onSessionComplete) { + mRemoteListener = listener; + mOnSessionComplete = onSessionComplete; + } + + @Override + public void onReadyForSpeech(Bundle params) throws RemoteException { + mRemoteListener.onReadyForSpeech(params); + } + + @Override + public void onBeginningOfSpeech() throws RemoteException { + mRemoteListener.onBeginningOfSpeech(); + } + + @Override + public void onRmsChanged(float rmsdB) throws RemoteException { + mRemoteListener.onRmsChanged(rmsdB); + } + + @Override + public void onBufferReceived(byte[] buffer) throws RemoteException { + mRemoteListener.onBufferReceived(buffer); + } + + @Override + public void onEndOfSpeech() throws RemoteException { + mRemoteListener.onEndOfSpeech(); + } + + @Override + public void onError(int error) throws RemoteException { + if (DEBUG) { + Slog.i(TAG, String.format("Error %d during recognition session", error)); + } + mOnSessionComplete.run(); + mRemoteListener.onError(error); + } + + @Override + public void onResults(Bundle results) throws RemoteException { + if (DEBUG) { + Slog.i(TAG, "#onResults invoked for a recognition session"); + } + mOnSessionComplete.run(); + mRemoteListener.onResults(results); + } + + @Override + public void onPartialResults(Bundle results) throws RemoteException { + mRemoteListener.onPartialResults(results); + } + + @Override + public void onEvent(int eventType, Bundle params) throws RemoteException { + mRemoteListener.onEvent(eventType, params); + } + } } diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java index 592ba9e616b8..dbe73546d748 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java @@ -18,7 +18,9 @@ package com.android.server.speech; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.content.ComponentName; import android.content.Context; +import android.os.IBinder; import android.os.UserHandle; import android.speech.IRecognitionServiceManager; import android.speech.IRecognitionServiceManagerCallback; @@ -42,6 +44,7 @@ public final class SpeechRecognitionManagerService extends public SpeechRecognitionManagerService(@NonNull Context context) { super(context, + // TODO(b/176578753): think if we want to favor the particular service here. new FrameworkResourcesServiceNameResolver( context, R.string.config_defaultOnDeviceSpeechRecognitionService), @@ -63,11 +66,15 @@ public final class SpeechRecognitionManagerService extends final class SpeechRecognitionManagerServiceStub extends IRecognitionServiceManager.Stub { @Override - public void createSession(IRecognitionServiceManagerCallback callback) { + public void createSession( + ComponentName componentName, + IBinder clientToken, + boolean onDevice, + IRecognitionServiceManagerCallback callback) { int userId = UserHandle.getCallingUserId(); synchronized (mLock) { SpeechRecognitionManagerServiceImpl service = getServiceForUserLocked(userId); - service.createSessionLocked(callback); + service.createSessionLocked(componentName, clientToken, onDevice, callback); } } } diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index bcaf174b1d92..2656a3d32555 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java @@ -24,30 +24,44 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; import android.speech.IRecognitionListener; import android.speech.IRecognitionService; import android.speech.IRecognitionServiceManagerCallback; +import android.speech.SpeechRecognizer; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.infra.AbstractPerUserSystemService; +import com.google.android.collect.Sets; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + final class SpeechRecognitionManagerServiceImpl extends AbstractPerUserSystemService<SpeechRecognitionManagerServiceImpl, SpeechRecognitionManagerService> { - private static final String TAG = SpeechRecognitionManagerServiceImpl.class.getSimpleName(); + private static final int MAX_CONCURRENT_CONNECTIONS_BY_CLIENT = 10; + + private final Object mLock = new Object(); + + @NonNull @GuardedBy("mLock") - @Nullable - private RemoteSpeechRecognitionService mRemoteService; + private final Map<Integer, Set<RemoteSpeechRecognitionService>> mRemoteServicesByUid = + new HashMap<>(); SpeechRecognitionManagerServiceImpl( @NonNull SpeechRecognitionManagerService master, @NonNull Object lock, @UserIdInt int userId, boolean disabled) { super(master, lock, userId); - updateRemoteServiceLocked(); } @GuardedBy("mLock") @@ -67,92 +81,196 @@ final class SpeechRecognitionManagerServiceImpl extends @Override // from PerUserSystemService protected boolean updateLocked(boolean disabled) { final boolean enabledChanged = super.updateLocked(disabled); - updateRemoteServiceLocked(); return enabledChanged; } - /** - * Updates the reference to the remote service. - */ - @GuardedBy("mLock") - private void updateRemoteServiceLocked() { - if (mRemoteService != null) { - if (mMaster.debug) { - Slog.d(TAG, "updateRemoteService(): destroying old remote service"); - } - mRemoteService.unbind(); - mRemoteService = null; + void createSessionLocked( + ComponentName componentName, + IBinder clientToken, + boolean onDevice, + IRecognitionServiceManagerCallback callback) { + if (mMaster.debug) { + Slog.i(TAG, String.format("#createSessionLocked, component=%s, onDevice=%s", + componentName, onDevice)); + } + + ComponentName serviceComponent = componentName; + if (onDevice) { + serviceComponent = getOnDeviceComponentNameLocked(); } - } - void createSessionLocked(IRecognitionServiceManagerCallback callback) { - // TODO(b/176578753): check clients have record audio permission. - // TODO(b/176578753): verify caller package is the one supplied + if (serviceComponent == null) { + tryRespondWithError(callback, SpeechRecognizer.ERROR_CLIENT); + return; + } - RemoteSpeechRecognitionService service = ensureRemoteServiceLocked(); + final int creatorCallingUid = Binder.getCallingUid(); + Set<String> creatorPackageNames = + Sets.newArraySet( + getContext().getPackageManager().getPackagesForUid(creatorCallingUid)); + + RemoteSpeechRecognitionService service = createService(creatorCallingUid, serviceComponent); if (service == null) { - tryRespondWithError(callback); + tryRespondWithError(callback, SpeechRecognizer.ERROR_TOO_MANY_REQUESTS); return; } + IBinder.DeathRecipient deathRecipient = + () -> handleClientDeath(creatorCallingUid, service, true /* invoke #cancel */); + + try { + clientToken.linkToDeath(deathRecipient, 0); + } catch (RemoteException e) { + // RemoteException == binder already died, schedule disconnect anyway. + handleClientDeath(creatorCallingUid, service, true /* invoke #cancel */); + } + service.connect().thenAccept(binderService -> { if (binderService != null) { try { callback.onSuccess(new IRecognitionService.Stub() { @Override - public void startListening(Intent recognizerIntent, + public void startListening( + Intent recognizerIntent, IRecognitionListener listener, - String packageName, String featureId) throws RemoteException { + String packageName, + String featureId, + int callingUid) throws RemoteException { + verifyCallerIdentity( + creatorCallingUid, packageName, creatorPackageNames, listener); + if (callingUid != creatorCallingUid) { + listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS); + return; + } + service.startListening( recognizerIntent, listener, packageName, featureId); } @Override - public void stopListening(IRecognitionListener listener, + public void stopListening( + IRecognitionListener listener, String packageName, String featureId) throws RemoteException { + verifyCallerIdentity( + creatorCallingUid, packageName, creatorPackageNames, listener); + service.stopListening(listener, packageName, featureId); } @Override - public void cancel(IRecognitionListener listener, + public void cancel( + IRecognitionListener listener, String packageName, - String featureId) throws RemoteException { - service.cancel(listener, packageName, featureId); + String featureId, + boolean isShutdown) throws RemoteException { + verifyCallerIdentity( + creatorCallingUid, packageName, creatorPackageNames, listener); + + service.cancel(listener, packageName, featureId, isShutdown); + + if (isShutdown) { + handleClientDeath( + creatorCallingUid, + service, + false /* invoke #cancel */); + clientToken.unlinkToDeath(deathRecipient, 0); + } } }); } catch (RemoteException e) { Slog.e(TAG, "Error creating a speech recognition session", e); - tryRespondWithError(callback); + tryRespondWithError(callback, SpeechRecognizer.ERROR_CLIENT); } } else { - tryRespondWithError(callback); + tryRespondWithError(callback, SpeechRecognizer.ERROR_CLIENT); } }); } + private void verifyCallerIdentity( + int creatorCallingUid, + String packageName, + Set<String> creatorPackageNames, + IRecognitionListener listener) throws RemoteException { + if (creatorCallingUid != Binder.getCallingUid() + || !creatorPackageNames.contains(packageName)) { + listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS); + } + } + + private void handleClientDeath( + int callingUid, + RemoteSpeechRecognitionService service, boolean invokeCancel) { + if (invokeCancel) { + service.shutdown(); + } + removeService(callingUid, service); + } + @GuardedBy("mLock") @Nullable - private RemoteSpeechRecognitionService ensureRemoteServiceLocked() { - if (mRemoteService == null) { - final String serviceName = getComponentNameLocked(); - if (serviceName == null) { - if (mMaster.verbose) { - Slog.v(TAG, "ensureRemoteServiceLocked(): no service component name."); - } + private ComponentName getOnDeviceComponentNameLocked() { + final String serviceName = getComponentNameLocked(); + if (serviceName == null) { + if (mMaster.verbose) { + Slog.v(TAG, "ensureRemoteServiceLocked(): no service component name."); + } + return null; + } + return ComponentName.unflattenFromString(serviceName); + } + + private RemoteSpeechRecognitionService createService( + int callingUid, ComponentName serviceComponent) { + synchronized (mLock) { + Set<RemoteSpeechRecognitionService> servicesForClient = + mRemoteServicesByUid.get(callingUid); + + if (servicesForClient != null + && servicesForClient.size() >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) { return null; } - final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName); - mRemoteService = - new RemoteSpeechRecognitionService(getContext(), serviceComponent, mUserId); + + if (servicesForClient != null) { + Optional<RemoteSpeechRecognitionService> existingService = + servicesForClient + .stream() + .filter(service -> + service.getServiceComponentName().equals(serviceComponent)) + .findFirst(); + if (existingService.isPresent()) { + return existingService.get(); + } + } + + RemoteSpeechRecognitionService service = + new RemoteSpeechRecognitionService( + getContext(), serviceComponent, getUserId(), callingUid); + + Set<RemoteSpeechRecognitionService> valuesByCaller = + mRemoteServicesByUid.computeIfAbsent(callingUid, key -> new HashSet<>()); + valuesByCaller.add(service); + + return service; + } + } + + private void removeService(int callingUid, RemoteSpeechRecognitionService service) { + synchronized (mLock) { + Set<RemoteSpeechRecognitionService> valuesByCaller = + mRemoteServicesByUid.get(callingUid); + if (valuesByCaller != null) { + valuesByCaller.remove(service); + } } - return mRemoteService; } - private static void tryRespondWithError(IRecognitionServiceManagerCallback callback) { + private static void tryRespondWithError(IRecognitionServiceManagerCallback callback, + int errorCode) { try { - callback.onError(); + callback.onError(errorCode); } catch (RemoteException e) { Slog.w(TAG, "Failed to respond with error"); } diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index 3726407211d5..6ad30b544257 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -19,10 +19,12 @@ package com.android.server.vcn; import static com.android.server.VcnManagementService.VDBG; import android.annotation.NonNull; +import android.annotation.Nullable; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.vcn.VcnConfig; import android.net.vcn.VcnGatewayConnectionConfig; +import android.net.vcn.VcnManager.VcnErrorCode; import android.os.Handler; import android.os.Message; import android.os.ParcelUuid; @@ -30,7 +32,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; -import com.android.server.VcnManagementService.VcnSafemodeCallback; +import com.android.server.VcnManagementService.VcnCallback; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import java.util.Collections; @@ -86,18 +88,18 @@ public class Vcn extends Handler { private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE; /** - * Causes this VCN to immediately enter Safemode. + * Causes this VCN to immediately enter safe mode. * - * <p>Upon entering Safemode, the VCN will unregister its RequestListener, tear down all of its - * VcnGatewayConnections, and notify VcnManagementService that it is in Safemode. + * <p>Upon entering safe mode, the VCN will unregister its RequestListener, tear down all of its + * VcnGatewayConnections, and notify VcnManagementService that it is in safe mode. */ - private static final int MSG_CMD_ENTER_SAFEMODE = MSG_CMD_BASE + 1; + private static final int MSG_CMD_ENTER_SAFE_MODE = MSG_CMD_BASE + 1; @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; @NonNull private final Dependencies mDeps; @NonNull private final VcnNetworkRequestListener mRequestListener; - @NonNull private final VcnSafemodeCallback mVcnSafemodeCallback; + @NonNull private final VcnCallback mVcnCallback; @NonNull private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections = @@ -125,14 +127,8 @@ public class Vcn extends Handler { @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, @NonNull TelephonySubscriptionSnapshot snapshot, - @NonNull VcnSafemodeCallback vcnSafemodeCallback) { - this( - vcnContext, - subscriptionGroup, - config, - snapshot, - vcnSafemodeCallback, - new Dependencies()); + @NonNull VcnCallback vcnCallback) { + this(vcnContext, subscriptionGroup, config, snapshot, vcnCallback, new Dependencies()); } @VisibleForTesting(visibility = Visibility.PRIVATE) @@ -141,13 +137,12 @@ public class Vcn extends Handler { @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, @NonNull TelephonySubscriptionSnapshot snapshot, - @NonNull VcnSafemodeCallback vcnSafemodeCallback, + @NonNull VcnCallback vcnCallback, @NonNull Dependencies deps) { super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper()); mVcnContext = vcnContext; mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); - mVcnSafemodeCallback = - Objects.requireNonNull(vcnSafemodeCallback, "Missing vcnSafemodeCallback"); + mVcnCallback = Objects.requireNonNull(vcnCallback, "Missing vcnCallback"); mDeps = Objects.requireNonNull(deps, "Missing deps"); mRequestListener = new VcnNetworkRequestListener(); @@ -216,8 +211,8 @@ public class Vcn extends Handler { case MSG_CMD_TEARDOWN: handleTeardown(); break; - case MSG_CMD_ENTER_SAFEMODE: - handleEnterSafemode(); + case MSG_CMD_ENTER_SAFE_MODE: + handleEnterSafeMode(); break; default: Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what); @@ -243,10 +238,10 @@ public class Vcn extends Handler { mIsActive.set(false); } - private void handleEnterSafemode() { + private void handleEnterSafeMode() { handleTeardown(); - mVcnSafemodeCallback.onEnteredSafemode(); + mVcnCallback.onEnteredSafeMode(); } private void handleNetworkRequested( @@ -335,14 +330,31 @@ public class Vcn extends Handler { /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */ @VisibleForTesting(visibility = Visibility.PACKAGE) public interface VcnGatewayStatusCallback { - /** Called by a VcnGatewayConnection to indicate that it has entered Safemode. */ - void onEnteredSafemode(); + /** Called by a VcnGatewayConnection to indicate that it has entered safe mode. */ + void onEnteredSafeMode(); + + /** Callback by a VcnGatewayConnection to indicate that an error occurred. */ + void onGatewayConnectionError( + @NonNull int[] networkCapabilities, + @VcnErrorCode int errorCode, + @Nullable String exceptionClass, + @Nullable String exceptionMessage); } private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback { @Override - public void onEnteredSafemode() { - sendMessage(obtainMessage(MSG_CMD_ENTER_SAFEMODE)); + public void onEnteredSafeMode() { + sendMessage(obtainMessage(MSG_CMD_ENTER_SAFE_MODE)); + } + + @Override + public void onGatewayConnectionError( + @NonNull int[] networkCapabilities, + @VcnErrorCode int errorCode, + @Nullable String exceptionClass, + @Nullable String exceptionMessage) { + mVcnCallback.onGatewayConnectionError( + networkCapabilities, errorCode, exceptionClass, exceptionMessage); } } diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 12590eba81f8..9ee072ee7ce5 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -22,6 +22,9 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR; +import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR; +import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR; import static com.android.server.VcnManagementService.VDBG; @@ -52,7 +55,9 @@ import android.net.ipsec.ike.IkeSession; import android.net.ipsec.ike.IkeSessionCallback; import android.net.ipsec.ike.IkeSessionConfiguration; import android.net.ipsec.ike.IkeSessionParams; +import android.net.ipsec.ike.exceptions.AuthenticationFailedException; import android.net.ipsec.ike.exceptions.IkeException; +import android.net.ipsec.ike.exceptions.IkeInternalException; import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnTransportInfo; @@ -418,13 +423,13 @@ public class VcnGatewayConnection extends StateMachine { private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9; /** - * Sent when this VcnGatewayConnection has entered Safemode. + * Sent when this VcnGatewayConnection has entered safe mode. * - * <p>A VcnGatewayConnection enters Safemode when it takes over {@link + * <p>A VcnGatewayConnection enters safe mode when it takes over {@link * #SAFEMODE_TIMEOUT_SECONDS} to enter {@link ConnectedState}. * * <p>When a VcnGatewayConnection enters safe mode, it will fire {@link - * VcnGatewayStatusCallback#onEnteredSafemode()} to notify its Vcn. The Vcn will then shut down + * VcnGatewayStatusCallback#onEnteredSafeMode()} to notify its Vcn. The Vcn will then shut down * its VcnGatewayConnectin(s). * * <p>Relevant in DisconnectingState, ConnectingState, ConnectedState (if the Vcn Network is not @@ -432,7 +437,7 @@ public class VcnGatewayConnection extends StateMachine { * * @param arg1 The "all" token; this signal is always honored. */ - private static final int EVENT_SAFEMODE_TIMEOUT_EXCEEDED = 10; + private static final int EVENT_SAFE_MODE_TIMEOUT_EXCEEDED = 10; @VisibleForTesting(visibility = Visibility.PRIVATE) @NonNull @@ -551,7 +556,7 @@ public class VcnGatewayConnection extends StateMachine { @Nullable private WakeupMessage mTeardownTimeoutAlarm; @Nullable private WakeupMessage mDisconnectRequestAlarm; @Nullable private WakeupMessage mRetryTimeoutAlarm; - @Nullable private WakeupMessage mSafemodeTimeoutAlarm; + @Nullable private WakeupMessage mSafeModeTimeoutAlarm; public VcnGatewayConnection( @NonNull VcnContext vcnContext, @@ -638,7 +643,7 @@ public class VcnGatewayConnection extends StateMachine { cancelTeardownTimeoutAlarm(); cancelDisconnectRequestAlarm(); cancelRetryTimeoutAlarm(); - cancelSafemodeAlarm(); + cancelSafeModeAlarm(); mUnderlyingNetworkTracker.teardown(); } @@ -928,38 +933,91 @@ public class VcnGatewayConnection extends StateMachine { } @VisibleForTesting(visibility = Visibility.PRIVATE) - void setSafemodeAlarm() { + void setSafeModeAlarm() { // Only schedule a NEW alarm if none is already set. - if (mSafemodeTimeoutAlarm != null) { + if (mSafeModeTimeoutAlarm != null) { return; } - final Message delayedMessage = obtainMessage(EVENT_SAFEMODE_TIMEOUT_EXCEEDED, TOKEN_ALL); - mSafemodeTimeoutAlarm = + final Message delayedMessage = obtainMessage(EVENT_SAFE_MODE_TIMEOUT_EXCEEDED, TOKEN_ALL); + mSafeModeTimeoutAlarm = createScheduledAlarm( SAFEMODE_TIMEOUT_ALARM, delayedMessage, TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS)); } - private void cancelSafemodeAlarm() { - if (mSafemodeTimeoutAlarm != null) { - mSafemodeTimeoutAlarm.cancel(); - mSafemodeTimeoutAlarm = null; + private void cancelSafeModeAlarm() { + if (mSafeModeTimeoutAlarm != null) { + mSafeModeTimeoutAlarm.cancel(); + mSafeModeTimeoutAlarm = null; } - removeEqualMessages(EVENT_SAFEMODE_TIMEOUT_EXCEEDED); + removeEqualMessages(EVENT_SAFE_MODE_TIMEOUT_EXCEEDED); } - private void sessionLost(int token, @Nullable Exception exception) { + private void sessionLostWithoutCallback(int token, @Nullable Exception exception) { sendMessageAndAcquireWakeLock( EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception)); } + private void sessionLost(int token, @Nullable Exception exception) { + // Only notify mGatewayStatusCallback if the session was lost with an error. All + // authentication and DNS failures are sent through + // IkeSessionCallback.onClosedExceptionally(), which calls sessionClosed() + if (exception != null) { + mGatewayStatusCallback.onGatewayConnectionError( + mConnectionConfig.getRequiredUnderlyingCapabilities(), + VCN_ERROR_CODE_INTERNAL_ERROR, + "java.lang.RuntimeException", + "Received " + + exception.getClass().getSimpleName() + + " with message: " + + exception.getMessage()); + } + + sessionLostWithoutCallback(token, exception); + } + + private void notifyStatusCallbackForSessionClosed(@NonNull Exception exception) { + final int errorCode; + final String exceptionClass; + final String exceptionMessage; + + if (exception instanceof AuthenticationFailedException) { + errorCode = VCN_ERROR_CODE_CONFIG_ERROR; + exceptionClass = exception.getClass().getName(); + exceptionMessage = exception.getMessage(); + } else if (exception instanceof IkeInternalException + && exception.getCause() instanceof IOException) { + errorCode = VCN_ERROR_CODE_NETWORK_ERROR; + exceptionClass = "java.io.IOException"; + exceptionMessage = exception.getCause().getMessage(); + } else { + errorCode = VCN_ERROR_CODE_INTERNAL_ERROR; + exceptionClass = "java.lang.RuntimeException"; + exceptionMessage = + "Received " + + exception.getClass().getSimpleName() + + " with message: " + + exception.getMessage(); + } + + mGatewayStatusCallback.onGatewayConnectionError( + mConnectionConfig.getRequiredUnderlyingCapabilities(), + errorCode, + exceptionClass, + exceptionMessage); + } + private void sessionClosed(int token, @Nullable Exception exception) { + if (exception != null) { + notifyStatusCallbackForSessionClosed(exception); + } + // SESSION_LOST MUST be sent before SESSION_CLOSED to ensure that the SM moves to the // Disconnecting state. - sessionLost(token, exception); + sessionLostWithoutCallback(token, exception); sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, token); } @@ -1084,6 +1142,8 @@ public class VcnGatewayConnection extends StateMachine { } protected void handleDisconnectRequested(String msg) { + // TODO(b/180526152): notify VcnStatusCallback for Network loss + Slog.v(TAG, "Tearing down. Cause: " + msg); mIsRunning = false; @@ -1125,7 +1185,7 @@ public class VcnGatewayConnection extends StateMachine { Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState"); } - cancelSafemodeAlarm(); + cancelSafeModeAlarm(); } @Override @@ -1153,7 +1213,7 @@ public class VcnGatewayConnection extends StateMachine { @Override protected void exitState() { // Safe to blindly set up, as it is cancelled and cleared on entering this state - setSafemodeAlarm(); + setSafeModeAlarm(); } } @@ -1228,6 +1288,8 @@ public class VcnGatewayConnection extends StateMachine { String reason = ((EventDisconnectRequestedInfo) msg.obj).reason; if (reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) { + // TODO(b/180526152): notify VcnStatusCallback for Network loss + // Will trigger EVENT_SESSION_CLOSED immediately. mIkeSession.kill(); break; @@ -1245,9 +1307,9 @@ public class VcnGatewayConnection extends StateMachine { transitionTo(mDisconnectedState); } break; - case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: - mGatewayStatusCallback.onEnteredSafemode(); - mSafemodeTimeoutAlarm = null; + case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafeMode(); + mSafeModeTimeoutAlarm = null; break; default: logUnhandledMessage(msg); @@ -1331,9 +1393,9 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_DISCONNECT_REQUESTED: handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); break; - case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: - mGatewayStatusCallback.onEnteredSafemode(); - mSafemodeTimeoutAlarm = null; + case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafeMode(); + mSafeModeTimeoutAlarm = null; break; default: logUnhandledMessage(msg); @@ -1399,7 +1461,7 @@ public class VcnGatewayConnection extends StateMachine { // Validated connection, clear failed attempt counter mFailedAttempts = 0; - cancelSafemodeAlarm(); + cancelSafeModeAlarm(); } protected void applyTransform( @@ -1517,9 +1579,9 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_DISCONNECT_REQUESTED: handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); break; - case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: - mGatewayStatusCallback.onEnteredSafemode(); - mSafemodeTimeoutAlarm = null; + case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafeMode(); + mSafeModeTimeoutAlarm = null; break; default: logUnhandledMessage(msg); @@ -1573,9 +1635,8 @@ public class VcnGatewayConnection extends StateMachine { @Override protected void exitState() { - // Attempt to set the safe mode alarm - this requires the Vcn Network being validated - // while in ConnectedState (which cancels the previous alarm) - setSafemodeAlarm(); + // Will only set a new alarm if no safe mode alarm is currently scheduled. + setSafeModeAlarm(); } } @@ -1623,9 +1684,9 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_DISCONNECT_REQUESTED: handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); break; - case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: - mGatewayStatusCallback.onEnteredSafemode(); - mSafemodeTimeoutAlarm = null; + case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafeMode(); + mSafeModeTimeoutAlarm = null; break; default: logUnhandledMessage(msg); @@ -1710,8 +1771,6 @@ public class VcnGatewayConnection extends StateMachine { } } - // TODO: Make a VcnNetworkSpecifier, and match all underlying subscription IDs. - return builder.build(); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 2e98c2cbc5c2..589a8c31a07b 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -318,6 +318,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public static final String DUMP_CONTAINERS_CMD = "containers"; public static final String DUMP_RECENTS_CMD = "recents"; public static final String DUMP_RECENTS_SHORT_CMD = "r"; + public static final String DUMP_TOP_RESUMED_ACTIVITY = "top-resumed"; /** This activity is not being relaunched, or being relaunched for a non-resize reason. */ public static final int RELAUNCH_REASON_NONE = 0; @@ -3756,6 +3757,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + void dumpTopResumedActivityLocked(PrintWriter pw) { + pw.println("ACTIVITY MANAGER TOP-RESUMED (dumpsys activity top-resumed)"); + ActivityRecord topRecord = mRootWindowContainer.getTopResumedActivity(); + if (topRecord != null) { + topRecord.dump(pw, "", true); + } + } + void dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) { dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage, @@ -5896,6 +5905,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (getRecentTasks() != null) { getRecentTasks().dump(pw, dumpAll, dumpPackage); } + } else if (DUMP_TOP_RESUMED_ACTIVITY.equals(cmd)) { + dumpTopResumedActivityLocked(pw); } } } diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java new file mode 100644 index 000000000000..5d6d51377c3f --- /dev/null +++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java @@ -0,0 +1,143 @@ +/* + * 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.wm; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS; +import static com.android.server.wm.AnimationAdapterProto.REMOTE; +import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; + +import android.graphics.Rect; +import android.os.SystemClock; +import android.util.proto.ProtoOutputStream; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.server.policy.WindowManagerPolicy; + +import java.io.PrintWriter; +import java.util.ArrayList; + +class NonAppWindowAnimationAdapter implements AnimationAdapter { + + private final WindowState mWindow; + private RemoteAnimationTarget mTarget; + private SurfaceControl mCapturedLeash; + + private long mDurationHint; + private long mStatusBarTransitionDelay; + + @Override + public boolean getShowWallpaper() { + return false; + } + + NonAppWindowAnimationAdapter(WindowState w, + long durationHint, long statusBarTransitionDelay) { + mWindow = w; + mDurationHint = durationHint; + mStatusBarTransitionDelay = statusBarTransitionDelay; + } + + /** + * Creates and starts remote animations for all the visible non app windows. + * + * @return RemoteAnimationTarget[] targets for all the visible non app windows + */ + public static RemoteAnimationTarget[] startNonAppWindowAnimationsForKeyguardExit( + WindowManagerService service, long durationHint, long statusBarTransitionDelay) { + final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); + + final WindowManagerPolicy policy = service.mPolicy; + service.mRoot.forAllWindows(nonAppWindow -> { + if (nonAppWindow.mActivityRecord == null && policy.canBeHiddenByKeyguardLw(nonAppWindow) + && nonAppWindow.wouldBeVisibleIfPolicyIgnored() && !nonAppWindow.isVisible()) { + final NonAppWindowAnimationAdapter nonAppAdapter = new NonAppWindowAnimationAdapter( + nonAppWindow, durationHint, statusBarTransitionDelay); + nonAppWindow.startAnimation(nonAppWindow.getPendingTransaction(), + nonAppAdapter, false /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION); + targets.add(nonAppAdapter.createRemoteAnimationTarget()); + } + }, true /* traverseTopToBottom */); + return targets.toArray(new RemoteAnimationTarget[targets.size()]); + } + + /** + * Create a remote animation target for this animation adapter. + */ + RemoteAnimationTarget createRemoteAnimationTarget() { + mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false, + new Rect(), null, mWindow.getPrefixOrderIndex(), mWindow.getLastSurfacePosition(), + mWindow.getBounds(), null, mWindow.getWindowConfiguration(), true, null, null, + null); + return mTarget; + } + + @Override + public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, + int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { + mCapturedLeash = animationLeash; + } + + @Override + public long getDurationHint() { + return mDurationHint; + } + + @Override + public long getStatusBarTransitionsStartTime() { + return SystemClock.uptimeMillis() + mStatusBarTransitionDelay; + } + + /** + * @return the leash for this animation (only valid after the non app window surface animation + * has started). + */ + SurfaceControl getLeash() { + return mCapturedLeash; + } + + @Override + public void onAnimationCancelled(SurfaceControl animationLeash) { + ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "onAnimationCancelled"); + } + + @Override + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); + pw.print("token="); + pw.println(mWindow.mToken); + if (mTarget != null) { + pw.print(prefix); + pw.println("Target:"); + mTarget.dump(pw, prefix + " "); + } else { + pw.print(prefix); + pw.println("Target: null"); + } + } + + @Override + public void dumpDebug(ProtoOutputStream proto) { + final long token = proto.start(REMOTE); + if (mTarget != null) { + mTarget.dumpDebug(proto, TARGET); + } + proto.end(token); + } +} diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 392f27ead2bb..42cb96f65738 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER; + import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS; import static com.android.server.wm.AnimationAdapterProto.REMOTE; import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; @@ -126,7 +129,7 @@ class RemoteAnimationController implements DeathRecipient { final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations(); // TODO(bc-unlock): Create the remote non app animation targets (if any) - final RemoteAnimationTarget[] nonAppTargets = new RemoteAnimationTarget[0]; + final RemoteAnimationTarget[] nonAppTargets = createNonAppWindowAnimations(transit); mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> { try { @@ -215,6 +218,17 @@ class RemoteAnimationController implements DeathRecipient { }, mPendingWallpaperAnimations); } + private RemoteAnimationTarget[] createNonAppWindowAnimations( + @WindowManager.TransitionOldType int transit) { + ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createNonAppWindowAnimations()"); + return (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY + || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER) + ? NonAppWindowAnimationAdapter.startNonAppWindowAnimationsForKeyguardExit(mService, + mRemoteAnimationAdapter.getDuration(), + mRemoteAnimationAdapter.getStatusBarTransitionDelay()) + : new RemoteAnimationTarget[0]; + } + private void onAnimationFinished() { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "onAnimationFinished(): mPendingAnimations=%d", mPendingAnimations.size()); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 66d5c77a5c5e..d36091623207 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -5301,7 +5301,8 @@ class Task extends WindowContainer<WindowContainer> { */ void onWindowFocusChanged(boolean hasFocus) { updateShadowsRadius(hasFocus, getSyncTransaction()); - dispatchTaskInfoChangedIfNeeded(false /* force */); + // TODO(b/180525887): Un-comment once there is resolution on the bug. + // dispatchTaskInfoChangedIfNeeded(false /* force */); } void onPictureInPictureParamsChanged() { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java index e76597d0a2f3..48ae8d672fb1 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java @@ -138,10 +138,12 @@ class ActiveAdmin { private static final String TAG_ENROLLMENT_SPECIFIC_ID = "enrollment-specific-id"; private static final String TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS = "admin-can-grant-sensors-permissions"; + private static final String TAG_NETWORK_SLICING_ENABLED = "network-slicing-enabled"; private static final String TAG_USB_DATA_SIGNALING = "usb-data-signaling"; private static final String ATTR_VALUE = "value"; private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification"; private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications"; + private static final boolean NETWORK_SLICING_ENABLED_DEFAULT = true; DeviceAdminInfo info; @@ -281,6 +283,7 @@ class ActiveAdmin { public String mOrganizationId; public String mEnrollmentSpecificId; public boolean mAdminCanGrantSensorsPermissions; + public boolean mNetworkSlicingEnabled = NETWORK_SLICING_ENABLED_DEFAULT; private static final boolean USB_DATA_SIGNALING_ENABLED_DEFAULT = true; boolean mUsbDataSignalingEnabled = USB_DATA_SIGNALING_ENABLED_DEFAULT; @@ -552,6 +555,9 @@ class ActiveAdmin { } writeAttributeValueToXml(out, TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS, mAdminCanGrantSensorsPermissions); + if (mNetworkSlicingEnabled != NETWORK_SLICING_ENABLED_DEFAULT) { + writeAttributeValueToXml(out, TAG_NETWORK_SLICING_ENABLED, mNetworkSlicingEnabled); + } if (mUsbDataSignalingEnabled != USB_DATA_SIGNALING_ENABLED_DEFAULT) { writeAttributeValueToXml(out, TAG_USB_DATA_SIGNALING, mUsbDataSignalingEnabled); } @@ -784,6 +790,9 @@ class ActiveAdmin { mAlwaysOnVpnPackage = parser.getAttributeValue(null, ATTR_VALUE); } else if (TAG_ALWAYS_ON_VPN_LOCKDOWN.equals(tag)) { mAlwaysOnVpnLockdown = parser.getAttributeBoolean(null, ATTR_VALUE, false); + } else if (TAG_NETWORK_SLICING_ENABLED.equals(tag)) { + mNetworkSlicingEnabled = parser.getAttributeBoolean( + null, ATTR_VALUE, NETWORK_SLICING_ENABLED_DEFAULT); } else if (TAG_COMMON_CRITERIA_MODE.equals(tag)) { mCommonCriteriaMode = parser.getAttributeBoolean(null, ATTR_VALUE, false); } else if (TAG_PASSWORD_COMPLEXITY.equals(tag)) { @@ -1146,6 +1155,9 @@ class ActiveAdmin { pw.print("mAlwaysOnVpnLockdown="); pw.println(mAlwaysOnVpnLockdown); + pw.print("mNetworkSlicingEnabled="); + pw.println(mNetworkSlicingEnabled); + pw.print("mCommonCriteriaMode="); pw.println(mCommonCriteriaMode); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c6884870b2dd..dd599c9cfb60 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -247,6 +247,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; +import android.permission.AdminPermissionControlParams; import android.permission.IPermissionManager; import android.permission.PermissionControllerManager; import android.provider.CalendarContract; @@ -533,8 +534,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { /** * Strings logged with {@link - * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB} - * and {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB}. + * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}, + * {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB}, + * {@link DevicePolicyEnums#SET_NETWORK_LOGGING_ENABLED} and + * {@link DevicePolicyEnums#RETRIEVE_NETWORK_LOGS}. */ private static final String LOG_TAG_PROFILE_OWNER = "profile-owner"; private static final String LOG_TAG_DEVICE_OWNER = "device-owner"; @@ -6450,7 +6453,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private void forceWipeUser(int userId, String wipeReasonForUser, boolean wipeSilently) { boolean success = false; try { - if (getCurrentForegroundUser() == userId) { + if (getCurrentForegroundUserId() == userId) { mInjector.getIActivityManager().switchUser(UserHandle.USER_SYSTEM); } @@ -7891,6 +7894,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { updateDeviceOwnerLocked(); setDeviceOwnershipSystemPropertyLocked(); + //TODO(b/180371154): when provisionFullyManagedDevice is used in tests, remove this + // hard-coded default value setting. + if (isAdb(caller)) { + activeAdmin.mAdminCanGrantSensorsPermissions = true; + mPolicyCache.setAdminCanGrantSensorsPermissions(userId, true); + saveSettingsLocked(userId); + } + mInjector.binderWithCleanCallingIdentity(() -> { // Restrict adding a managed profile when a device owner is set on the device. // That is to prevent the co-existence of a managed profile and a device owner @@ -7910,7 +7921,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Slog.i(LOG_TAG, "Device owner set: " + admin + " on user " + userId); if (mInjector.userManagerIsHeadlessSystemUserMode()) { - int currentForegroundUser = getCurrentForegroundUser(); + int currentForegroundUser = getCurrentForegroundUserId(); Slog.i(LOG_TAG, "setDeviceOwner(): setting " + admin + " as profile owner on user " + currentForegroundUser); // Sets profile owner on current foreground user since @@ -9041,7 +9052,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return UserHandle.isSameApp(caller.getUid(), Process.SHELL_UID); } - private @UserIdInt int getCurrentForegroundUser() { + private @UserIdInt int getCurrentForegroundUserId() { try { return mInjector.getIActivityManager().getCurrentUser().id; } catch (RemoteException e) { @@ -9050,6 +9061,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return UserHandle.USER_NULL; } + @Override + public List<UserHandle> listForegroundAffiliatedUsers() { + checkIsDeviceOwner(getCallerIdentity()); + + int userId = mInjector.binderWithCleanCallingIdentity(() -> getCurrentForegroundUserId()); + + boolean isAffiliated; + synchronized (getLockObject()) { + isAffiliated = isUserAffiliatedWithDeviceLocked(userId); + } + + if (!isAffiliated) return Collections.emptyList(); + + List<UserHandle> users = new ArrayList<>(1); + users.add(UserHandle.of(userId)); + + return users; + } + protected int getProfileParentId(int userHandle) { return mInjector.binderWithCleanCallingIdentity(() -> { UserInfo parentUser = mUserManager.getProfileParent(userHandle); @@ -11133,6 +11163,50 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public void setNetworkSlicingEnabled(boolean enabled) { + if (!mHasFeature) { + return; + } + + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization(isProfileOwner(caller), + "Caller is not profile owner; only profile owner may control the network slicing"); + + synchronized (getLockObject()) { + final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked( + caller.getUserId()); + if (requiredAdmin != null && requiredAdmin.mNetworkSlicingEnabled != enabled) { + requiredAdmin.mNetworkSlicingEnabled = enabled; + saveSettingsLocked(caller.getUserId()); + // TODO(b/178655595) notify CS the change. + // TODO(b/178655595) DevicePolicyEventLogger metrics + } + } + } + + @Override + public boolean isNetworkSlicingEnabled(int userHandle) { + if (!mHasFeature) { + return false; + } + + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); + Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( + permission.READ_NETWORK_DEVICE_CONFIG) || isProfileOwner(caller), + "Caller is not profile owner and not granted" + + " READ_NETWORK_DEVICE_CONFIG permission"); + synchronized (getLockObject()) { + final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(userHandle); + if (requiredAdmin != null) { + return requiredAdmin.mNetworkSlicingEnabled; + } else { + return false; + } + } + } + + @Override public void setLockTaskPackages(ComponentName who, String[] packages) throws SecurityException { Objects.requireNonNull(who, "ComponentName is null"); @@ -12642,9 +12716,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) { + AdminPermissionControlParams permissionParams = + new AdminPermissionControlParams(packageName, permission, grantState, + canAdminGrantSensorsPermissionsForUser(caller.getUserId())); mInjector.getPermissionControllerManager(caller.getUserHandle()) .setRuntimePermissionGrantStateByDeviceAdmin(caller.getPackageName(), - packageName, permission, grantState, mContext.getMainExecutor(), + permissionParams, mContext.getMainExecutor(), (permissionWasSet) -> { if (isPostQAdmin && !permissionWasSet) { callback.sendResult(null); @@ -12866,7 +12943,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return CODE_NONSYSTEM_USER_EXISTS; } - int currentForegroundUser = getCurrentForegroundUser(); + int currentForegroundUser = getCurrentForegroundUserId(); if (callingUserId != currentForegroundUser && mInjector.userManagerIsHeadlessSystemUserMode() && currentForegroundUser == UserHandle.USER_SYSTEM) { @@ -12962,6 +13039,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return CODE_OK; } + private void checkIsDeviceOwner(CallerIdentity caller) { + Preconditions.checkCallAuthorization(isDeviceOwner(caller), caller.getUid() + + " is not device owner"); + } + private ComponentName getOwnerComponent(String packageName, int userId) { if (isDeviceOwnerPackage(packageName, userId)) { return mOwners.getDeviceOwnerComponent(); @@ -14158,9 +14240,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } final CallerIdentity caller = getCallerIdentity(admin, packageName); + final boolean isManagedProfileOwner = isProfileOwner(caller) + && isManagedProfile(caller.getUserId()); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isDeviceOwner(caller) - || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())))) + && (isDeviceOwner(caller) || isManagedProfileOwner)) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))); synchronized (getLockObject()) { @@ -14182,6 +14265,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .setAdmin(caller.getPackageName()) .setBoolean(/* isDelegate */ admin == null) .setInt(enabled ? 1 : 0) + .setStrings(isManagedProfileOwner + ? LOG_TAG_PROFILE_OWNER : LOG_TAG_DEVICE_OWNER) .write(); } } @@ -14338,9 +14423,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return null; } final CallerIdentity caller = getCallerIdentity(admin, packageName); + final boolean isManagedProfileOwner = isProfileOwner(caller) + && isManagedProfile(caller.getUserId()); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isDeviceOwner(caller) - || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())))) + && (isDeviceOwner(caller) || isManagedProfileOwner)) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))); if (mOwners.hasDeviceOwner()) { checkAllUsersAreAffiliatedWithDevice(); @@ -14354,6 +14440,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .createEvent(DevicePolicyEnums.RETRIEVE_NETWORK_LOGS) .setAdmin(caller.getPackageName()) .setBoolean(/* isDelegate */ admin == null) + .setStrings(isManagedProfileOwner + ? LOG_TAG_PROFILE_OWNER : LOG_TAG_DEVICE_OWNER) .write(); final long currentTime = System.currentTimeMillis(); @@ -15452,7 +15540,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private boolean isLockTaskFeatureEnabled(int lockTaskFeature) throws RemoteException { //TODO(b/175285301): Explicitly get the user's identity to check. int lockTaskFeatures = - getUserData(getCurrentForegroundUser()).mLockTaskFeatures; + getUserData(getCurrentForegroundUserId()).mLockTaskFeatures; return (lockTaskFeatures & lockTaskFeature) == lockTaskFeature; } @@ -16708,6 +16796,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean canUsbDataSignalingBeDisabled() { return mInjector.binderWithCleanCallingIdentity(() -> - mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3); + mInjector.getUsbManager() != null + && mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3 + ); } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index dd2dd8150165..2b09d12c97c7 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -326,8 +326,6 @@ public final class SystemServer implements Dumpable { "com.android.server.musicrecognition.MusicRecognitionManagerService"; private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS = "com.android.server.systemcaptions.SystemCaptionsManagerService"; - private static final String TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS = - "com.android.server.texttospeech.TextToSpeechManagerService"; private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS = "com.android.server.timezone.RulesManagerService$Lifecycle"; private static final String IOT_SERVICE_CLASS = @@ -1715,7 +1713,6 @@ public final class SystemServer implements Dumpable { startAttentionService(context, t); startRotationResolverService(context, t); startSystemCaptionsManagerService(context, t); - startTextToSpeechManagerService(context, t); // System Speech Recognition Service if (deviceHasConfigString(context, @@ -2921,13 +2918,6 @@ public final class SystemServer implements Dumpable { t.traceEnd(); } - private void startTextToSpeechManagerService(@NonNull Context context, - @NonNull TimingsTraceAndSlog t) { - t.traceBegin("StartTextToSpeechManagerService"); - mSystemServiceManager.startService(TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS); - t.traceEnd(); - } - private void startContentCaptureService(@NonNull Context context, @NonNull TimingsTraceAndSlog t) { // First check if it was explicitly enabled by DeviceConfig diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java index 966633701e6f..e7d01219f6c1 100644 --- a/services/people/java/com/android/server/people/PeopleService.java +++ b/services/people/java/com/android/server/people/PeopleService.java @@ -23,6 +23,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.people.ConversationChannel; import android.app.people.ConversationStatus; +import android.app.people.IConversationListener; import android.app.people.IPeopleManager; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionSessionId; @@ -33,10 +34,12 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; +import android.content.pm.ShortcutInfo; import android.os.Binder; import android.os.CancellationSignal; import android.os.IBinder; import android.os.Process; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; @@ -47,6 +50,7 @@ import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.people.data.DataManager; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -58,7 +62,9 @@ public class PeopleService extends SystemService { private static final String TAG = "PeopleService"; - private final DataManager mDataManager; + private DataManager mDataManager; + @VisibleForTesting + ConversationListenerHelper mConversationListenerHelper; private PackageManagerInternal mPackageManagerInternal; @@ -71,6 +77,8 @@ public class PeopleService extends SystemService { super(context); mDataManager = new DataManager(context); + mConversationListenerHelper = new ConversationListenerHelper(); + mDataManager.addConversationsListener(mConversationListenerHelper); } @Override @@ -148,12 +156,14 @@ public class PeopleService extends SystemService { * @param message used as message if SecurityException is thrown * @throws SecurityException if the caller is not system or root */ - private static void enforceSystemRootOrSystemUI(Context context, String message) { + @VisibleForTesting + protected void enforceSystemRootOrSystemUI(Context context, String message) { if (isSystemOrRoot()) return; context.enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE, message); } + @VisibleForTesting final IBinder mService = new IPeopleManager.Stub() { @Override @@ -241,8 +251,137 @@ public class PeopleService extends SystemService { return new ParceledListSlice<>( mDataManager.getStatuses(packageName, userId, conversationId)); } + + @Override + public void registerConversationListener( + String packageName, int userId, String shortcutId, IConversationListener listener) { + enforceSystemRootOrSystemUI(getContext(), "register conversation listener"); + mConversationListenerHelper.addConversationListener( + new ListenerKey(packageName, userId, shortcutId), listener); + } + + @Override + public void unregisterConversationListener(IConversationListener listener) { + enforceSystemRootOrSystemUI(getContext(), "unregister conversation listener"); + mConversationListenerHelper.removeConversationListener(listener); + } }; + /** + * Listeners for conversation changes. + * + * @hide + */ + public interface ConversationsListener { + /** + * Triggers with the list of modified conversations from {@link DataManager} for dispatching + * relevant updates to clients. + * + * @param conversations The conversations with modified data + * @see IPeopleManager#registerConversationListener(String, int, String, + * android.app.people.ConversationListener) + */ + default void onConversationsUpdate(@NonNull List<ConversationChannel> conversations) { + } + } + + /** + * Implements {@code ConversationListenerHelper} to dispatch conversation updates to registered + * clients. + */ + public static class ConversationListenerHelper implements ConversationsListener { + + ConversationListenerHelper() { + } + + @VisibleForTesting + final RemoteCallbackList<IConversationListener> mListeners = + new RemoteCallbackList<>(); + + /** Adds {@code listener} with {@code key} associated. */ + public synchronized void addConversationListener(ListenerKey key, + IConversationListener listener) { + mListeners.unregister(listener); + mListeners.register(listener, key); + } + + /** Removes {@code listener}. */ + public synchronized void removeConversationListener( + IConversationListener listener) { + mListeners.unregister(listener); + } + + @Override + /** Dispatches updates to {@code mListeners} with keys mapped to {@code conversations}. */ + public void onConversationsUpdate(List<ConversationChannel> conversations) { + int count = mListeners.beginBroadcast(); + // Early opt-out if no listeners are registered. + if (count == 0) { + return; + } + Map<ListenerKey, ConversationChannel> keyedConversations = new HashMap<>(); + for (ConversationChannel conversation : conversations) { + keyedConversations.put(getListenerKey(conversation), conversation); + } + for (int i = 0; i < count; i++) { + final ListenerKey listenerKey = (ListenerKey) mListeners.getBroadcastCookie(i); + if (!keyedConversations.containsKey(listenerKey)) { + continue; + } + final IConversationListener listener = mListeners.getBroadcastItem(i); + try { + ConversationChannel channel = keyedConversations.get(listenerKey); + listener.onConversationUpdate(channel); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing the dead object. + } + } + mListeners.finishBroadcast(); + } + + private ListenerKey getListenerKey(ConversationChannel conversation) { + ShortcutInfo info = conversation.getShortcutInfo(); + return new ListenerKey(info.getPackage(), info.getUserId(), + info.getId()); + } + } + + private static class ListenerKey { + private final String mPackageName; + private final Integer mUserId; + private final String mShortcutId; + + ListenerKey(String packageName, Integer userId, String shortcutId) { + this.mPackageName = packageName; + this.mUserId = userId; + this.mShortcutId = shortcutId; + } + + public String getPackageName() { + return mPackageName; + } + + public Integer getUserId() { + return mUserId; + } + + public String getShortcutId() { + return mShortcutId; + } + + @Override + public boolean equals(Object o) { + ListenerKey key = (ListenerKey) o; + return key.getPackageName().equals(mPackageName) && key.getUserId() == mUserId + && key.getShortcutId().equals(mShortcutId); + } + + @Override + public int hashCode() { + return mPackageName.hashCode() + mUserId.hashCode() + mShortcutId.hashCode(); + } + } + @VisibleForTesting final class LocalService extends PeopleServiceInternal { diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index 1048dfdea8e5..75614d60ab9e 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -48,6 +48,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.CancellationSignal; import android.os.Handler; +import android.os.Looper; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; @@ -75,14 +76,17 @@ import com.android.internal.telephony.SmsApplication; import com.android.server.LocalServices; import com.android.server.notification.NotificationManagerInternal; import com.android.server.notification.ShortcutHelper; +import com.android.server.people.PeopleService; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.PriorityQueue; import java.util.Set; import java.util.concurrent.Executor; @@ -118,6 +122,11 @@ public class DataManager { private final SparseArray<ScheduledFuture<?>> mUsageStatsQueryFutures = new SparseArray<>(); private final SparseArray<NotificationListener> mNotificationListeners = new SparseArray<>(); private final SparseArray<PackageMonitor> mPackageMonitors = new SparseArray<>(); + @GuardedBy("mLock") + private final List<PeopleService.ConversationsListener> mConversationsListeners = + new ArrayList<>(1); + private final Handler mHandler; + private ContentObserver mCallLogContentObserver; private ContentObserver mMmsSmsContentObserver; @@ -128,14 +137,14 @@ public class DataManager { private ConversationStatusExpirationBroadcastReceiver mStatusExpReceiver; public DataManager(Context context) { - this(context, new Injector()); + this(context, new Injector(), BackgroundThread.get().getLooper()); } - @VisibleForTesting - DataManager(Context context, Injector injector) { + DataManager(Context context, Injector injector, Looper looper) { mContext = context; mInjector = injector; mScheduledExecutor = mInjector.createScheduledExecutor(); + mHandler = new Handler(looper); } /** Initialization. Called when the system services are up running. */ @@ -234,20 +243,19 @@ public class DataManager { PackageData packageData = userData.getPackageData(packageName); // App may have been uninstalled. if (packageData != null) { - return getConversationChannel(packageData, shortcutId); + ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId); + return getConversationChannel(packageName, userId, shortcutId, conversationInfo); } } return null; } @Nullable - private ConversationChannel getConversationChannel(PackageData packageData, String shortcutId) { - ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId); + private ConversationChannel getConversationChannel(String packageName, int userId, + String shortcutId, ConversationInfo conversationInfo) { if (conversationInfo == null) { return null; } - int userId = packageData.getUserId(); - String packageName = packageData.getPackageName(); ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId); if (shortcutInfo == null) { return null; @@ -278,7 +286,8 @@ public class DataManager { return; } String shortcutId = conversationInfo.getShortcutId(); - ConversationChannel channel = getConversationChannel(packageData, shortcutId); + ConversationChannel channel = getConversationChannel(packageData.getPackageName(), + packageData.getUserId(), shortcutId, conversationInfo); if (channel == null || channel.getParentNotificationChannel() == null) { return; } @@ -384,7 +393,11 @@ public class DataManager { ConversationInfo convToModify = getConversationInfoOrThrow(cs, conversationId); ConversationInfo.Builder builder = new ConversationInfo.Builder(convToModify); builder.addOrUpdateStatus(status); - cs.addOrUpdate(builder.build()); + ConversationInfo modifiedConv = builder.build(); + cs.addOrUpdate(modifiedConv); + ConversationChannel conversation = getConversationChannel(packageName, userId, + conversationId, modifiedConv); + notifyConversationsListeners(Arrays.asList(conversation)); if (status.getEndTimeMillis() >= 0) { mStatusExpReceiver.scheduleExpiration( @@ -1235,6 +1248,32 @@ public class DataManager { } } + /** Adds {@code listener} to be notified on conversation changes. */ + public void addConversationsListener( + @NonNull PeopleService.ConversationsListener listener) { + synchronized (mConversationsListeners) { + mConversationsListeners.add(Objects.requireNonNull(listener)); + } + } + + // TODO(b/178792356): Trigger ConversationsListener on all-related data changes. + @VisibleForTesting + void notifyConversationsListeners( + @Nullable final List<ConversationChannel> changedConversations) { + mHandler.post(() -> { + try { + final List<PeopleService.ConversationsListener> copy; + synchronized (mLock) { + copy = new ArrayList<>(mConversationsListeners); + } + for (PeopleService.ConversationsListener listener : copy) { + listener.onConversationsUpdate(changedConversations); + } + } catch (Exception e) { + } + }); + } + /** A {@link BroadcastReceiver} that receives the intents for a specified user. */ private class PerUserBroadcastReceiver extends BroadcastReceiver { 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 1254df95a1c0..91098813380e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -18,6 +18,7 @@ package com.android.server.alarm; import static android.app.AlarmManager.ELAPSED_REALTIME; import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE; +import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; import static android.app.AlarmManager.FLAG_IDLE_UNTIL; import static android.app.AlarmManager.FLAG_STANDALONE; @@ -25,11 +26,14 @@ import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE; import static android.app.AlarmManager.RTC; import static android.app.AlarmManager.RTC_WAKEUP; import static android.app.AlarmManager.WINDOW_EXACT; +import static android.app.AlarmManager.WINDOW_HEURISTIC; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; @@ -44,7 +48,7 @@ import static com.android.server.alarm.AlarmManagerService.ACTIVE_INDEX; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED; -import static com.android.server.alarm.AlarmManagerService.Constants.ALLOW_WHILE_IDLE_WINDOW; +import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LAZY_BATCHING; @@ -61,6 +65,7 @@ import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE; import static com.android.server.alarm.Constants.TEST_CALLING_UID; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -75,20 +80,29 @@ import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; +import android.Manifest; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.AlarmManager; +import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IAlarmCompleteListener; import android.app.IAlarmListener; +import android.app.IAlarmManager; import android.app.PendingIntent; +import android.app.compat.CompatChanges; import android.app.usage.UsageStatsManagerInternal; import android.content.Context; import android.content.Intent; +import android.content.PermissionChecker; import android.os.BatteryManager; +import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PowerManager; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -102,9 +116,12 @@ import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.MockedVoidMethod; import com.android.internal.annotations.GuardedBy; +import com.android.internal.app.IAppOpsCallback; +import com.android.internal.app.IAppOpsService; import com.android.server.AlarmManagerInternal; import com.android.server.AppStateTracker; import com.android.server.AppStateTrackerImpl; +import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.usage.AppStandbyInternal; @@ -125,6 +142,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.LongConsumer; @Presubmit @RunWith(AndroidJUnit4.class) @@ -134,14 +152,21 @@ public class AlarmManagerServiceTest { private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID); private long mAppStandbyWindow; + private long mAllowWhileIdleWindow; private AlarmManagerService mService; private AppStandbyInternal.AppIdleStateChangeListener mAppStandbyListener; private AlarmManagerService.ChargingReceiver mChargingReceiver; + private IAppOpsCallback mIAppOpsCallback; + private IAlarmManager mBinder; @Mock private Context mMockContext; @Mock private IActivityManager mIActivityManager; @Mock + private IAppOpsService mIAppOpsService; + @Mock + private DeviceIdleInternal mDeviceIdleInternal; + @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal; @Mock private AppStandbyInternal mAppStandbyInternal; @@ -280,6 +305,11 @@ public class AlarmManagerServiceTest { // "IllegalStateException: Querying activity state off main thread is not allowed." // when AlarmManager calls DeviceConfig.addOnPropertiesChangedListener(). } + + @Override + IAppOpsService getAppOpsService() { + return mIAppOpsService; + } } @Before @@ -287,15 +317,20 @@ public class AlarmManagerServiceTest { mMockingSession = mockitoSession() .initMocks(this) .spyStatic(ActivityManager.class) + .mockStatic(CompatChanges.class) .spyStatic(DeviceConfig.class) .mockStatic(LocalServices.class) .spyStatic(Looper.class) + .mockStatic(PermissionChecker.class) .mockStatic(Settings.Global.class) .mockStatic(ServiceManager.class) .spyStatic(UserHandle.class) .strictness(Strictness.WARN) .startMocking(); + doReturn(mIActivityManager).when(ActivityManager::getService); + doReturn(mDeviceIdleInternal).when( + () -> LocalServices.getService(DeviceIdleInternal.class)); doReturn(mActivityManagerInternal).when( () -> LocalServices.getService(ActivityManagerInternal.class)); doReturn(mAppStateTracker).when(() -> LocalServices.getService(AppStateTracker.class)); @@ -330,6 +365,9 @@ public class AlarmManagerServiceTest { () -> DeviceConfig.getProperties( eq(DeviceConfig.NAMESPACE_ALARM_MANAGER), ArgumentMatchers.<String>any())); + when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn( + mock(AppOpsManager.class)); + mInjector = new Injector(mMockContext); mService = new AlarmManagerService(mMockContext, mInjector); spyOn(mService); @@ -346,6 +384,7 @@ public class AlarmManagerServiceTest { // Other boot phases don't matter mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW; + mAllowWhileIdleWindow = mService.mConstants.ALLOW_WHILE_IDLE_WINDOW; ArgumentCaptor<AppStandbyInternal.AppIdleStateChangeListener> captor = ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class); verify(mAppStandbyInternal).addListener(captor.capture()); @@ -358,6 +397,20 @@ public class AlarmManagerServiceTest { && filter.hasAction(BatteryManager.ACTION_DISCHARGING))); mChargingReceiver = chargingReceiverCaptor.getValue(); + ArgumentCaptor<IBinder> binderCaptor = ArgumentCaptor.forClass(IBinder.class); + verify(() -> ServiceManager.addService(eq(Context.ALARM_SERVICE), binderCaptor.capture(), + anyBoolean(), anyInt())); + mBinder = IAlarmManager.Stub.asInterface(binderCaptor.getValue()); + + ArgumentCaptor<IAppOpsCallback> appOpsCallbackCaptor = ArgumentCaptor.forClass( + IAppOpsCallback.class); + try { + verify(mIAppOpsService).startWatchingMode(eq(AppOpsManager.OP_SCHEDULE_EXACT_ALARM), + isNull(), appOpsCallbackCaptor.capture()); + } catch (RemoteException e) { + // Not expected on a mock. + } + mIAppOpsCallback = appOpsCallbackCaptor.getValue(); setTestableQuotas(); } @@ -389,13 +442,18 @@ public class AlarmManagerServiceTest { private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval, int flags, int callingUid) { + setTestAlarm(type, triggerTime, operation, interval, flags, callingUid, null); + } + + private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval, + int flags, int callingUid, Bundle idleOptions) { mService.setImpl(type, triggerTime, WINDOW_EXACT, interval, operation, null, "test", flags, - null, null, callingUid, TEST_CALLING_PACKAGE); + null, null, callingUid, TEST_CALLING_PACKAGE, idleOptions); } private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) { mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test", - FLAG_STANDALONE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE); + FLAG_STANDALONE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null); } @@ -506,14 +564,27 @@ public class AlarmManagerServiceTest { setDeviceConfigLong(KEY_MIN_INTERVAL, 10); setDeviceConfigLong(KEY_MAX_INTERVAL, 15); setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, 20); + setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, 25); setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, 30); setDeviceConfigLong(KEY_LISTENER_TIMEOUT, 35); assertEquals(5, mService.mConstants.MIN_FUTURITY); assertEquals(10, mService.mConstants.MIN_INTERVAL); assertEquals(15, mService.mConstants.MAX_INTERVAL); assertEquals(20, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA); + assertEquals(25, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA); assertEquals(30, mService.mConstants.ALLOW_WHILE_IDLE_WHITELIST_DURATION); assertEquals(35, mService.mConstants.LISTENER_TIMEOUT); + + // Test safeguards. + setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, -3); + assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA); + setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, 0); + assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA); + + setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, -8); + assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA); + setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, 0); + assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA); } @Test @@ -559,56 +630,45 @@ public class AlarmManagerServiceTest { assertEquals(mNowElapsedTest + 9, mTestTimer.getElapsed()); } - private void testQuotasDeferralOnSet(int standbyBucket) throws Exception { - final int quota = mService.getQuotaForBucketLocked(standbyBucket); - when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), - anyLong())).thenReturn(standbyBucket); + private void testQuotasDeferralOnSet(LongConsumer alarmSetter, int quota, long window) + throws Exception { final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < quota; i++) { - setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, - getNewMockPendingIntent()); + alarmSetter.accept(firstTrigger + i); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); } // This one should get deferred on set - setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, - getNewMockPendingIntent()); - final long expectedNextTrigger = firstTrigger + mAppStandbyWindow; + alarmSetter.accept(firstTrigger + quota); + final long expectedNextTrigger = firstTrigger + window; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } - private void testQuotasDeferralOnExpiration(int standbyBucket) throws Exception { - final int quota = mService.getQuotaForBucketLocked(standbyBucket); - when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), - anyLong())).thenReturn(standbyBucket); + private void testQuotasDeferralOnExpiration(LongConsumer alarmSetter, int quota, long window) + throws Exception { final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < quota; i++) { - setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, - getNewMockPendingIntent()); + alarmSetter.accept(firstTrigger + i); } - // This one should get deferred after the latest alarm expires - setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, - getNewMockPendingIntent()); + // This one should get deferred after the latest alarm expires. + alarmSetter.accept(firstTrigger + quota); for (int i = 0; i < quota; i++) { mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); } - final long expectedNextTrigger = firstTrigger + mAppStandbyWindow; + final long expectedNextTrigger = firstTrigger + window; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } - private void testQuotasNoDeferral(int standbyBucket) throws Exception { - final int quota = mService.getQuotaForBucketLocked(standbyBucket); - when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), - anyLong())).thenReturn(standbyBucket); + private void testQuotasNoDeferral(LongConsumer alarmSetter, int quota, long window) + throws Exception { final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < quota; i++) { - setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i, - getNewMockPendingIntent()); + alarmSetter.accept(firstTrigger + i); } // This delivery time maintains the quota invariant. Should not be deferred. - final long expectedNextTrigger = firstTrigger + mAppStandbyWindow + 5; - setTestAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger, getNewMockPendingIntent()); + final long expectedNextTrigger = firstTrigger + window + 5; + alarmSetter.accept(expectedNextTrigger); for (int i = 0; i < quota; i++) { mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); @@ -616,64 +676,88 @@ public class AlarmManagerServiceTest { assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } + private void testStandbyQuotasDeferralOnSet(int standbyBucket) throws Exception { + final int quota = mService.getQuotaForBucketLocked(standbyBucket); + when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), + anyLong())).thenReturn(standbyBucket); + testQuotasDeferralOnSet(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger, + getNewMockPendingIntent()), quota, mAppStandbyWindow); + } + + private void testStandbyQuotasDeferralOnExpiration(int standbyBucket) throws Exception { + final int quota = mService.getQuotaForBucketLocked(standbyBucket); + when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), + anyLong())).thenReturn(standbyBucket); + testQuotasDeferralOnExpiration(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger, + getNewMockPendingIntent()), quota, mAppStandbyWindow); + } + + private void testStandbyQuotasNoDeferral(int standbyBucket) throws Exception { + final int quota = mService.getQuotaForBucketLocked(standbyBucket); + when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), + anyLong())).thenReturn(standbyBucket); + testQuotasNoDeferral(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger, + getNewMockPendingIntent()), quota, mAppStandbyWindow); + } + @Test public void testActiveQuota_deferredOnSet() throws Exception { - testQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE); + testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE); } @Test public void testActiveQuota_deferredOnExpiration() throws Exception { - testQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE); + testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE); } @Test public void testActiveQuota_notDeferred() throws Exception { - testQuotasNoDeferral(STANDBY_BUCKET_ACTIVE); + testStandbyQuotasNoDeferral(STANDBY_BUCKET_ACTIVE); } @Test public void testWorkingQuota_deferredOnSet() throws Exception { - testQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET); + testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET); } @Test public void testWorkingQuota_deferredOnExpiration() throws Exception { - testQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET); + testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET); } @Test public void testWorkingQuota_notDeferred() throws Exception { - testQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET); + testStandbyQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET); } @Test public void testFrequentQuota_deferredOnSet() throws Exception { - testQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT); + testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT); } @Test public void testFrequentQuota_deferredOnExpiration() throws Exception { - testQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT); + testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT); } @Test public void testFrequentQuota_notDeferred() throws Exception { - testQuotasNoDeferral(STANDBY_BUCKET_FREQUENT); + testStandbyQuotasNoDeferral(STANDBY_BUCKET_FREQUENT); } @Test public void testRareQuota_deferredOnSet() throws Exception { - testQuotasDeferralOnSet(STANDBY_BUCKET_RARE); + testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_RARE); } @Test public void testRareQuota_deferredOnExpiration() throws Exception { - testQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE); + testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE); } @Test public void testRareQuota_notDeferred() throws Exception { - testQuotasNoDeferral(STANDBY_BUCKET_RARE); + testStandbyQuotasNoDeferral(STANDBY_BUCKET_RARE); } @Test @@ -731,7 +815,7 @@ public class AlarmManagerServiceTest { } @Test - public void testQuotaDowngrade() throws Exception { + public void testStandbyQuotaDowngrade() throws Exception { final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET); @@ -758,7 +842,7 @@ public class AlarmManagerServiceTest { } @Test - public void testQuotaUpgrade() throws Exception { + public void testStandbyQuotaUpgrade() throws Exception { final int frequentQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_FREQUENT); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(STANDBY_BUCKET_FREQUENT); @@ -1308,7 +1392,7 @@ public class AlarmManagerServiceTest { public void allowWhileIdleAlarmsWhileDeviceIdle() throws Exception { doReturn(0).when(mService).fuzzForDuration(anyLong()); - setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + ALLOW_WHILE_IDLE_WINDOW + 1000, + setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + mAllowWhileIdleWindow + 1000, getNewMockPendingIntent()); assertNotNull(mService.mPendingIdleUntil); @@ -1323,7 +1407,7 @@ public class AlarmManagerServiceTest { // This one should get deferred on set. setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, getNewMockPendingIntent(), false); - final long expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW; + final long expectedNextTrigger = firstTrigger + mAllowWhileIdleWindow; assertEquals("Incorrect trigger when no quota left", expectedNextTrigger, mTestTimer.getElapsed()); @@ -1449,61 +1533,24 @@ public class AlarmManagerServiceTest { when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(true); when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true); - final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA; - long firstTrigger = mNowElapsedTest + 10; - for (int i = 0; i < quota; i++) { - setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, - getNewMockPendingIntent(), false); - mNowElapsedTest = mTestTimer.getElapsed(); - mTestTimer.expire(); - } - // This one should get deferred on set. - setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, - getNewMockPendingIntent(), false); - long expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW; - assertEquals("Incorrect trigger when no quota available", expectedNextTrigger, - mTestTimer.getElapsed()); + + testQuotasDeferralOnSet(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, + getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow); // Refresh the state mService.removeLocked(TEST_CALLING_UID); mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER); - firstTrigger = mNowElapsedTest + 10; - for (int i = 0; i < quota; i++) { - setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, - getNewMockPendingIntent(), false); - } - // This one should get deferred after the latest alarm expires. - setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, - getNewMockPendingIntent(), false); - for (int i = 0; i < quota; i++) { - mNowElapsedTest = mTestTimer.getElapsed(); - mTestTimer.expire(); - } - expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW; - assertEquals("Incorrect trigger when no quota available", expectedNextTrigger, - mTestTimer.getElapsed()); + testQuotasDeferralOnExpiration(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, + trigger, getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow); // Refresh the state mService.removeLocked(TEST_CALLING_UID); mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER); - firstTrigger = mNowElapsedTest + 10; - for (int i = 0; i < quota; i++) { - setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, - getNewMockPendingIntent(), false); - } - // This delivery time maintains the quota invariant. Should not be deferred. - expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW + 5; - setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger, - getNewMockPendingIntent(), false); - for (int i = 0; i < quota; i++) { - mNowElapsedTest = mTestTimer.getElapsed(); - mTestTimer.expire(); - } - assertEquals("Incorrect trigger when no quota available", expectedNextTrigger, - mTestTimer.getElapsed()); + testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, + getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow); } @Test @@ -1545,6 +1592,259 @@ public class AlarmManagerServiceTest { } @Test + public void canScheduleExactAlarms() throws RemoteException { + doReturn(PermissionChecker.PERMISSION_GRANTED).when( + () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, + Manifest.permission.SCHEDULE_EXACT_ALARM)); + assertTrue(mBinder.canScheduleExactAlarms()); + + doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( + () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, + Manifest.permission.SCHEDULE_EXACT_ALARM)); + assertFalse(mBinder.canScheduleExactAlarms()); + + doReturn(PermissionChecker.PERMISSION_SOFT_DENIED).when( + () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, + Manifest.permission.SCHEDULE_EXACT_ALARM)); + assertFalse(mBinder.canScheduleExactAlarms()); + } + + @Test + public void noPermissionCheckWhenChangeDisabled() throws RemoteException { + doReturn(false).when( + () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), + anyString(), any(UserHandle.class))); + + // alarm clock + mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0, + getNewMockPendingIntent(), null, null, null, + mock(AlarmManager.AlarmClockInfo.class)); + + // exact, allow-while-idle + mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, + FLAG_ALLOW_WHILE_IDLE, getNewMockPendingIntent(), null, null, null, null); + + // inexact, allow-while-idle + mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_HEURISTIC, 0, + FLAG_ALLOW_WHILE_IDLE, getNewMockPendingIntent(), null, null, null, null); + + verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, + Manifest.permission.SCHEDULE_EXACT_ALARM), never()); + verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); + } + + @Test + public void exactAllowWhileIdleBinderCallWhenChangeDisabled() throws Exception { + doReturn(false).when( + () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), + anyString(), any(UserHandle.class))); + + final PendingIntent alarmPi = getNewMockPendingIntent(); + mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, + FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); + + final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), + eq(alarmPi), isNull(), isNull(), + eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(), + eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + + final Bundle idleOptions = bundleCaptor.getValue(); + final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); + } + + @Test + public void inexactAllowWhileIdleBinderCallWhenChangeDisabled() throws Exception { + doReturn(false).when( + () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), + anyString(), any(UserHandle.class))); + + final PendingIntent alarmPi = getNewMockPendingIntent(); + mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_HEURISTIC, 0, + FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); + + final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), anyLong(), eq(0L), + eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), + isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + + final Bundle idleOptions = bundleCaptor.getValue(); + final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); + } + + @Test + public void alarmClockBinderCallWhenChangeDisabled() throws Exception { + doReturn(false).when( + () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), + anyString(), any(UserHandle.class))); + + final PendingIntent alarmPi = getNewMockPendingIntent(); + final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class); + mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0, + alarmPi, null, null, null, alarmClock); + + verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), + eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE), + isNull(), eq(alarmClock), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), isNull()); + } + + @Test + public void alarmClockBinderCall() throws RemoteException { + doReturn(true).when( + () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), + anyString(), any(UserHandle.class))); + + doReturn(PermissionChecker.PERMISSION_GRANTED).when( + () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, + Manifest.permission.SCHEDULE_EXACT_ALARM)); + + final PendingIntent alarmPi = getNewMockPendingIntent(); + final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class); + mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0, + alarmPi, null, null, null, alarmClock); + + // Correct permission checks are invoked. + verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, + Manifest.permission.SCHEDULE_EXACT_ALARM)); + verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); + + final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), + eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE), + isNull(), eq(alarmClock), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), + bundleCaptor.capture()); + + final Bundle idleOptions = bundleCaptor.getValue(); + final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); + } + + @Test + public void exactAllowWhileIdleBinderCallWithPermission() throws RemoteException { + doReturn(true).when( + () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), + anyString(), any(UserHandle.class))); + + // Permission check is granted by default by the mock. + final PendingIntent alarmPi = getNewMockPendingIntent(); + mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, + FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); + + verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, + Manifest.permission.SCHEDULE_EXACT_ALARM)); + verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); + + final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), + eq(alarmPi), isNull(), isNull(), + eq(FLAG_ALLOW_WHILE_IDLE | FLAG_STANDALONE), isNull(), isNull(), + eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + + final Bundle idleOptions = bundleCaptor.getValue(); + final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); + } + + @Test + public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException { + doReturn(true).when( + () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), + anyString(), any(UserHandle.class))); + // If permission is denied, only then allowlist will be checked. + doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( + () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, + Manifest.permission.SCHEDULE_EXACT_ALARM)); + when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); + + final PendingIntent alarmPi = getNewMockPendingIntent(); + mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, + FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); + + verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, + Manifest.permission.SCHEDULE_EXACT_ALARM)); + verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid())); + + final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), + eq(alarmPi), isNull(), isNull(), + eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(), + eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + + final Bundle idleOptions = bundleCaptor.getValue(); + final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); + } + + @Test + public void inexactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException { + doReturn(true).when( + () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), + anyString(), any(UserHandle.class))); + + when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); + final PendingIntent alarmPi = getNewMockPendingIntent(); + mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 4321, WINDOW_HEURISTIC, 0, + FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); + + verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, + Manifest.permission.SCHEDULE_EXACT_ALARM), never()); + verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid())); + + final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(4321L), anyLong(), eq(0L), + eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), + isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + + final Bundle idleOptions = bundleCaptor.getValue(); + final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); + } + + @Test + public void inexactAllowWhileIdleBinderCallWithoutAllowlist() throws RemoteException { + doReturn(true).when( + () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION), + anyString(), any(UserHandle.class))); + + when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false); + final PendingIntent alarmPi = getNewMockPendingIntent(); + mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 4321, WINDOW_HEURISTIC, 0, + FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); + + verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext, + Manifest.permission.SCHEDULE_EXACT_ALARM), never()); + verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid())); + + final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(4321L), anyLong(), eq(0L), + eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), + isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture()); + + final Bundle idleOptions = bundleCaptor.getValue(); + final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType"); + assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, type); + } + + @Test + public void idleOptionsSentOnExpiration() throws Exception { + final long triggerTime = mNowElapsedTest + 5000; + final PendingIntent alarmPi = getNewMockPendingIntent(); + final Bundle idleOptions = new Bundle(); + idleOptions.putChar("TEST_CHAR_KEY", 'x'); + idleOptions.putInt("TEST_INT_KEY", 53); + setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi, 0, 0, TEST_CALLING_UID, + idleOptions); + + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + + verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), + any(), any(Handler.class), isNull(), eq(idleOptions)); + } + + @Test public void alarmStoreMigration() { setDeviceConfigBoolean(KEY_LAZY_BATCHING, false); final int numAlarms = 10; diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java index 42fa3d480046..12894ae4f75e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java @@ -69,12 +69,12 @@ public class AlarmStoreTest { mock(PendingIntent.class)); return new Alarm(ELAPSED_REALTIME_WAKEUP, whenElapsed, whenElapsed, 0, 0, mock(PendingIntent.class), null, null, null, 0, info, TEST_CALLING_UID, - TEST_CALLING_PACKAGE); + TEST_CALLING_PACKAGE, null); } private static Alarm createAlarm(int type, long whenElapsed, long windowLength, int flags) { return new Alarm(type, whenElapsed, whenElapsed, windowLength, 0, mock(PendingIntent.class), - null, null, null, flags, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE); + null, null, null, flags, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null); } private void addAlarmsToStore(Alarm... alarms) { diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java index e80f0655e641..b64528c2f4d4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java @@ -17,10 +17,17 @@ package com.android.server.alarm; import static android.app.AlarmManager.ELAPSED_REALTIME; +import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE; +import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT; +import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; +import static android.app.AlarmManager.FLAG_STANDALONE; +import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE; +import static android.app.AlarmManager.RTC_WAKEUP; import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX; import static com.android.server.alarm.Alarm.NUM_POLICIES; import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX; +import static com.android.server.alarm.AlarmManagerService.isExemptFromAppStandby; import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE; import static com.android.server.alarm.Constants.TEST_CALLING_UID; @@ -28,7 +35,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import android.app.AlarmManager; import android.app.PendingIntent; import android.platform.test.annotations.Presubmit; @@ -43,15 +52,29 @@ import java.util.Random; @RunWith(AndroidJUnit4.class) public class AlarmTest { - private Alarm createDefaultAlarm(long requestedElapsed, long windowLength) { + private Alarm createDefaultAlarm(long requestedElapsed, long windowLength, int flags) { return new Alarm(ELAPSED_REALTIME, 0, requestedElapsed, windowLength, 0, - mock(PendingIntent.class), null, null, null, 0, null, TEST_CALLING_UID, - TEST_CALLING_PACKAGE); + createAlarmSender(), null, null, null, flags, null, TEST_CALLING_UID, + TEST_CALLING_PACKAGE, null); + } + + private Alarm createAlarmClock(long requestedRtc) { + final AlarmManager.AlarmClockInfo info = mock(AlarmManager.AlarmClockInfo.class); + return new Alarm(RTC_WAKEUP, requestedRtc, requestedRtc, 0, 0, createAlarmSender(), + null, null, null, FLAG_WAKE_FROM_IDLE | FLAG_STANDALONE, info, TEST_CALLING_UID, + TEST_CALLING_PACKAGE, null); + } + + private PendingIntent createAlarmSender() { + final PendingIntent alarmPi = mock(PendingIntent.class); + when(alarmPi.getCreatorPackage()).thenReturn(TEST_CALLING_PACKAGE); + when(alarmPi.getCreatorUid()).thenReturn(TEST_CALLING_UID); + return alarmPi; } @Test public void initSetsOnlyRequesterPolicy() { - final Alarm a = createDefaultAlarm(4567, 2); + final Alarm a = createDefaultAlarm(4567, 2, 0); for (int i = 0; i < NUM_POLICIES; i++) { if (i == REQUESTER_POLICY_INDEX) { @@ -86,7 +109,7 @@ public class AlarmTest { @Test public void whenElapsed() { - final Alarm a = createDefaultAlarm(0, 0); + final Alarm a = createDefaultAlarm(0, 0, 0); final long[][] uniqueData = generatePolicyTestMatrix(NUM_POLICIES); for (int i = 0; i < NUM_POLICIES; i++) { @@ -104,7 +127,7 @@ public class AlarmTest { @Test public void maxWhenElapsed() { - final Alarm a = createDefaultAlarm(10, 12); + final Alarm a = createDefaultAlarm(10, 12, 0); assertEquals(22, a.getMaxWhenElapsed()); a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 15); @@ -128,7 +151,7 @@ public class AlarmTest { @Test public void setPolicyElapsedExact() { - final Alarm exactAlarm = createDefaultAlarm(10, 0); + final Alarm exactAlarm = createDefaultAlarm(10, 0, 0); assertTrue(exactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4)); assertTrue(exactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10)); @@ -143,7 +166,7 @@ public class AlarmTest { @Test public void setPolicyElapsedInexact() { - final Alarm inexactAlarm = createDefaultAlarm(10, 5); + final Alarm inexactAlarm = createDefaultAlarm(10, 5, 0); assertTrue(inexactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4)); assertTrue(inexactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10)); @@ -154,4 +177,20 @@ public class AlarmTest { assertFalse(inexactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 8)); } + + @Test + public void isExemptFromStandby() { + final long anything = 35412; // Arbitrary number, doesn't matter for this test. + + assertFalse("Basic alarm exempt", isExemptFromAppStandby( + createDefaultAlarm(anything, anything, 0))); + assertFalse("FLAG_ALLOW_WHILE_IDLE_COMPAT exempt", isExemptFromAppStandby( + createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE_COMPAT))); + + assertTrue("ALLOW_WHILE_IDLE not exempt", isExemptFromAppStandby( + createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE))); + assertTrue("ALLOW_WHILE_IDLE_UNRESTRICTED not exempt", isExemptFromAppStandby( + createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED))); + assertTrue("Alarm clock not exempt", isExemptFromAppStandby(createAlarmClock(anything))); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java index 5bb6a42b2604..0e795a938d48 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java @@ -45,7 +45,7 @@ public class BackgroundRestrictedAlarmsTest { } uidAlarms.add(new Alarm( removeIt ? RTC : RTC_WAKEUP, - 0, 0, 0, 0, null, null, null, null, 0, null, uid, name)); + 0, 0, 0, 0, null, null, null, null, 0, null, uid, name, null)); return all; } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/OWNERS b/services/tests/mockingservicestests/src/com/android/server/pm/OWNERS index d825dfd7cf00..46b797b83f1d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/OWNERS +++ b/services/tests/mockingservicestests/src/com/android/server/pm/OWNERS @@ -1 +1,3 @@ include /services/core/java/com/android/server/pm/OWNERS + +per-file StagingManagerTest.java = dariofreni@google.com, ioffe@google.com, olilan@google.com diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java index a946534f4ecd..8d54ead75761 100644 --- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java @@ -16,6 +16,12 @@ package com.android.server.am; +import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL; +import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU; +import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_DISPLAY; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import android.content.Context; @@ -27,6 +33,7 @@ import android.hardware.power.stats.EnergyMeasurement; import android.hardware.power.stats.PowerEntity; import android.hardware.power.stats.StateResidencyResult; import android.power.PowerStatsInternal; +import android.util.IntArray; import android.util.SparseArray; import androidx.test.InstrumentationRegistry; @@ -34,7 +41,9 @@ import androidx.test.InstrumentationRegistry; import com.android.internal.os.BatteryStatsImpl; import org.junit.Before; +import org.junit.Test; +import java.util.Arrays; import java.util.concurrent.CompletableFuture; /** @@ -58,6 +67,69 @@ public class BatteryExternalStatsWorkerTest { mBatteryStatsImpl); } + @Test + public void testTargetedEnergyConsumerQuerying() { + final int numCpuClusters = 4; + final int numOther = 3; + + final IntArray tempAllIds = new IntArray(); + // Add some energy consumers used by BatteryExternalStatsWorker. + final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0, + "display"); + tempAllIds.add(displayId); + mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345); + + final int[] cpuClusterIds = new int[numCpuClusters]; + for (int i = 0; i < numCpuClusters; i++) { + cpuClusterIds[i] = mPowerStatsInternal.addEnergyConsumer( + EnergyConsumerType.CPU_CLUSTER, i, "cpu_cluster" + i); + tempAllIds.add(cpuClusterIds[i]); + mPowerStatsInternal.incrementEnergyConsumption(cpuClusterIds[i], 1111 + i); + } + Arrays.sort(cpuClusterIds); + + final int[] otherIds = new int[numOther]; + for (int i = 0; i < numOther; i++) { + otherIds[i] = mPowerStatsInternal.addEnergyConsumer( + EnergyConsumerType.OTHER, i, "other" + i); + tempAllIds.add(otherIds[i]); + mPowerStatsInternal.incrementEnergyConsumption(otherIds[i], 3000 + i); + } + Arrays.sort(otherIds); + + final int[] allIds = tempAllIds.toArray(); + Arrays.sort(allIds); + + // Inform BESW that PowerStatsInternal is ready to query + mBatteryExternalStatsWorker.systemServicesReady(); + + final EnergyConsumerResult[] displayResults = + mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_DISPLAY).getNow(null); + // Results should only have the display energy consumer + assertEquals(1, displayResults.length); + assertEquals(displayId, displayResults[0].id); + + final EnergyConsumerResult[] cpuResults = + mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_CPU).getNow(null); + // Results should only have the cpu cluster energy consumers + final int[] receivedCpuIds = new int[cpuResults.length]; + for (int i = 0; i < cpuResults.length; i++) { + receivedCpuIds[i] = cpuResults[i].id; + } + Arrays.sort(receivedCpuIds); + assertArrayEquals(cpuClusterIds, receivedCpuIds); + + final EnergyConsumerResult[] allResults = + mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_ALL).getNow(null); + // All energy consumer results should be available + final int[] receivedAllIds = new int[allResults.length]; + for (int i = 0; i < allResults.length; i++) { + receivedAllIds[i] = allResults[i].id; + } + Arrays.sort(receivedAllIds); + assertArrayEquals(allIds, receivedAllIds); + } + public class TestInjector extends BatteryExternalStatsWorker.Injector { public TestInjector(Context context) { super(context); @@ -79,9 +151,8 @@ public class BatteryExternalStatsWorkerTest { } public class TestPowerStatsInternal extends PowerStatsInternal { - private final SparseArray<EnergyConsumer> mEnergyConsumers = new SparseArray<>(); - private final SparseArray<EnergyConsumerResult> mEnergyConsumerResults = - new SparseArray<>(); + private final SparseArray<EnergyConsumer> mEnergyConsumers = new SparseArray(); + private final SparseArray<EnergyConsumerResult> mEnergyConsumerResults = new SparseArray(); private final int mTimeSinceBoot = 0; @Override @@ -98,10 +169,19 @@ public class BatteryExternalStatsWorkerTest { public CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync( int[] energyConsumerIds) { final CompletableFuture<EnergyConsumerResult[]> future = new CompletableFuture(); - final int size = mEnergyConsumerResults.size(); - final EnergyConsumerResult[] results = new EnergyConsumerResult[size]; - for (int i = 0; i < size; i++) { - results[i] = mEnergyConsumerResults.valueAt(i); + final EnergyConsumerResult[] results; + final int length = energyConsumerIds.length; + if (length == 0) { + final int size = mEnergyConsumerResults.size(); + results = new EnergyConsumerResult[size]; + for (int i = 0; i < size; i++) { + results[i] = mEnergyConsumerResults.valueAt(i); + } + } else { + results = new EnergyConsumerResult[length]; + for (int i = 0; i < length; i++) { + results[i] = mEnergyConsumerResults.get(energyConsumerIds[i]); + } } future.complete(results); return future; 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 5bb65abd3033..6add8d18aa3e 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -3993,6 +3993,30 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + public void testGetSetNetworkSlicing() throws Exception { + assertExpectException(SecurityException.class, null, + () -> dpm.setNetworkSlicingEnabled(false)); + + assertExpectException(SecurityException.class, null, + () -> dpm.isNetworkSlicingEnabled()); + + assertExpectException(SecurityException.class, null, + () -> dpm.isNetworkSlicingEnabledForUser(UserHandle.of(CALLER_USER_HANDLE))); + + mContext.callerPermissions.add(permission.READ_NETWORK_DEVICE_CONFIG); + mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL); + try { + dpm.isNetworkSlicingEnabledForUser(UserHandle.of(CALLER_USER_HANDLE)); + } catch (SecurityException se) { + fail("Threw SecurityException with right permission"); + } + + setupProfileOwner(); + dpm.setNetworkSlicingEnabled(false); + assertThat(dpm.isNetworkSlicingEnabled()).isFalse(); + } + + @Test public void testSetSystemSettingFailWithNonWhitelistedSettings() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java index fcbd89781de4..1958cb01b510 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java @@ -58,6 +58,7 @@ final class FakeNativeWrapper implements NativeWrapper { private int mMyPhysicalAddress = 0; private HdmiPortInfo[] mHdmiPortInfo = null; private HdmiCecController.HdmiCecCallback mCallback = null; + private int mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0; @Override public String nativeInit() { @@ -96,7 +97,7 @@ final class FakeNativeWrapper implements NativeWrapper { @Override public int nativeGetVersion() { - return HdmiControlManager.HDMI_CEC_VERSION_2_0; + return mCecVersion; } @Override @@ -132,6 +133,10 @@ final class FakeNativeWrapper implements NativeWrapper { mPortConnectionStatus.put(port, connected); } + public void setCecVersion(@HdmiControlManager.HdmiCecVersion int cecVersion) { + mCecVersion = cecVersion; + } + public void onCecMessage(HdmiCecMessage hdmiCecMessage) { if (mCallback == null) { return; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index be584d7b4591..462f3e32ab3b 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -49,6 +49,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -622,6 +623,45 @@ public class HdmiControlServiceTest { assertEquals(runnerUid, Binder.getCallingWorkSourceUid()); } + @Ignore("b/180499471") + @Test + public void initCecVersion_limitToMinimumSupportedVersion() { + mHdmiControlService.getHdmiCecConfig().setIntValue( + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, + HdmiControlManager.HDMI_CEC_VERSION_2_0); + mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + + mHdmiControlService.initService(); + assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + } + + @Ignore("b/180499471") + @Test + public void initCecVersion_limitToAtLeast1_4() { + mHdmiControlService.getHdmiCecConfig().setIntValue( + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, + HdmiControlManager.HDMI_CEC_VERSION_2_0); + mNativeWrapper.setCecVersion(0x0); + + mHdmiControlService.initService(); + assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + } + + @Ignore("b/180499471") + @Test + public void initCecVersion_useHighestMatchingVersion() { + mHdmiControlService.getHdmiCecConfig().setIntValue( + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, + HdmiControlManager.HDMI_CEC_VERSION_2_0); + mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_2_0); + + mHdmiControlService.initService(); + assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + HdmiControlManager.HDMI_CEC_VERSION_2_0); + } + private static class VolumeControlFeatureCallback extends IHdmiCecVolumeControlFeatureListener.Stub { boolean mCallbackReceived = false; diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java index 1f66c7c02658..67d69292b476 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java @@ -339,11 +339,11 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { mService.setLockCredential(nonePassword(), newPattern("123654"), PRIMARY_USER_ID); // Verify fingerprint is removed - verify(mFingerprintManager).remove(any(), eq(PRIMARY_USER_ID), any()); - verify(mFaceManager).remove(any(), eq(PRIMARY_USER_ID), any()); + verify(mFingerprintManager).removeAll(eq(PRIMARY_USER_ID), any()); + verify(mFaceManager).removeAll(eq(PRIMARY_USER_ID), any()); - verify(mFingerprintManager).remove(any(), eq(MANAGED_PROFILE_USER_ID), any()); - verify(mFaceManager).remove(any(), eq(MANAGED_PROFILE_USER_ID), any()); + verify(mFingerprintManager).removeAll(eq(MANAGED_PROFILE_USER_ID), any()); + verify(mFaceManager).removeAll(eq(MANAGED_PROFILE_USER_ID), any()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java index a112b145fdc9..ecff409ccaf1 100644 --- a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java @@ -16,58 +16,104 @@ package com.android.server.people; +import static android.app.people.ConversationStatus.ACTIVITY_GAME; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.people.ConversationChannel; +import android.app.people.ConversationStatus; +import android.app.people.IConversationListener; +import android.app.people.IPeopleManager; +import android.app.people.PeopleManager; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionSessionId; import android.app.prediction.AppTarget; import android.app.prediction.IPredictionCallback; import android.content.Context; import android.content.pm.ParceledListSlice; +import android.content.pm.ShortcutInfo; import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; +import android.os.test.TestLooper; +import android.testing.AndroidTestingRunner; +import android.testing.TestableContext; +import android.testing.TestableLooper; + +import androidx.test.InstrumentationRegistry; import com.android.server.LocalServices; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.function.Consumer; -@RunWith(JUnit4.class) +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper public final class PeopleServiceTest { - private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share"; private static final int APP_PREDICTION_TARGET_COUNT = 4; private static final String TEST_PACKAGE_NAME = "com.example"; private static final int USER_ID = 0; + private static final String CONVERSATION_ID_1 = "12"; + private static final String CONVERSATION_ID_2 = "123"; private PeopleServiceInternal mServiceInternal; private PeopleService.LocalService mLocalService; private AppPredictionSessionId mSessionId; private AppPredictionContext mPredictionContext; - @Mock private Context mContext; - @Mock private IPredictionCallback mCallback; + @Mock + private Context mMockContext; + + @Rule + public final TestableContext mContext = + new TestableContext(InstrumentationRegistry.getContext(), null); + + protected TestableContext getContext() { + return mContext; + } + + @Mock + private IPredictionCallback mCallback; + private TestableLooper mTestableLooper; + private final TestLooper mTestLooper = new TestLooper(); + + private TestablePeopleService mPeopleService; + private IPeopleManager mIPeopleManager; + private PeopleManager mPeopleManager; @Before public void setUp() { MockitoAnnotations.initMocks(this); - when(mContext.getPackageName()).thenReturn(TEST_PACKAGE_NAME); + mPeopleService = new TestablePeopleService(mContext); + mTestableLooper = TestableLooper.get(this); + mIPeopleManager = ((IPeopleManager) mPeopleService.mService); + mPeopleManager = new PeopleManager(mContext, mIPeopleManager); + when(mMockContext.getPackageName()).thenReturn(TEST_PACKAGE_NAME); when(mCallback.asBinder()).thenReturn(new Binder()); - PeopleService service = new PeopleService(mContext); service.onStart(/* isForTesting= */ true); @@ -75,7 +121,7 @@ public final class PeopleServiceTest { mLocalService = (PeopleService.LocalService) mServiceInternal; mSessionId = new AppPredictionSessionId("abc", USER_ID); - mPredictionContext = new AppPredictionContext.Builder(mContext) + mPredictionContext = new AppPredictionContext.Builder(mMockContext) .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE) .setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT) .setExtras(new Bundle()) @@ -111,4 +157,134 @@ public final class PeopleServiceTest { mServiceInternal.onDestroyPredictionSession(mSessionId); } + + @Test + public void testRegisterConversationListener() throws Exception { + assertEquals(0, + mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount()); + + mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1, + new TestableConversationListener()); + mTestableLooper.processAllMessages(); + assertEquals(1, + mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount()); + + mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1, + new TestableConversationListener()); + mTestableLooper.processAllMessages(); + assertEquals(2, + mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount()); + + mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_2, + new TestableConversationListener()); + mTestableLooper.processAllMessages(); + assertEquals(3, + mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount()); + } + + @Test + public void testUnregisterConversationListener() throws Exception { + TestableConversationListener listener1 = new TestableConversationListener(); + mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1, + listener1); + TestableConversationListener listener2 = new TestableConversationListener(); + mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1, + listener2); + TestableConversationListener listener3 = new TestableConversationListener(); + mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_2, + listener3); + mTestableLooper.processAllMessages(); + assertEquals(3, + mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount()); + + mIPeopleManager.unregisterConversationListener( + listener2); + assertEquals(2, + mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount()); + mIPeopleManager.unregisterConversationListener( + listener1); + assertEquals(1, + mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount()); + mIPeopleManager.unregisterConversationListener( + listener3); + assertEquals(0, + mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount()); + } + + @Test + public void testOnlyTriggersConversationListenersForRegisteredConversation() { + PeopleManager.ConversationListener listenerForConversation1 = mock( + PeopleManager.ConversationListener.class); + registerListener(CONVERSATION_ID_1, listenerForConversation1); + PeopleManager.ConversationListener secondListenerForConversation1 = mock( + PeopleManager.ConversationListener.class); + registerListener(CONVERSATION_ID_1, secondListenerForConversation1); + PeopleManager.ConversationListener listenerForConversation2 = mock( + PeopleManager.ConversationListener.class); + registerListener(CONVERSATION_ID_2, listenerForConversation2); + assertEquals(3, + mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount()); + + // Update conversation with two listeners. + ConversationStatus status = new ConversationStatus.Builder(CONVERSATION_ID_1, + ACTIVITY_GAME).build(); + mPeopleService.mConversationListenerHelper.onConversationsUpdate( + Arrays.asList(getConversation(CONVERSATION_ID_1, status))); + mTestLooper.dispatchAll(); + + // Never update listeners for other conversations. + verify(listenerForConversation2, never()).onConversationUpdate(any()); + // Should update both listeners for the conversation. + ArgumentCaptor<ConversationChannel> capturedConversation = ArgumentCaptor.forClass( + ConversationChannel.class); + verify(listenerForConversation1, times(1)).onConversationUpdate( + capturedConversation.capture()); + ConversationChannel conversationChannel = capturedConversation.getValue(); + verify(secondListenerForConversation1, times(1)).onConversationUpdate( + eq(conversationChannel)); + assertEquals(conversationChannel.getShortcutInfo().getId(), CONVERSATION_ID_1); + assertThat(conversationChannel.getStatuses()).containsExactly(status); + } + + private void registerListener(String conversationId, + PeopleManager.ConversationListener listener) { + mPeopleManager.registerConversationListener(mContext.getPackageName(), mContext.getUserId(), + conversationId, listener, + mTestLooper.getNewExecutor()); + } + + private ConversationChannel getConversation(String shortcutId, ConversationStatus status) { + ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, + shortcutId).setLongLabel( + "name").build(); + NotificationChannel notificationChannel = new NotificationChannel("123", + "channel", + NotificationManager.IMPORTANCE_DEFAULT); + return new ConversationChannel(shortcutInfo, 0, + notificationChannel, null, + 123L, false, false, Arrays.asList(status)); + } + + private class TestableConversationListener extends IConversationListener.Stub { + @Override + public void onConversationUpdate(ConversationChannel conversation) { + } + } + + // Use a Testable subclass so we can simulate calls from the system without failing. + private static class TestablePeopleService extends PeopleService { + TestablePeopleService(Context context) { + super(context); + } + + @Override + public void onStart() { + super.onStart(true); + } + + @Override + protected void enforceSystemRootOrSystemUI(Context context, String message) { + return; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index 50d9f6165204..7709edbbbb31 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -79,6 +79,7 @@ import android.os.CancellationSignal; import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; +import android.os.test.TestLooper; import android.provider.ContactsContract; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; @@ -91,8 +92,11 @@ import com.android.internal.app.ChooserActivity; import com.android.internal.content.PackageMonitor; import com.android.server.LocalServices; import com.android.server.notification.NotificationManagerInternal; +import com.android.server.people.PeopleService; import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.google.common.collect.Iterables; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -123,6 +127,7 @@ public final class DataManagerTest { private static final String TEST_PKG_NAME = "pkg"; private static final String TEST_CLASS_NAME = "class"; private static final String TEST_SHORTCUT_ID = "sc"; + private static final String TEST_SHORTCUT_ID_2 = "sc2"; private static final int TEST_PKG_UID = 35; private static final String CONTACT_URI = "content://com.android.contacts/contacts/lookup/123"; private static final String PHONE_NUMBER = "+1234567890"; @@ -157,6 +162,7 @@ public final class DataManagerTest { private ShortcutChangeCallback mShortcutChangeCallback; private ShortcutInfo mShortcutInfo; private TestInjector mInjector; + private TestLooper mLooper; @Before public void setUp() throws PackageManager.NameNotFoundException { @@ -237,7 +243,8 @@ public final class DataManagerTest { mCancellationSignal = new CancellationSignal(); mInjector = new TestInjector(); - mDataManager = new DataManager(mContext, mInjector); + mLooper = new TestLooper(); + mDataManager = new DataManager(mContext, mInjector, mLooper.getLooper()); mDataManager.initialize(); when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), @@ -515,6 +522,84 @@ public final class DataManagerTest { } @Test + public void testAddConversationsListener() throws Exception { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.addOrUpdateConversationInfo(shortcut); + ConversationChannel conversationChannel = mDataManager.getConversation(TEST_PKG_NAME, + USER_ID_PRIMARY, + TEST_SHORTCUT_ID); + + PeopleService.ConversationsListener listener = mock( + PeopleService.ConversationsListener.class); + mDataManager.addConversationsListener(listener); + + List<ConversationChannel> changedConversations = Arrays.asList(conversationChannel); + verify(listener, times(0)).onConversationsUpdate(eq(changedConversations)); + mDataManager.notifyConversationsListeners(changedConversations); + mLooper.dispatchAll(); + + verify(listener, times(1)).onConversationsUpdate(eq(changedConversations)); + } + + @Test + public void testAddConversationListenersNotifiesMultipleConversations() throws Exception { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.addOrUpdateConversationInfo(shortcut); + ConversationChannel conversationChannel = mDataManager.getConversation(TEST_PKG_NAME, + USER_ID_PRIMARY, + TEST_SHORTCUT_ID); + ShortcutInfo shortcut2 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, + TEST_SHORTCUT_ID_2, + buildPerson()); + mDataManager.addOrUpdateConversationInfo(shortcut2); + ConversationChannel conversationChannel2 = mDataManager.getConversation(TEST_PKG_NAME, + USER_ID_PRIMARY, + TEST_SHORTCUT_ID_2); + PeopleService.ConversationsListener listener = mock( + PeopleService.ConversationsListener.class); + mDataManager.addConversationsListener(listener); + + List<ConversationChannel> changedConversations = Arrays.asList(conversationChannel, + conversationChannel2); + verify(listener, times(0)).onConversationsUpdate(eq(changedConversations)); + mDataManager.notifyConversationsListeners(changedConversations); + mLooper.dispatchAll(); + + verify(listener, times(1)).onConversationsUpdate(eq(changedConversations)); + ArgumentCaptor<List<ConversationChannel>> capturedConversation = ArgumentCaptor.forClass( + List.class); + verify(listener, times(1)).onConversationsUpdate(capturedConversation.capture()); + assertThat(capturedConversation.getValue()).containsExactly(conversationChannel, + conversationChannel2); + } + + @Test + public void testAddOrUpdateStatusNotifiesConversationsListeners() throws Exception { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.addOrUpdateConversationInfo(shortcut); + PeopleService.ConversationsListener listener = mock( + PeopleService.ConversationsListener.class); + mDataManager.addConversationsListener(listener); + + ConversationStatus status = new ConversationStatus.Builder("cs1", ACTIVITY_GAME).build(); + mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, status); + mLooper.dispatchAll(); + + ArgumentCaptor<List<ConversationChannel>> capturedConversation = ArgumentCaptor.forClass( + List.class); + verify(listener, times(1)).onConversationsUpdate(capturedConversation.capture()); + ConversationChannel result = Iterables.getOnlyElement(capturedConversation.getValue()); + assertThat(result.getStatuses()).containsExactly(status); + assertEquals(result.getShortcutInfo().getId(), TEST_SHORTCUT_ID); + } + + @Test public void testGetConversationReturnsCustomizedConversation() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); @@ -975,7 +1060,7 @@ public final class DataManagerTest { mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter); byte[] payload = mDataManager.getBackupPayload(USER_ID_PRIMARY); - DataManager dataManager = new DataManager(mContext, mInjector); + DataManager dataManager = new DataManager(mContext, mInjector, mLooper.getLooper()); dataManager.onUserUnlocked(USER_ID_PRIMARY); dataManager.restore(USER_ID_PRIMARY, payload); ConversationInfo conversationInfo = dataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY) diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java index 03e60afe4489..ddbe81c81d6d 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java @@ -16,6 +16,7 @@ package com.android.server.powerstats; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -34,6 +35,9 @@ import androidx.test.InstrumentationRegistry; import com.android.server.SystemService; import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper; +import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils; +import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerUtils; +import com.android.server.powerstats.ProtoStreamUtils.PowerEntityUtils; import com.android.server.powerstats.nano.PowerEntityProto; import com.android.server.powerstats.nano.PowerStatsServiceMeterProto; import com.android.server.powerstats.nano.PowerStatsServiceModelProto; @@ -52,6 +56,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.Files; +import java.util.Arrays; import java.util.Random; /** @@ -63,15 +68,18 @@ import java.util.Random; public class PowerStatsServiceTest { private static final String TAG = PowerStatsServiceTest.class.getSimpleName(); private static final String DATA_STORAGE_SUBDIR = "powerstatstest"; - private static final String METER_FILENAME = "metertest"; - private static final String MODEL_FILENAME = "modeltest"; - private static final String RESIDENCY_FILENAME = "residencytest"; + private static final String METER_FILENAME = "log.powerstats.metertest.0"; + private static final String MODEL_FILENAME = "log.powerstats.modeltest.0"; + private static final String RESIDENCY_FILENAME = "log.powerstats.residencytest.0"; private static final String PROTO_OUTPUT_FILENAME = "powerstats.proto"; private static final String CHANNEL_NAME = "channelname"; private static final String CHANNEL_SUBSYSTEM = "channelsubsystem"; private static final String POWER_ENTITY_NAME = "powerentityinfo"; private static final String STATE_NAME = "stateinfo"; private static final String ENERGY_CONSUMER_NAME = "energyconsumer"; + private static final String METER_CACHE_FILENAME = "meterCacheTest"; + private static final String MODEL_CACHE_FILENAME = "modelCacheTest"; + private static final String RESIDENCY_CACHE_FILENAME = "residencyCacheTest"; private static final int ENERGY_METER_COUNT = 8; private static final int ENERGY_CONSUMER_COUNT = 2; private static final int ENERGY_CONSUMER_ATTRIBUTION_COUNT = 5; @@ -90,12 +98,12 @@ public class PowerStatsServiceTest { private TestPowerStatsHALWrapper mTestPowerStatsHALWrapper = new TestPowerStatsHALWrapper(); @Override File createDataStoragePath() { - mDataStorageDir = null; - - try { - mDataStorageDir = Files.createTempDirectory(DATA_STORAGE_SUBDIR).toFile(); - } catch (IOException e) { - fail("Could not create temp directory."); + if (mDataStorageDir == null) { + try { + mDataStorageDir = Files.createTempDirectory(DATA_STORAGE_SUBDIR).toFile(); + } catch (IOException e) { + fail("Could not create temp directory."); + } } return mDataStorageDir; @@ -117,16 +125,36 @@ public class PowerStatsServiceTest { } @Override + String createMeterCacheFilename() { + return METER_CACHE_FILENAME; + } + + @Override + String createModelCacheFilename() { + return MODEL_CACHE_FILENAME; + } + + @Override + String createResidencyCacheFilename() { + return RESIDENCY_CACHE_FILENAME; + } + + @Override IPowerStatsHALWrapper getPowerStatsHALWrapperImpl() { return mTestPowerStatsHALWrapper; } @Override PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath, - String meterFilename, String modelFilename, String residencyFilename, + String meterFilename, String meterCacheFilename, + String modelFilename, String modelCacheFilename, + String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { - mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, meterFilename, - modelFilename, residencyFilename, powerStatsHALWrapper); + mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, + meterFilename, meterCacheFilename, + modelFilename, modelCacheFilename, + residencyFilename, residencyCacheFilename, + powerStatsHALWrapper); return mPowerStatsLogger; } @@ -665,4 +693,315 @@ public class PowerStatsServiceTest { // input buffer had only length and no data. assertTrue(pssProto.stateResidencyResult.length == 0); } + + @Test + public void testDataStorageDeletedMeterMismatch() throws IOException { + // Create the directory where cached data will be stored. + mInjector.createDataStoragePath(); + + // In order to create cached data that will match the current data read by the + // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is + // returned from the Injector. + IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl(); + + // Generate random array of bytes to emulate cached meter data. Store to file. + Random rd = new Random(); + byte[] bytes = new byte[100]; + rd.nextBytes(bytes); + File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename()); + FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile); + onDeviceStorageFos.write(bytes); + onDeviceStorageFos.close(); + + // Create cached energy consumer data and write to file. + EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo(); + bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers); + onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename()); + onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile); + onDeviceStorageFos.write(bytes); + onDeviceStorageFos.close(); + + // Create cached power entity info data and write to file. + PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo(); + bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo); + onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename()); + onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile); + onDeviceStorageFos.write(bytes); + onDeviceStorageFos.close(); + + // Create log files. + File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename()); + File modelFile = new File(mDataStorageDir, mInjector.createModelFilename()); + File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename()); + meterFile.createNewFile(); + modelFile.createNewFile(); + residencyFile.createNewFile(); + + // Verify log files exist. + assertTrue(meterFile.exists()); + assertTrue(modelFile.exists()); + assertTrue(residencyFile.exists()); + + // Boot device after creating old cached data. + mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + + // Since cached meter data is just random bytes it won't match the data read from the HAL. + // This mismatch of cached and current HAL data should force a delete. + assertTrue(mService.getDeleteMeterDataOnBoot()); + assertFalse(mService.getDeleteModelDataOnBoot()); + assertFalse(mService.getDeleteResidencyDataOnBoot()); + + // Verify log files were deleted. + assertFalse(meterFile.exists()); + assertTrue(modelFile.exists()); + assertTrue(residencyFile.exists()); + + // Verify cached meter data was updated to new HAL output. + Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo(); + byte[] bytesExpected = ChannelUtils.getProtoBytes(channels); + onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename()); + byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()]; + FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile); + onDeviceStorageFis.read(bytesActual); + assertTrue(Arrays.equals(bytesExpected, bytesActual)); + } + + @Test + public void testDataStorageDeletedModelMismatch() throws IOException { + // Create the directory where cached data will be stored. + mInjector.createDataStoragePath(); + + // In order to create cached data that will match the current data read by the + // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is + // returned from the Injector. + IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl(); + + // Create cached channel data and write to file. + Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo(); + byte[] bytes = ChannelUtils.getProtoBytes(channels); + File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename()); + FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile); + onDeviceStorageFos.write(bytes); + onDeviceStorageFos.close(); + + // Generate random array of bytes to emulate cached energy consumer data. Store to file. + Random rd = new Random(); + bytes = new byte[100]; + rd.nextBytes(bytes); + onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename()); + onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile); + onDeviceStorageFos.write(bytes); + onDeviceStorageFos.close(); + + // Create cached power entity info data and write to file. + PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo(); + bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo); + onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename()); + onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile); + onDeviceStorageFos.write(bytes); + onDeviceStorageFos.close(); + + // Create log files. + File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename()); + File modelFile = new File(mDataStorageDir, mInjector.createModelFilename()); + File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename()); + meterFile.createNewFile(); + modelFile.createNewFile(); + residencyFile.createNewFile(); + + // Verify log files exist. + assertTrue(meterFile.exists()); + assertTrue(modelFile.exists()); + assertTrue(residencyFile.exists()); + + // Boot device after creating old cached data. + mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + + // Since cached energy consumer data is just random bytes it won't match the data read from + // the HAL. This mismatch of cached and current HAL data should force a delete. + assertFalse(mService.getDeleteMeterDataOnBoot()); + assertTrue(mService.getDeleteModelDataOnBoot()); + assertFalse(mService.getDeleteResidencyDataOnBoot()); + + // Verify log files were deleted. + assertTrue(meterFile.exists()); + assertFalse(modelFile.exists()); + assertTrue(residencyFile.exists()); + + // Verify cached energy consumer data was updated to new HAL output. + EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo(); + byte[] bytesExpected = EnergyConsumerUtils.getProtoBytes(energyConsumers); + onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename()); + byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()]; + FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile); + onDeviceStorageFis.read(bytesActual); + assertTrue(Arrays.equals(bytesExpected, bytesActual)); + } + + @Test + public void testDataStorageDeletedResidencyMismatch() throws IOException { + // Create the directory where cached data will be stored. + mInjector.createDataStoragePath(); + + // In order to create cached data that will match the current data read by the + // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is + // returned from the Injector. + IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl(); + + // Create cached channel data and write to file. + Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo(); + byte[] bytes = ChannelUtils.getProtoBytes(channels); + File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename()); + FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile); + onDeviceStorageFos.write(bytes); + onDeviceStorageFos.close(); + + // Create cached energy consumer data and write to file. + EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo(); + bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers); + onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename()); + onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile); + onDeviceStorageFos.write(bytes); + onDeviceStorageFos.close(); + + // Generate random array of bytes to emulate cached power entity info data. Store to file. + Random rd = new Random(); + bytes = new byte[100]; + rd.nextBytes(bytes); + onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename()); + onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile); + onDeviceStorageFos.write(bytes); + onDeviceStorageFos.close(); + + // Create log files. + File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename()); + File modelFile = new File(mDataStorageDir, mInjector.createModelFilename()); + File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename()); + meterFile.createNewFile(); + modelFile.createNewFile(); + residencyFile.createNewFile(); + + // Verify log files exist. + assertTrue(meterFile.exists()); + assertTrue(modelFile.exists()); + assertTrue(residencyFile.exists()); + + // Boot device after creating old cached data. + mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + + // Since cached power entity info data is just random bytes it won't match the data read + // from the HAL. This mismatch of cached and current HAL data should force a delete. + assertFalse(mService.getDeleteMeterDataOnBoot()); + assertFalse(mService.getDeleteModelDataOnBoot()); + assertTrue(mService.getDeleteResidencyDataOnBoot()); + + // Verify log files were deleted. + assertTrue(meterFile.exists()); + assertTrue(modelFile.exists()); + assertFalse(residencyFile.exists()); + + // Verify cached power entity data was updated to new HAL output. + PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo(); + byte[] bytesExpected = PowerEntityUtils.getProtoBytes(powerEntityInfo); + onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename()); + byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()]; + FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile); + onDeviceStorageFis.read(bytesActual); + assertTrue(Arrays.equals(bytesExpected, bytesActual)); + } + + @Test + public void testDataStorageNotDeletedNoCachedData() throws IOException { + // Create the directory where log files will be stored. + mInjector.createDataStoragePath(); + + // Create log files. + File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename()); + File modelFile = new File(mDataStorageDir, mInjector.createModelFilename()); + File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename()); + meterFile.createNewFile(); + modelFile.createNewFile(); + residencyFile.createNewFile(); + + // Verify log files exist. + assertTrue(meterFile.exists()); + assertTrue(modelFile.exists()); + assertTrue(residencyFile.exists()); + + // This test mimics the device's first boot where there is no cached data. + mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + + // Since there is no cached data on the first boot any log files that happen to exist + // should be deleted. + assertTrue(mService.getDeleteMeterDataOnBoot()); + assertTrue(mService.getDeleteModelDataOnBoot()); + assertTrue(mService.getDeleteResidencyDataOnBoot()); + + // Verify log files were deleted. + assertFalse(meterFile.exists()); + assertFalse(modelFile.exists()); + assertFalse(residencyFile.exists()); + } + + @Test + public void testDataStorageNotDeletedAllDataMatches() throws IOException { + // Create the directory where cached data will be stored. + mInjector.createDataStoragePath(); + + // In order to create cached data that will match the current data read by the + // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is + // returned from the Injector. + IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl(); + + // Create cached channel data and write to file. + Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo(); + byte[] bytes = ChannelUtils.getProtoBytes(channels); + File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename()); + FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile); + onDeviceStorageFos.write(bytes); + onDeviceStorageFos.close(); + + // Create cached energy consumer data and write to file. + EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo(); + bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers); + onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename()); + onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile); + onDeviceStorageFos.write(bytes); + onDeviceStorageFos.close(); + + // Create cached power entity info data and write to file. + PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo(); + bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo); + onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename()); + onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile); + onDeviceStorageFos.write(bytes); + onDeviceStorageFos.close(); + + // Create log files. + File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename()); + File modelFile = new File(mDataStorageDir, mInjector.createModelFilename()); + File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename()); + meterFile.createNewFile(); + modelFile.createNewFile(); + residencyFile.createNewFile(); + + // Verify log files exist. + assertTrue(meterFile.exists()); + assertTrue(modelFile.exists()); + assertTrue(residencyFile.exists()); + + // Boot device after creating old cached data. + mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + + // All cached data created above should match current data read in PowerStatsService so we + // expect the data not to be deleted. + assertFalse(mService.getDeleteMeterDataOnBoot()); + assertFalse(mService.getDeleteModelDataOnBoot()); + assertFalse(mService.getDeleteResidencyDataOnBoot()); + + // Verify log files were not deleted. + assertTrue(meterFile.exists()); + assertTrue(modelFile.exists()); + assertTrue(residencyFile.exists()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 15e045c85c29..2fdd63ed93d5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -16,8 +16,11 @@ package com.android.server.wm; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; +import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER; import static android.view.WindowManager.TRANSIT_OLD_NONE; import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE; @@ -94,10 +97,19 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mController = new RemoteAnimationController(mWm, mAdapter, mHandler); } + private WindowState createAppOverlayWindow() { + final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION_OVERLAY, + "testOverlayWindow"); + win.mActivityRecord = null; + win.mHasSurface = true; + return win; + } + @Test public void testRun() throws Exception { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); mDisplayContent.mOpeningApps.add(win.mActivityRecord); + final WindowState overlayWin = createAppOverlayWindow(); try { final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord, new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter; @@ -109,12 +121,12 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); - final ArgumentCaptor<RemoteAnimationTarget[]> nonApsCaptor = + final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN), - appsCaptor.capture(), wallpapersCaptor.capture(), nonApsCaptor.capture(), + appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(), finishedCaptor.capture()); assertEquals(1, appsCaptor.getValue().length); final RemoteAnimationTarget app = appsCaptor.getValue()[0]; @@ -130,6 +142,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { finishedCaptor.getValue().onAnimationFinished(); verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), eq(adapter)); + assertEquals(0, nonAppsCaptor.getValue().length); } finally { mDisplayContent.mOpeningApps.clear(); } @@ -424,6 +437,89 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { } } + @Test + public void testNonAppIncluded_keygaurdGoingAway() throws Exception { + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + mDisplayContent.mOpeningApps.add(win.mActivityRecord); + // Add overlay window hidden by the keyguard. + final WindowState overlayWin = createAppOverlayWindow(); + overlayWin.hide(false /* doAnimation */, false /* requestAnim */); + try { + final AnimationAdapter adapter = mController.createRemoteAnimationRecord( + win.mActivityRecord, new Point(50, 100), null, + new Rect(50, 100, 150, 150), null).mAdapter; + adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, + mFinishedCallback); + mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = + ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); + verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_KEYGUARD_GOING_AWAY), + appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(), + finishedCaptor.capture()); + assertEquals(1, appsCaptor.getValue().length); + final RemoteAnimationTarget app = appsCaptor.getValue()[0]; + assertEquals(new Point(50, 100), app.position); + assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds); + assertEquals(win.mActivityRecord.getPrefixOrderIndex(), app.prefixOrderIndex); + assertEquals(win.mActivityRecord.getTask().mTaskId, app.taskId); + assertEquals(mMockLeash, app.leash); + assertEquals(false, app.isTranslucent); + verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y); + verify(mMockTransaction).setWindowCrop(mMockLeash, 100, 50); + + finishedCaptor.getValue().onAnimationFinished(); + verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), + eq(adapter)); + assertEquals(1, nonAppsCaptor.getValue().length); + } finally { + mDisplayContent.mOpeningApps.clear(); + } + } + + @Test + public void testNonAppIncluded_keygaurdGoingAwayToWallpaper() throws Exception { + final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class), + true, mDisplayContent, true /* ownerCanManageAppTokens */); + spyOn(mDisplayContent.mWallpaperController); + doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible(); + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + mDisplayContent.mOpeningApps.add(win.mActivityRecord); + // Add overlay window hidden by the keyguard. + final WindowState overlayWin = createAppOverlayWindow(); + overlayWin.hide(false /* doAnimation */, false /* requestAnim */); + try { + final AnimationAdapter adapter = mController.createRemoteAnimationRecord( + win.mActivityRecord, new Point(50, 100), null, + new Rect(50, 100, 150, 150), null).mAdapter; + adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, + mFinishedCallback); + mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = + ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); + verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER), + appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(), + finishedCaptor.capture()); + assertEquals(1, wallpapersCaptor.getValue().length); + assertEquals(1, nonAppsCaptor.getValue().length); + } finally { + mDisplayContent.mOpeningApps.clear(); + } + } + private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) { verify(binder, atLeast(0)).asBinder(); verifyNoMoreInteractions(binder); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index e4b865fd2941..86d8eee878fd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -91,11 +91,6 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { return attrs.type == TYPE_NOTIFICATION_SHADE; } - @Override - public boolean canBeHiddenByKeyguardLw(WindowState win) { - return false; - } - /** * Sets a runnable to run when adding a splash screen which gets executed after the window has * been added but before returning the surface. diff --git a/services/texttospeech/Android.bp b/services/texttospeech/Android.bp deleted file mode 100644 index bacc932f760f..000000000000 --- a/services/texttospeech/Android.bp +++ /dev/null @@ -1,13 +0,0 @@ -filegroup { - name: "services.texttospeech-sources", - srcs: ["java/**/*.java"], - path: "java", - visibility: ["//frameworks/base/services"], -} - -java_library_static { - name: "services.texttospeech", - defaults: ["platform_service_defaults"], - srcs: [":services.texttospeech-sources"], - libs: ["services.core"], -}
\ No newline at end of file diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java deleted file mode 100644 index f80590420d09..000000000000 --- a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java +++ /dev/null @@ -1,184 +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.server.texttospeech; - -import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS; - -import android.annotation.NonNull; -import android.annotation.UserIdInt; -import android.app.AppGlobals; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ServiceInfo; -import android.os.IBinder.DeathRecipient; -import android.os.RemoteException; -import android.speech.tts.ITextToSpeechService; -import android.speech.tts.ITextToSpeechSession; -import android.speech.tts.ITextToSpeechSessionCallback; -import android.speech.tts.TextToSpeech; -import android.util.Slog; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.infra.ServiceConnector; -import com.android.server.infra.AbstractPerUserSystemService; - -import java.util.NoSuchElementException; - -/** - * Manages per-user text to speech session activated by {@link TextToSpeechManagerService}. - * Creates {@link TtsClient} interface object with direct connection to - * {@link android.speech.tts.TextToSpeechService} provider. - * - * @see ITextToSpeechSession - * @see TextToSpeech - */ -final class TextToSpeechManagerPerUserService extends - AbstractPerUserSystemService<TextToSpeechManagerPerUserService, - TextToSpeechManagerService> { - - private static final String TAG = TextToSpeechManagerPerUserService.class.getSimpleName(); - - TextToSpeechManagerPerUserService( - @NonNull TextToSpeechManagerService master, - @NonNull Object lock, @UserIdInt int userId) { - super(master, lock, userId); - } - - void createSessionLocked(String engine, ITextToSpeechSessionCallback sessionCallback) { - TextToSpeechSessionConnection.start(getContext(), mUserId, engine, sessionCallback); - } - - @GuardedBy("mLock") - @Override // from PerUserSystemService - @NonNull - protected ServiceInfo newServiceInfoLocked( - @SuppressWarnings("unused") @NonNull ComponentName serviceComponent) - throws PackageManager.NameNotFoundException { - try { - return AppGlobals.getPackageManager().getServiceInfo(serviceComponent, - PackageManager.GET_META_DATA, mUserId); - } catch (RemoteException e) { - throw new PackageManager.NameNotFoundException( - "Could not get service for " + serviceComponent); - } - } - - private static class TextToSpeechSessionConnection extends - ServiceConnector.Impl<ITextToSpeechService> { - - private final String mEngine; - private final ITextToSpeechSessionCallback mCallback; - private final DeathRecipient mUnbindOnDeathHandler; - - static void start(Context context, @UserIdInt int userId, String engine, - ITextToSpeechSessionCallback callback) { - new TextToSpeechSessionConnection(context, userId, engine, callback).start(); - } - - private TextToSpeechSessionConnection(Context context, @UserIdInt int userId, String engine, - ITextToSpeechSessionCallback callback) { - super(context, - new Intent(TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE).setPackage(engine), - Context.BIND_AUTO_CREATE, - userId, - ITextToSpeechService.Stub::asInterface); - mEngine = engine; - mCallback = callback; - mUnbindOnDeathHandler = () -> unbindEngine("client process death is reported"); - } - - private void start() { - Slog.d(TAG, "Trying to start connection to TTS engine: " + mEngine); - - connect() - .thenAccept( - serviceBinder -> { - if (serviceBinder != null) { - Slog.d(TAG, - "Connected successfully to TTS engine: " + mEngine); - try { - mCallback.onConnected(new ITextToSpeechSession.Stub() { - @Override - public void disconnect() { - unbindEngine("client disconnection request"); - } - }, serviceBinder.asBinder()); - - mCallback.asBinder().linkToDeath(mUnbindOnDeathHandler, 0); - } catch (RemoteException ex) { - Slog.w(TAG, "Error notifying the client on connection", ex); - - unbindEngine( - "failed communicating with the client - process " - + "is dead"); - } - } else { - Slog.w(TAG, "Failed to obtain TTS engine binder"); - runSessionCallbackMethod( - () -> mCallback.onError("Failed creating TTS session")); - } - }) - .exceptionally(ex -> { - Slog.w(TAG, "TTS engine binding error", ex); - runSessionCallbackMethod( - () -> mCallback.onError( - "Failed creating TTS session: " + ex.getCause())); - - return null; - }); - } - - @Override // from ServiceConnector.Impl - protected void onServiceConnectionStatusChanged( - ITextToSpeechService service, boolean connected) { - if (!connected) { - Slog.w(TAG, "Disconnected from TTS engine"); - runSessionCallbackMethod(mCallback::onDisconnected); - - try { - mCallback.asBinder().unlinkToDeath(mUnbindOnDeathHandler, 0); - } catch (NoSuchElementException ex) { - Slog.d(TAG, "The death recipient was not linked."); - } - } - } - - @Override // from ServiceConnector.Impl - protected long getAutoDisconnectTimeoutMs() { - return PERMANENT_BOUND_TIMEOUT_MS; - } - - private void unbindEngine(String reason) { - Slog.d(TAG, "Unbinding TTS engine: " + mEngine + ". Reason: " + reason); - unbind(); - } - } - - static void runSessionCallbackMethod(ThrowingRunnable callbackRunnable) { - try { - callbackRunnable.runOrThrow(); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed running callback method", ex); - } - } - - interface ThrowingRunnable { - void runOrThrow() throws RemoteException; - } -} diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java deleted file mode 100644 index 9015563f439e..000000000000 --- a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java +++ /dev/null @@ -1,77 +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.server.texttospeech; - -import static com.android.server.texttospeech.TextToSpeechManagerPerUserService.runSessionCallbackMethod; - -import android.annotation.NonNull; -import android.annotation.UserIdInt; -import android.content.Context; -import android.os.UserHandle; -import android.speech.tts.ITextToSpeechManager; -import android.speech.tts.ITextToSpeechSessionCallback; - -import com.android.server.infra.AbstractMasterSystemService; - - -/** - * A service that allows secured synthesizing of text to speech audio. Upon request creates a - * session - * that is managed by {@link TextToSpeechManagerPerUserService}. - * - * @see ITextToSpeechManager - */ -public final class TextToSpeechManagerService extends - AbstractMasterSystemService<TextToSpeechManagerService, - TextToSpeechManagerPerUserService> { - - private static final String TAG = TextToSpeechManagerService.class.getSimpleName(); - - public TextToSpeechManagerService(@NonNull Context context) { - super(context, /* serviceNameResolver= */ null, - /* disallowProperty = */null); - } - - @Override // from SystemService - public void onStart() { - publishBinderService(Context.TEXT_TO_SPEECH_MANAGER_SERVICE, - new TextToSpeechManagerServiceStub()); - } - - @Override - protected TextToSpeechManagerPerUserService newServiceLocked( - @UserIdInt int resolvedUserId, boolean disabled) { - return new TextToSpeechManagerPerUserService(this, mLock, resolvedUserId); - } - - private final class TextToSpeechManagerServiceStub extends ITextToSpeechManager.Stub { - @Override - public void createSession(String engine, - ITextToSpeechSessionCallback sessionCallback) { - synchronized (mLock) { - TextToSpeechManagerPerUserService perUserService = getServiceForUserLocked( - UserHandle.getCallingUserId()); - if (perUserService != null) { - perUserService.createSessionLocked(engine, sessionCallback); - } else { - runSessionCallbackMethod( - () -> sessionCallback.onError("Service is not available for user")); - } - } - } - } -} diff --git a/core/java/android/speech/tts/ITextToSpeechSession.aidl b/telecomm/java/android/telecom/CallScreeningService.aidl index b2afeb0d1ba8..87b5138745f2 100644 --- a/core/java/android/speech/tts/ITextToSpeechSession.aidl +++ b/telecomm/java/android/telecom/CallScreeningService.aidl @@ -11,23 +11,12 @@ * 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. + * limitations under the License */ -package android.speech.tts; +package android.telecom; /** - * TextToSpeech session interface. Allows to control remote TTS service session once connected. - * - * @see ITextToSpeechManager - * @see ITextToSpeechSessionCallback - * * {@hide} */ -oneway interface ITextToSpeechSession { - - /** - * Disconnects the client from the TTS provider. - */ - void disconnect(); -}
\ No newline at end of file +parcelable CallScreeningService.ParcelableCallResponse; diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java index 7988b036ccd3..deeb4331bcaa 100644 --- a/telecomm/java/android/telecom/CallScreeningService.java +++ b/telecomm/java/android/telecom/CallScreeningService.java @@ -17,6 +17,7 @@ package android.telecom; import android.Manifest; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; @@ -30,12 +31,18 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; import com.android.internal.os.SomeArgs; import com.android.internal.telecom.ICallScreeningAdapter; import com.android.internal.telecom.ICallScreeningService; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + /** * This service can be implemented by the default dialer (see * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow @@ -132,7 +139,10 @@ public abstract class CallScreeningService extends Service { .createFromParcelableCall((ParcelableCall) args.arg2); onScreenCall(callDetails); if (callDetails.getCallDirection() == Call.Details.DIRECTION_OUTGOING) { - mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId()); + mCallScreeningAdapter.onScreeningResponse( + callDetails.getTelecomCallId(), + new ComponentName(getPackageName(), getClass().getName()), + null); } } catch (RemoteException e) { Log.w(this, "Exception when screening call: " + e); @@ -157,16 +167,171 @@ public abstract class CallScreeningService extends Service { private ICallScreeningAdapter mCallScreeningAdapter; - /* - * Information about how to respond to an incoming call. + /** + * Parcelable version of {@link CallResponse} used to do IPC. + * @hide + */ + public static class ParcelableCallResponse implements Parcelable { + private final boolean mShouldDisallowCall; + private final boolean mShouldRejectCall; + private final boolean mShouldSilenceCall; + private final boolean mShouldSkipCallLog; + private final boolean mShouldSkipNotification; + private final boolean mShouldScreenCallViaAudioProcessing; + + private final int mCallComposerAttachmentsToShow; + + private ParcelableCallResponse( + boolean shouldDisallowCall, + boolean shouldRejectCall, + boolean shouldSilenceCall, + boolean shouldSkipCallLog, + boolean shouldSkipNotification, + boolean shouldScreenCallViaAudioProcessing, + int callComposerAttachmentsToShow) { + mShouldDisallowCall = shouldDisallowCall; + mShouldRejectCall = shouldRejectCall; + mShouldSilenceCall = shouldSilenceCall; + mShouldSkipCallLog = shouldSkipCallLog; + mShouldSkipNotification = shouldSkipNotification; + mShouldScreenCallViaAudioProcessing = shouldScreenCallViaAudioProcessing; + mCallComposerAttachmentsToShow = callComposerAttachmentsToShow; + } + + protected ParcelableCallResponse(Parcel in) { + mShouldDisallowCall = in.readBoolean(); + mShouldRejectCall = in.readBoolean(); + mShouldSilenceCall = in.readBoolean(); + mShouldSkipCallLog = in.readBoolean(); + mShouldSkipNotification = in.readBoolean(); + mShouldScreenCallViaAudioProcessing = in.readBoolean(); + mCallComposerAttachmentsToShow = in.readInt(); + } + + public CallResponse toCallResponse() { + return new CallResponse.Builder() + .setDisallowCall(mShouldDisallowCall) + .setRejectCall(mShouldRejectCall) + .setSilenceCall(mShouldSilenceCall) + .setSkipCallLog(mShouldSkipCallLog) + .setSkipNotification(mShouldSkipNotification) + .setShouldScreenCallViaAudioProcessing(mShouldScreenCallViaAudioProcessing) + .setCallComposerAttachmentsToShow(mCallComposerAttachmentsToShow) + .build(); + } + + public boolean shouldDisallowCall() { + return mShouldDisallowCall; + } + + public boolean shouldRejectCall() { + return mShouldRejectCall; + } + + public boolean shouldSilenceCall() { + return mShouldSilenceCall; + } + + public boolean shouldSkipCallLog() { + return mShouldSkipCallLog; + } + + public boolean shouldSkipNotification() { + return mShouldSkipNotification; + } + + public boolean shouldScreenCallViaAudioProcessing() { + return mShouldScreenCallViaAudioProcessing; + } + + public int getCallComposerAttachmentsToShow() { + return mCallComposerAttachmentsToShow; + } + + public static final Creator<ParcelableCallResponse> CREATOR = + new Creator<ParcelableCallResponse>() { + @Override + public ParcelableCallResponse createFromParcel(Parcel in) { + return new ParcelableCallResponse(in); + } + + @Override + public ParcelableCallResponse[] newArray(int size) { + return new ParcelableCallResponse[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeBoolean(mShouldDisallowCall); + dest.writeBoolean(mShouldRejectCall); + dest.writeBoolean(mShouldSilenceCall); + dest.writeBoolean(mShouldSkipCallLog); + dest.writeBoolean(mShouldSkipNotification); + dest.writeBoolean(mShouldScreenCallViaAudioProcessing); + dest.writeInt(mCallComposerAttachmentsToShow); + } + } + + /** + * Information about how to respond to an incoming call. Call screening apps can construct an + * instance of this class using {@link CallResponse.Builder}. */ public static class CallResponse { + /** + * Bit flag indicating whether to show the picture attachment for call composer. + * + * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}. + */ + public static final int CALL_COMPOSER_ATTACHMENT_PICTURE = 1; + + /** + * Bit flag indicating whether to show the location attachment for call composer. + * + * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}. + */ + public static final int CALL_COMPOSER_ATTACHMENT_LOCATION = 1 << 1; + + /** + * Bit flag indicating whether to show the subject attachment for call composer. + * + * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}. + */ + public static final int CALL_COMPOSER_ATTACHMENT_SUBJECT = 1 << 2; + + /** + * Bit flag indicating whether to show the priority attachment for call composer. + * + * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}. + */ + public static final int CALL_COMPOSER_ATTACHMENT_PRIORITY = 1 << 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "CALL_COMPOSER_ATTACHMENT_", flag = true, + value = { + CALL_COMPOSER_ATTACHMENT_PICTURE, + CALL_COMPOSER_ATTACHMENT_LOCATION, + CALL_COMPOSER_ATTACHMENT_SUBJECT, + CALL_COMPOSER_ATTACHMENT_PRIORITY + } + ) + public @interface CallComposerAttachmentType {} + + private static final int NUM_CALL_COMPOSER_ATTACHMENT_TYPES = 4; + private final boolean mShouldDisallowCall; private final boolean mShouldRejectCall; private final boolean mShouldSilenceCall; private final boolean mShouldSkipCallLog; private final boolean mShouldSkipNotification; private final boolean mShouldScreenCallViaAudioProcessing; + private final int mCallComposerAttachmentsToShow; private CallResponse( boolean shouldDisallowCall, @@ -174,7 +339,8 @@ public abstract class CallScreeningService extends Service { boolean shouldSilenceCall, boolean shouldSkipCallLog, boolean shouldSkipNotification, - boolean shouldScreenCallViaAudioProcessing) { + boolean shouldScreenCallViaAudioProcessing, + int callComposerAttachmentsToShow) { if (!shouldDisallowCall && (shouldRejectCall || shouldSkipCallLog || shouldSkipNotification)) { throw new IllegalStateException("Invalid response state for allowed call."); @@ -190,6 +356,7 @@ public abstract class CallScreeningService extends Service { mShouldSkipNotification = shouldSkipNotification; mShouldSilenceCall = shouldSilenceCall; mShouldScreenCallViaAudioProcessing = shouldScreenCallViaAudioProcessing; + mCallComposerAttachmentsToShow = callComposerAttachmentsToShow; } /* @@ -237,6 +404,49 @@ public abstract class CallScreeningService extends Service { return mShouldScreenCallViaAudioProcessing; } + /** + * @return A bitmask of call composer attachments that should be shown to the user. + */ + public @CallComposerAttachmentType int getCallComposerAttachmentsToShow() { + return mCallComposerAttachmentsToShow; + } + + /** @hide */ + public ParcelableCallResponse toParcelable() { + return new ParcelableCallResponse( + mShouldDisallowCall, + mShouldRejectCall, + mShouldSilenceCall, + mShouldSkipCallLog, + mShouldSkipNotification, + mShouldScreenCallViaAudioProcessing, + mCallComposerAttachmentsToShow + ); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CallResponse that = (CallResponse) o; + return mShouldDisallowCall == that.mShouldDisallowCall && + mShouldRejectCall == that.mShouldRejectCall && + mShouldSilenceCall == that.mShouldSilenceCall && + mShouldSkipCallLog == that.mShouldSkipCallLog && + mShouldSkipNotification == that.mShouldSkipNotification && + mShouldScreenCallViaAudioProcessing + == that.mShouldScreenCallViaAudioProcessing && + mCallComposerAttachmentsToShow == that.mCallComposerAttachmentsToShow; + } + + @Override + public int hashCode() { + return Objects.hash(mShouldDisallowCall, mShouldRejectCall, mShouldSilenceCall, + mShouldSkipCallLog, mShouldSkipNotification, + mShouldScreenCallViaAudioProcessing, + mCallComposerAttachmentsToShow); + } + public static class Builder { private boolean mShouldDisallowCall; private boolean mShouldRejectCall; @@ -244,6 +454,7 @@ public abstract class CallScreeningService extends Service { private boolean mShouldSkipCallLog; private boolean mShouldSkipNotification; private boolean mShouldScreenCallViaAudioProcessing; + private int mCallComposerAttachmentsToShow = -1; /** * Sets whether the incoming call should be blocked. @@ -329,6 +540,38 @@ public abstract class CallScreeningService extends Service { return this; } + /** + * Sets the call composer attachments that should be shown to the user. + * + * Attachments that are not shown will not be passed to the in-call UI responsible for + * displaying the call to the user. + * + * If this method is not called on a {@link Builder}, all attachments will be shown, + * except pictures, which will only be shown to users if the call is from a contact. + * + * Setting attachments to show will have no effect if the call screening service does + * not belong to the same package as the system dialer (as returned by + * {@link TelecomManager#getSystemDialerPackage()}). + * + * @param callComposerAttachmentsToShow A bitmask of call composer attachments to show. + */ + public @NonNull Builder setCallComposerAttachmentsToShow( + @CallComposerAttachmentType int callComposerAttachmentsToShow) { + // If the argument is less than zero (meaning unset), no-op since the conversion + // to/from the parcelable version may call with that value. + if (callComposerAttachmentsToShow < 0) { + return this; + } + + if ((callComposerAttachmentsToShow + & (1 << NUM_CALL_COMPOSER_ATTACHMENT_TYPES)) != 0) { + throw new IllegalArgumentException("Attachment types must match the ones" + + " defined in CallResponse"); + } + mCallComposerAttachmentsToShow = callComposerAttachmentsToShow; + return this; + } + public CallResponse build() { return new CallResponse( mShouldDisallowCall, @@ -336,7 +579,8 @@ public abstract class CallScreeningService extends Service { mShouldSilenceCall, mShouldSkipCallLog, mShouldSkipNotification, - mShouldScreenCallViaAudioProcessing); + mShouldScreenCallViaAudioProcessing, + mCallComposerAttachmentsToShow); } } } @@ -423,21 +667,12 @@ public abstract class CallScreeningService extends Service { public final void respondToCall(@NonNull Call.Details callDetails, @NonNull CallResponse response) { try { - if (response.getDisallowCall()) { - mCallScreeningAdapter.disallowCall( - callDetails.getTelecomCallId(), - response.getRejectCall(), - !response.getSkipCallLog(), - !response.getSkipNotification(), - new ComponentName(getPackageName(), getClass().getName())); - } else if (response.getSilenceCall()) { - mCallScreeningAdapter.silenceCall(callDetails.getTelecomCallId()); - } else if (response.getShouldScreenCallViaAudioProcessing()) { - mCallScreeningAdapter.screenCallFurther(callDetails.getTelecomCallId()); - } else { - mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId()); - } + mCallScreeningAdapter.onScreeningResponse( + callDetails.getTelecomCallId(), + new ComponentName(getPackageName(), getClass().getName()), + response.toParcelable()); } catch (RemoteException e) { + Log.e(this, e, "Got remote exception when returning response"); } } } diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 089a948b6e55..942a54eb98ba 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -3390,11 +3390,20 @@ public abstract class Connection extends Conferenceable { * {@code true}, {@link #onDisconnect()} will be called soon after * this is called. * @param isInContacts Indicates whether the caller is in the user's contacts list. + * @param callScreeningResponse The response that was returned from the + * {@link CallScreeningService} that handled this call. If no + * response was received from a call screening service, + * this will be {@code null}. + * @param isResponseFromSystemDialer Whether {@code callScreeningResponse} was sent from the + * system dialer. If {@code callScreeningResponse} is + * {@code null}, this will be {@code false}. * @hide */ @SystemApi @RequiresPermission(Manifest.permission.READ_CONTACTS) - public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) { } + public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts, + @Nullable CallScreeningService.CallResponse callScreeningResponse, + boolean isResponseFromSystemDialer) { } static String toLogSafePhoneNumber(String number) { // For unknown number, log empty string. diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 170ed3eff614..966ece3a3ba2 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -759,6 +759,8 @@ public abstract class ConnectionService extends Service { @Override public void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts, + CallScreeningService.ParcelableCallResponse callScreeningResponse, + boolean isResponseFromSystemDialer, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_CALL_FILTERING_COMPLETED); try { @@ -766,7 +768,9 @@ public abstract class ConnectionService extends Service { args.arg1 = callId; args.arg2 = isBlocked; args.arg3 = isInContacts; - args.arg4 = Log.createSubsession(); + args.arg4 = callScreeningResponse; + args.arg5 = isResponseFromSystemDialer; + args.arg6 = Log.createSubsession(); mHandler.obtainMessage(MSG_ON_CALL_FILTERING_COMPLETED, args).sendToTarget(); } finally { Log.endSession(); @@ -1437,12 +1441,16 @@ public abstract class ConnectionService extends Service { case MSG_ON_CALL_FILTERING_COMPLETED: { SomeArgs args = (SomeArgs) msg.obj; try { - Log.continueSession((Session) args.arg4, + Log.continueSession((Session) args.arg6, SESSION_HANDLER + SESSION_CALL_FILTERING_COMPLETED); String callId = (String) args.arg1; boolean isBlocked = (boolean) args.arg2; boolean isInContacts = (boolean) args.arg3; - onCallFilteringCompleted(callId, isBlocked, isInContacts); + CallScreeningService.ParcelableCallResponse callScreeningResponse = + (CallScreeningService.ParcelableCallResponse) args.arg4; + boolean isResponseFromSystemDialer = (boolean) args.arg5; + onCallFilteringCompleted(callId, isBlocked, isInContacts, + callScreeningResponse, isResponseFromSystemDialer); } finally { args.recycle(); Log.endSession(); @@ -2458,11 +2466,16 @@ public abstract class ConnectionService extends Service { } } - private void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts) { - Log.i(this, "onCallFilteringCompleted(%b, %b)", isBlocked, isInContacts); + private void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts, + CallScreeningService.ParcelableCallResponse callScreeningResponse, + boolean isResponseFromSystemDialer) { + Log.i(this, "onCallFilteringCompleted(%s, %b, %b, %s, %b)", callId, + isBlocked, isInContacts, callScreeningResponse, isResponseFromSystemDialer); Connection connection = findConnectionForAction(callId, "onCallFilteringCompleted"); if (connection != null) { - connection.onCallFilteringCompleted(isBlocked, isInContacts); + connection.onCallFilteringCompleted(isBlocked, isInContacts, + callScreeningResponse == null ? null : callScreeningResponse.toCallResponse(), + isResponseFromSystemDialer); } } diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java index feb2ca53bbbe..6c6097ac71e5 100644 --- a/telecomm/java/android/telecom/RemoteConnection.java +++ b/telecomm/java/android/telecom/RemoteConnection.java @@ -1204,15 +1204,25 @@ public final class RemoteConnection { * the results of a contacts lookup for the remote party. * @param isBlocked Whether call filtering indicates that the call should be blocked * @param isInContacts Whether the remote party is in the user's contacts + * @param callScreeningResponse The response that was returned from the + * {@link CallScreeningService} that handled this call. If no + * response was received from a call screening service, + * this will be {@code null}. + * @param isResponseFromSystemDialer Whether {@code callScreeningResponse} was sent from the + * system dialer. If {@code callScreeningResponse} is + * {@code null}, this will be {@code false}. * @hide */ @SystemApi @RequiresPermission(Manifest.permission.READ_CONTACTS) - public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) { + public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts, + @Nullable CallScreeningService.CallResponse callScreeningResponse, + boolean isResponseFromSystemDialer) { Log.startSession("RC.oCFC", getActiveOwnerInfo()); try { if (mConnected) { mConnectionService.onCallFilteringCompleted(mConnectionId, isBlocked, isInContacts, + callScreeningResponse.toParcelable(), isResponseFromSystemDialer, null /*Session.Info*/); } } catch (RemoteException ignored) { diff --git a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl index 83c8f62bb3db..0f2e178f1970 100644 --- a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl @@ -17,6 +17,7 @@ package com.android.internal.telecom; import android.content.ComponentName; +import android.telecom.CallScreeningService; /** * Internal remote callback interface for call screening services. @@ -26,16 +27,6 @@ import android.content.ComponentName; * {@hide} */ oneway interface ICallScreeningAdapter { - void allowCall(String callId); - - void silenceCall(String callId); - - void screenCallFurther(String callId); - - void disallowCall( - String callId, - boolean shouldReject, - boolean shouldAddToCallLog, - boolean shouldShowNotification, - in ComponentName componentName); + void onScreeningResponse(String callId, in ComponentName componentName, + in CallScreeningService.ParcelableCallResponse response); } diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl index 301c2bb6cdb2..7599e189cc37 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl @@ -20,6 +20,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.telecom.CallAudioState; +import android.telecom.CallScreeningService; import android.telecom.ConnectionRequest; import android.telecom.Logging.Session; import android.telecom.PhoneAccountHandle; @@ -119,7 +120,8 @@ oneway interface IConnectionService { void sendCallEvent(String callId, String event, in Bundle extras, in Session.Info sessionInfo); void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts, - in Session.Info sessionInfo); + in CallScreeningService.ParcelableCallResponse callScreeningResponse, + boolean isResponseFromSystemDialer, in Session.Info sessionInfo); void onExtrasChanged(String callId, in Bundle extras, in Session.Info sessionInfo); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 3f6162d6422b..6d3fa81a856d 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -4601,8 +4601,9 @@ public class CarrierConfigManager { "mmi_two_digit_number_pattern_string_array"; /** - * Holds the list of carrier certificate hashes. - * Note that each carrier has its own certificates. + * Holds the list of carrier certificate hashes, followed by optional package names. + * Format: "sha1/256" or "sha1/256:package1,package2,package3..." + * Note that each carrier has its own hashes. */ public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY = "carrier_certificate_string_array"; @@ -4763,6 +4764,14 @@ public class CarrierConfigManager { public static final String KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL = "store_sim_pin_for_unattended_reboot_bool"; + /** + * Determine whether "Enable 2G" toggle can be shown. + * + * Used to trade privacy/security against potentially reduced carrier coverage for some + * carriers. + */ + public static final String KEY_HIDE_ENABLE_2G = "hide_enable_2g_bool"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -5320,6 +5329,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL, true); sDefaults.putInt(KEY_DEFAULT_RTT_MODE_INT, 0); sDefaults.putBoolean(KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL, true); + sDefaults.putBoolean(KEY_HIDE_ENABLE_2G, false); } /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index ee3a0ef5f4fe..6a9b71d78dd9 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -4461,6 +4461,10 @@ public class TelephonyManager { * This functionality is only available to the app filling the {@link RoleManager#ROLE_DIALER} * role on the device. * + * This functionality is only available when + * {@link CarrierConfigManager#KEY_SUPPORTS_CALL_COMPOSER_BOOL} is set to {@code true} in the + * bundle returned from {@link #getCarrierConfig()}. + * * @param pictureToUpload An {@link InputStream} that supplies the bytes representing the * picture to upload. The client bears responsibility for closing this * stream after {@code callback} is called with success or failure. @@ -4469,7 +4473,9 @@ public class TelephonyManager { * of {@link #getMaximumCallComposerPictureSize()}, the upload will be * aborted and the callback will be called with an exception containing * {@link CallComposerException#ERROR_FILE_TOO_LARGE}. - * @param contentType The MIME type of the picture you're uploading (e.g. image/jpeg) + * @param contentType The MIME type of the picture you're uploading (e.g. image/jpeg). The list + * of acceptable content types can be found at 3GPP TS 26.141 sections + * 4.2 and 4.3. * @param executor The {@link Executor} on which the {@code pictureToUpload} stream will be * read, as well as on which the callback will be called. * @param callback A callback called when the upload operation terminates, either in success @@ -8591,7 +8597,8 @@ public class TelephonyManager { @IntDef({ ALLOWED_NETWORK_TYPES_REASON_USER, ALLOWED_NETWORK_TYPES_REASON_POWER, - ALLOWED_NETWORK_TYPES_REASON_CARRIER + ALLOWED_NETWORK_TYPES_REASON_CARRIER, + ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G }) @Retention(RetentionPolicy.SOURCE) public @interface AllowedNetworkTypesReason { @@ -8628,6 +8635,14 @@ public class TelephonyManager { public static final int ALLOWED_NETWORK_TYPES_REASON_CARRIER = 2; /** + * To indicate allowed network type change is requested by the user via the 2G toggle. + * + * @hide + */ + @SystemApi + public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3; + + /** * Set the allowed network types of the device and * provide the reason triggering the allowed network change. * This can be called for following reasons @@ -8636,6 +8651,8 @@ public class TelephonyManager { * <li>Allowed network types control by power manager * {@link #ALLOWED_NETWORK_TYPES_REASON_POWER} * <li>Allowed network types control by carrier {@link #ALLOWED_NETWORK_TYPES_REASON_CARRIER} + * <li>Allowed network types control by the user-controlled "Allow 2G" toggle + * {@link #ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G} * </ol> * This API will result in allowing an intersection of allowed network types for all reasons, * including the configuration done through other reasons. @@ -8719,6 +8736,7 @@ public class TelephonyManager { case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER: case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER: case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER: + case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G: return true; } return false; diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java index 7773c0aef252..38b551bff9a7 100644 --- a/telephony/java/android/telephony/UiccAccessRule.java +++ b/telephony/java/android/telephony/UiccAccessRule.java @@ -35,6 +35,7 @@ import java.io.DataOutputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -52,6 +53,16 @@ public final class UiccAccessRule implements Parcelable { private static final int ENCODING_VERSION = 1; + /** + * Delimiter used to decode {@link CarrierConfigManager#KEY_CARRIER_CERTIFICATE_STRING_ARRAY}. + */ + private static final String DELIMITER_CERTIFICATE_HASH_PACKAGE_NAMES = ":"; + + /** + * Delimiter used to decode {@link CarrierConfigManager#KEY_CARRIER_CERTIFICATE_STRING_ARRAY}. + */ + private static final String DELIMITER_INDIVIDUAL_PACKAGE_NAMES = ","; + public static final @android.annotation.NonNull Creator<UiccAccessRule> CREATOR = new Creator<UiccAccessRule>() { @Override public UiccAccessRule createFromParcel(Parcel in) { @@ -98,6 +109,36 @@ public final class UiccAccessRule implements Parcelable { } /** + * Decodes {@link CarrierConfigManager#KEY_CARRIER_CERTIFICATE_STRING_ARRAY} values. + * @hide + */ + @Nullable + public static UiccAccessRule[] decodeRulesFromCarrierConfig(@Nullable String[] certs) { + if (certs == null) { + return null; + } + List<UiccAccessRule> carrierConfigAccessRulesArray = new ArrayList(); + for (String cert : certs) { + String[] splitStr = cert.split(DELIMITER_CERTIFICATE_HASH_PACKAGE_NAMES); + byte[] certificateHash = IccUtils.hexStringToBytes(splitStr[0]); + if (splitStr.length == 1) { + // The value is a certificate hash, without any package name + carrierConfigAccessRulesArray.add(new UiccAccessRule(certificateHash, null, 0)); + } else { + // The value is composed of the certificate hash followed by at least one + // package name + String[] packageNames = splitStr[1].split(DELIMITER_INDIVIDUAL_PACKAGE_NAMES); + for (String packageName : packageNames) { + carrierConfigAccessRulesArray.add( + new UiccAccessRule(certificateHash, packageName, 0)); + } + } + } + return carrierConfigAccessRulesArray.toArray( + new UiccAccessRule[carrierConfigAccessRulesArray.size()]); + } + + /** * Decodes a byte array generated with {@link #encodeRules}. * @hide */ @@ -222,6 +263,14 @@ public final class UiccAccessRule implements Parcelable { return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; } + /** + * Returns true if the given certificate and package name match this rule's values. + * @hide + */ + public boolean matches(@Nullable String certHash, @Nullable String packageName) { + return matches(IccUtils.hexStringToBytes(certHash), packageName); + } + private boolean matches(byte[] certHash, String packageName) { return certHash != null && Arrays.equals(this.mCertificateHash, certHash) && (TextUtils.isEmpty(this.mPackageName) || this.mPackageName.equals(packageName)); diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java index 070fd799d6cc..09c07d3f203c 100644 --- a/telephony/java/android/telephony/ims/RcsUceAdapter.java +++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java @@ -32,6 +32,7 @@ import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsRcsController; import android.telephony.ims.aidl.IRcsUceControllerCallback; import android.telephony.ims.aidl.IRcsUcePublishStateCallback; +import android.telephony.ims.feature.RcsFeature; import android.util.Log; import java.lang.annotation.Retention; @@ -467,7 +468,7 @@ public class RcsUceAdapter { * poll on the network unless there are contacts being queried with stale information. * <p> * Be sure to check the availability of this feature using - * {@link ImsRcsManager#isAvailable(int)} and ensuring + * {@link ImsRcsManager#isAvailable(int, int)} and ensuring * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is enabled or else * this operation will fail with {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}. @@ -484,7 +485,8 @@ public class RcsUceAdapter { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, + Manifest.permission.READ_CONTACTS}) public void requestCapabilities(@NonNull List<Uri> contactNumbers, @NonNull @CallbackExecutor Executor executor, @NonNull CapabilitiesCallback c) throws ImsException { @@ -557,7 +559,7 @@ public class RcsUceAdapter { * * <p> * Be sure to check the availability of this feature using - * {@link ImsRcsManager#isAvailable(int)} and ensuring + * {@link ImsRcsManager#isAvailable(int, int)} and ensuring * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is * enabled or else this operation will fail with @@ -571,7 +573,8 @@ public class RcsUceAdapter { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + @RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, + Manifest.permission.READ_CONTACTS}) public void requestAvailability(@NonNull Uri contactNumber, @NonNull @CallbackExecutor Executor executor, @NonNull CapabilitiesCallback c) throws ImsException { diff --git a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java index 4435640e008c..a217d1321342 100644 --- a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java +++ b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.net.Uri; import android.os.Binder; import android.os.RemoteException; +import android.telephony.ims.ImsException; import android.telephony.ims.RcsContactUceCapability; import android.telephony.ims.stub.CapabilityExchangeEventListener; import android.util.Log; @@ -47,7 +48,7 @@ public class CapabilityExchangeAidlWrapper implements CapabilityExchangeEventLis * Receives the request of publishing capabilities from the network and deliver this request * to the framework via the registered capability exchange event listener. */ - public void onRequestPublishCapabilities(int publishTriggerType) { + public void onRequestPublishCapabilities(int publishTriggerType) throws ImsException { ICapabilityExchangeEventListener listener = mListenerBinder; if (listener == null) { return; @@ -56,13 +57,15 @@ public class CapabilityExchangeAidlWrapper implements CapabilityExchangeEventLis listener.onRequestPublishCapabilities(publishTriggerType); } catch (RemoteException e) { Log.w(LOG_TAG, "request publish capabilities exception: " + e); + throw new ImsException("Remote is not available", + ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } /** * Receives the unpublish notification and deliver this callback to the framework. */ - public void onUnpublish() { + public void onUnpublish() throws ImsException { ICapabilityExchangeEventListener listener = mListenerBinder; if (listener == null) { return; @@ -71,6 +74,8 @@ public class CapabilityExchangeAidlWrapper implements CapabilityExchangeEventLis listener.onUnpublish(); } catch (RemoteException e) { Log.w(LOG_TAG, "Unpublish exception: " + e); + throw new ImsException("Remote is not available", + ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -79,7 +84,8 @@ public class CapabilityExchangeAidlWrapper implements CapabilityExchangeEventLis * request to the framework. */ public void onRemoteCapabilityRequest(@NonNull Uri contactUri, - @NonNull List<String> remoteCapabilities, @NonNull OptionsRequestCallback callback) { + @NonNull List<String> remoteCapabilities, @NonNull OptionsRequestCallback callback) + throws ImsException { ICapabilityExchangeEventListener listener = mListenerBinder; if (listener == null) { return; @@ -87,10 +93,11 @@ public class CapabilityExchangeAidlWrapper implements CapabilityExchangeEventLis IOptionsRequestCallback internalCallback = new IOptionsRequestCallback.Stub() { @Override - public void respondToCapabilityRequest(RcsContactUceCapability ownCapabilities) { + public void respondToCapabilityRequest(RcsContactUceCapability ownCapabilities, + boolean isBlocked) { final long callingIdentity = Binder.clearCallingIdentity(); try { - callback.onRespondToCapabilityRequest(ownCapabilities); + callback.onRespondToCapabilityRequest(ownCapabilities, isBlocked); } finally { restoreCallingIdentity(callingIdentity); } @@ -110,6 +117,8 @@ public class CapabilityExchangeAidlWrapper implements CapabilityExchangeEventLis listener.onRemoteCapabilityRequest(contactUri, remoteCapabilities, internalCallback); } catch (RemoteException e) { Log.w(LOG_TAG, "Remote capability request exception: " + e); + throw new ImsException("Remote is not available", + ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } } diff --git a/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl b/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl index d4d5301f38fa..8eecbca7e6a7 100644 --- a/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl +++ b/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl @@ -27,8 +27,9 @@ oneway interface IOptionsRequestCallback { * Respond to a remote capability request from the contact specified with the capabilities * of this device. * @param ownCapabilities The capabilities of this device. + * @param isBlocked True if the user has blocked the number sending this request. */ - void respondToCapabilityRequest(in RcsContactUceCapability ownCapabilities); + void respondToCapabilityRequest(in RcsContactUceCapability ownCapabilities, boolean isBlocked); /** * Respond to a remote capability request from the contact specified with the diff --git a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java index d9734a7475c0..4967e5da7c9a 100644 --- a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java +++ b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java @@ -16,32 +16,58 @@ package android.telephony.ims.stub; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.net.Uri; import android.telephony.ims.ImsException; import android.telephony.ims.RcsContactUceCapability; import android.telephony.ims.RcsUceAdapter; import android.telephony.ims.feature.ImsFeature; import android.telephony.ims.feature.RcsFeature; +import android.util.Log; + +import java.util.List; /** - * The interface of the capabilities event listener for ImsService to notify the framework of the - * UCE request and status updated. + * The interface that is used by the framework to listen to events from the vendor RCS stack + * regarding capabilities exchange using presence server and OPTIONS. * @hide */ @SystemApi public interface CapabilityExchangeEventListener { /** * Interface used by the framework to respond to OPTIONS requests. - * @hide */ interface OptionsRequestCallback { /** * Respond to a remote capability request from the contact specified with the * capabilities of this device. * @param ownCapabilities The capabilities of this device. + * @hide */ - void onRespondToCapabilityRequest(@NonNull RcsContactUceCapability ownCapabilities); + default void onRespondToCapabilityRequest( + @NonNull RcsContactUceCapability ownCapabilities) {} + + /** + * Respond to a remote capability request from the contact specified with the + * capabilities of this device. + * @param ownCapabilities The capabilities of this device. + * @param isBlocked Whether or not the user has blocked the number requesting the + * capabilities of this device. If true, the device should respond to the OPTIONS + * request with a 200 OK response and no capabilities. + */ + default void onRespondToCapabilityRequest(@NonNull RcsContactUceCapability ownCapabilities, + boolean isBlocked) { + Log.w("CapabilityExchangeEventListener", "implement " + + "onRespondToCapabilityRequest(RcsContactUceCapability, boolean) instead!"); + // Fall back to old implementation + if (isBlocked) { + onRespondToCapabilityRequestWithError(200, "OK"); + } else { + onRespondToCapabilityRequest(ownCapabilities); + } + } /** * Respond to a remote capability request from the contact specified with the @@ -49,7 +75,8 @@ public interface CapabilityExchangeEventListener { * @param code The SIP response code to respond with. * @param reason A non-null String containing the reason associated with the SIP code. */ - void onRespondToCapabilityRequestWithError(int code, @NonNull String reason); + void onRespondToCapabilityRequestWithError(@IntRange(from = 100, to = 699) int code, + @NonNull String reason); } /** @@ -59,8 +86,7 @@ public interface CapabilityExchangeEventListener { * This is typically used when trying to generate an initial PUBLISH for a new subscription to * the network. The device will cache all presence publications after boot until this method is * called the first time. - * @param publishTriggerType {@link RcsUceAdapter#StackPublishTriggerType} The reason for the - * capability update request. + * @param publishTriggerType The reason for the capability update request. * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not currently * connected to the framework. This can happen if the {@link RcsFeature} is not * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the @@ -81,4 +107,25 @@ public interface CapabilityExchangeEventListener { * Telephony stack has crashed. */ void onUnpublish() throws ImsException; + + /** + * Inform the framework of an OPTIONS query from a remote device for this device's UCE + * capabilities. + * <p> + * The framework will respond via the + * {@link OptionsRequestCallback#onRespondToCapabilityRequest} or + * {@link OptionsRequestCallback#onRespondToCapabilityRequestWithError}. + * @param contactUri The URI associated with the remote contact that is + * requesting capabilities. + * @param remoteCapabilities The remote contact's capability information. + * @param callback The callback of this request which is sent from the remote user. + * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not + * currently connected to the framework. This can happen if the {@link RcsFeature} is not + * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received + * the {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare + * cases when the Telephony stack has crashed. + */ + void onRemoteCapabilityRequest(@NonNull Uri contactUri, + @NonNull List<String> remoteCapabilities, + @NonNull OptionsRequestCallback callback) throws ImsException; } diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java index ec98be6e5062..908869beb607 100644 --- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java +++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java @@ -19,7 +19,6 @@ package android.telephony.ims.stub; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.net.Uri; @@ -141,7 +140,7 @@ public class RcsCapabilityExchangeImplBase { * {@link #publishCapabilities(String, PublishResponseCallback)}. * * If this network response also contains a “Reason” header, then the - * {@link onNetworkResponse(int, String, int, String)} method should be used instead. + * {@link #onNetworkResponse(int, String, int, String)} method should be used instead. * * @param sipCode The SIP response code sent from the network for the operation * token specified. @@ -160,7 +159,7 @@ public class RcsCapabilityExchangeImplBase { /** * Provide the framework with a subsequent network response update to - * {@link #publishCapabilities(RcsContactUceCapability, int)} that also + * {@link #publishCapabilities(String, PublishResponseCallback)} that also * includes a reason provided in the “reason” header. See RFC3326 for more * information. * @@ -186,7 +185,6 @@ public class RcsCapabilityExchangeImplBase { /** * Interface used by the framework to respond to OPTIONS requests. - * @hide */ public interface OptionsResponseCallback { /** @@ -217,7 +215,7 @@ public class RcsCapabilityExchangeImplBase { * cases when the Telephony stack has crashed. */ void onNetworkResponse(int sipCode, @NonNull String reason, - @Nullable List<String> theirCaps) throws ImsException; + @NonNull List<String> theirCaps) throws ImsException; } /** @@ -243,7 +241,7 @@ public class RcsCapabilityExchangeImplBase { /** * Notify the framework of the response to the SUBSCRIBE request from - * {@link #subscribeForCapabilities(List<Uri>, SubscribeResponseCallback)}. + * {@link #subscribeForCapabilities(List, SubscribeResponseCallback)}. * <p> * If the carrier network responds to the SUBSCRIBE request with a 2XX response, then the * framework will expect the IMS stack to call {@link #onNotifyCapabilitiesUpdate}, @@ -251,7 +249,7 @@ public class RcsCapabilityExchangeImplBase { * subsequent NOTIFY responses to the subscription. * * If this network response also contains a “Reason” header, then the - * {@link onNetworkResponse(int, String, int, String)} method should be used instead. + * {@link #onNetworkResponse(int, String, int, String)} method should be used instead. * * @param sipCode The SIP response code sent from the network for the operation * token specified. @@ -268,7 +266,7 @@ public class RcsCapabilityExchangeImplBase { /** * Notify the framework of the response to the SUBSCRIBE request from - * {@link #subscribeForCapabilities(RcsContactUceCapability, int)} that also + * {@link #subscribeForCapabilities(List, SubscribeResponseCallback)} that also * includes a reason provided in the “reason” header. See RFC3326 for more * information. * @@ -294,7 +292,8 @@ public class RcsCapabilityExchangeImplBase { /** * Notify the framework of the latest XML PIDF documents included in the network response * for the requested contacts' capabilities requested by the Framework using - * {@link RcsUceAdapter#requestCapabilities(Executor, List<Uri>, CapabilitiesCallback)}. + * {@link RcsUceAdapter#requestCapabilities(List, Executor, + * RcsUceAdapter.CapabilitiesCallback)}. * <p> * The expected format for the PIDF XML is defined in RFC3861. Each XML document must be a * "application/pidf+xml" object and start with a root <presence> element. For NOTIFY @@ -336,7 +335,8 @@ public class RcsCapabilityExchangeImplBase { /** * The subscription associated with a previous - * {@link RcsUceAdapter#requestCapabilities(Executor, List<Uri>, CapabilitiesCallback)} + * {@link RcsUceAdapter#requestCapabilities(List, Executor, + * RcsUceAdapter.CapabilitiesCallback)} * operation has been terminated. This will mostly be due to the network sending a final * NOTIFY response due to the subscription expiring, but this may also happen due to a * network error. @@ -427,12 +427,11 @@ public class RcsCapabilityExchangeImplBase { * Push one's own capabilities to a remote user via the SIP OPTIONS presence exchange mechanism * in order to receive the capabilities of the remote user in response. * <p> - * The implementer must call {@link #onNetworkResponse} to send the response of this - * query back to the framework. + * The implementer must use {@link OptionsResponseCallback} to send the response of + * this query from the network back to the framework. * @param contactUri The URI of the remote user that we wish to get the capabilities of. * @param myCapabilities The capabilities of this device to send to the remote user. * @param callback The callback of this request which is sent from the remote user. - * @hide */ // executor used is defined in the constructor. @SuppressLint("ExecutorRegistration") diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java index 6fbde503c3a0..15d19a49ee56 100644 --- a/telephony/java/com/android/internal/telephony/DctConstants.java +++ b/telephony/java/com/android/internal/telephony/DctConstants.java @@ -125,6 +125,7 @@ public class DctConstants { public static final String PROVISIONING_URL_KEY = "provisioningUrl"; public static final String BANDWIDTH_SOURCE_MODEM_KEY = "modem"; public static final String BANDWIDTH_SOURCE_CARRIER_CONFIG_KEY = "carrier_config"; + public static final String BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR_KEY = "bandwidth_estimator"; public static final String RAT_NAME_LTE = "LTE"; public static final String RAT_NAME_NR_NSA = "NR_NSA"; public static final String RAT_NAME_NR_NSA_MMWAVE = "NR_NSA_MMWAVE"; diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 2a693eb94015..13584109b093 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -66,6 +66,8 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; 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_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; +import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE; import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS; import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL; @@ -169,6 +171,7 @@ import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; import android.net.INetworkStatsService; +import android.net.IOnSetOemNetworkPreferenceListener; import android.net.IQosCallback; import android.net.InetAddresses; import android.net.InterfaceConfigurationParcel; @@ -192,6 +195,7 @@ import android.net.NetworkStack; import android.net.NetworkStackClient; import android.net.NetworkState; import android.net.NetworkTestResultParcelable; +import android.net.OemNetworkPreferences; import android.net.ProxyInfo; import android.net.QosCallbackException; import android.net.QosFilter; @@ -300,6 +304,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -359,6 +364,7 @@ public class ConnectivityServiceTest { private static final String WIFI_WOL_IFNAME = "test_wlan_wol"; private static final String VPN_IFNAME = "tun10042"; private static final String TEST_PACKAGE_NAME = "com.android.test.package"; + private static final int TEST_PACKAGE_UID = 123; private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn"; private static final String INTERFACE_NAME = "interface"; @@ -418,6 +424,7 @@ public class ConnectivityServiceTest { @Mock EthernetManager mEthernetManager; @Mock NetworkPolicyManager mNetworkPolicyManager; @Mock KeyStore mKeyStore; + @Mock IOnSetOemNetworkPreferenceListener mOnSetOemNetworkPreferenceListener; private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor = ArgumentCaptor.forClass(ResolverParamsParcel.class); @@ -9422,4 +9429,264 @@ public class ConnectivityServiceTest { } fail("TOO_MANY_REQUESTS never thrown"); } + + private void mockGetApplicationInfo(@NonNull final String packageName, @NonNull final int uid) + throws PackageManager.NameNotFoundException { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.uid = uid; + when(mPackageManager.getApplicationInfo(eq(packageName), anyInt())) + .thenReturn(applicationInfo); + } + + private void mockHasSystemFeature(@NonNull final String featureName, + @NonNull final boolean hasFeature) { + when(mPackageManager.hasSystemFeature(eq(featureName))) + .thenReturn(hasFeature); + } + + private UidRange getNriFirstUidRange( + @NonNull final ConnectivityService.NetworkRequestInfo nri) { + return nri.mRequests.get(0).networkCapabilities.getUids().iterator().next(); + } + + private OemNetworkPreferences createDefaultOemNetworkPreferences( + @OemNetworkPreferences.OemNetworkPreference final int preference) + throws PackageManager.NameNotFoundException { + // Arrange PackageManager mocks + mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); + + // Build OemNetworkPreferences object + return new OemNetworkPreferences.Builder() + .addNetworkPreference(TEST_PACKAGE_NAME, preference) + .build(); + } + + @Test + public void testOemNetworkRequestFactoryPreferenceUninitializedThrowsError() + throws PackageManager.NameNotFoundException { + @OemNetworkPreferences.OemNetworkPreference final int prefToTest = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED; + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + assertThrows(IllegalArgumentException.class, + () -> mService.new OemNetworkRequestFactory() + .createNrisFromOemNetworkPreferences( + createDefaultOemNetworkPreferences(prefToTest))); + } + + @Test + public void testOemNetworkRequestFactoryPreferenceOemPaid() + throws PackageManager.NameNotFoundException { + // Expectations + final int expectedNumOfNris = 1; + final int expectedNumOfRequests = 3; + + @OemNetworkPreferences.OemNetworkPreference final int prefToTest = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final ArraySet<ConnectivityService.NetworkRequestInfo> nris = + mService.new OemNetworkRequestFactory() + .createNrisFromOemNetworkPreferences( + createDefaultOemNetworkPreferences(prefToTest)); + + final List<NetworkRequest> mRequests = nris.iterator().next().mRequests; + assertEquals(expectedNumOfNris, nris.size()); + assertEquals(expectedNumOfRequests, mRequests.size()); + assertTrue(mRequests.get(0).isListen()); + assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED)); + assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED)); + assertTrue(mRequests.get(1).isRequest()); + assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID)); + assertTrue(mRequests.get(2).isRequest()); + assertTrue(mService.getDefaultRequest().networkCapabilities.equalsNetCapabilities( + mRequests.get(2).networkCapabilities)); + } + + @Test + public void testOemNetworkRequestFactoryPreferenceOemPaidNoFallback() + throws PackageManager.NameNotFoundException { + // Expectations + final int expectedNumOfNris = 1; + final int expectedNumOfRequests = 2; + + @OemNetworkPreferences.OemNetworkPreference final int prefToTest = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final ArraySet<ConnectivityService.NetworkRequestInfo> nris = + mService.new OemNetworkRequestFactory() + .createNrisFromOemNetworkPreferences( + createDefaultOemNetworkPreferences(prefToTest)); + + final List<NetworkRequest> mRequests = nris.iterator().next().mRequests; + assertEquals(expectedNumOfNris, nris.size()); + assertEquals(expectedNumOfRequests, mRequests.size()); + assertTrue(mRequests.get(0).isListen()); + assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED)); + assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED)); + assertTrue(mRequests.get(1).isRequest()); + assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID)); + } + + @Test + public void testOemNetworkRequestFactoryPreferenceOemPaidOnly() + throws PackageManager.NameNotFoundException { + // Expectations + final int expectedNumOfNris = 1; + final int expectedNumOfRequests = 1; + + @OemNetworkPreferences.OemNetworkPreference final int prefToTest = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final ArraySet<ConnectivityService.NetworkRequestInfo> nris = + mService.new OemNetworkRequestFactory() + .createNrisFromOemNetworkPreferences( + createDefaultOemNetworkPreferences(prefToTest)); + + final List<NetworkRequest> mRequests = nris.iterator().next().mRequests; + assertEquals(expectedNumOfNris, nris.size()); + assertEquals(expectedNumOfRequests, mRequests.size()); + assertTrue(mRequests.get(0).isRequest()); + assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID)); + } + + @Test + public void testOemNetworkRequestFactoryPreferenceOemPrivateOnly() + throws PackageManager.NameNotFoundException { + // Expectations + final int expectedNumOfNris = 1; + final int expectedNumOfRequests = 1; + + @OemNetworkPreferences.OemNetworkPreference final int prefToTest = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final ArraySet<ConnectivityService.NetworkRequestInfo> nris = + mService.new OemNetworkRequestFactory() + .createNrisFromOemNetworkPreferences( + createDefaultOemNetworkPreferences(prefToTest)); + + final List<NetworkRequest> mRequests = nris.iterator().next().mRequests; + assertEquals(expectedNumOfNris, nris.size()); + assertEquals(expectedNumOfRequests, mRequests.size()); + assertTrue(mRequests.get(0).isRequest()); + assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PRIVATE)); + assertFalse(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID)); + } + + @Test + public void testOemNetworkRequestFactoryCreatesCorrectNumOfNris() + throws PackageManager.NameNotFoundException { + // Expectations + final int expectedNumOfNris = 2; + + // Arrange PackageManager mocks + final String testPackageName2 = "com.google.apps.dialer"; + mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); + mockGetApplicationInfo(testPackageName2, TEST_PACKAGE_UID); + + // Build OemNetworkPreferences object + final int testOemPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; + final int testOemPref2 = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; + final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() + .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref) + .addNetworkPreference(testPackageName2, testOemPref2) + .build(); + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final ArraySet<ConnectivityService.NetworkRequestInfo> nris = + mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref); + + assertNotNull(nris); + assertEquals(expectedNumOfNris, nris.size()); + } + + @Test + public void testOemNetworkRequestFactoryCorrectlySetsUids() + throws PackageManager.NameNotFoundException { + // Arrange PackageManager mocks + final String testPackageName2 = "com.google.apps.dialer"; + final int testPackageNameUid2 = 456; + mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); + mockGetApplicationInfo(testPackageName2, testPackageNameUid2); + + // Build OemNetworkPreferences object + final int testOemPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; + final int testOemPref2 = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; + final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() + .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref) + .addNetworkPreference(testPackageName2, testOemPref2) + .build(); + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final List<ConnectivityService.NetworkRequestInfo> nris = + new ArrayList<>( + mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences( + pref)); + + // Sort by uid to access nris by index + nris.sort(Comparator.comparingInt(nri -> getNriFirstUidRange(nri).start)); + assertEquals(TEST_PACKAGE_UID, getNriFirstUidRange(nris.get(0)).start); + assertEquals(TEST_PACKAGE_UID, getNriFirstUidRange(nris.get(0)).stop); + assertEquals(testPackageNameUid2, getNriFirstUidRange(nris.get(1)).start); + assertEquals(testPackageNameUid2, getNriFirstUidRange(nris.get(1)).stop); + } + + @Test + public void testOemNetworkRequestFactoryAddsPackagesToCorrectPreference() + throws PackageManager.NameNotFoundException { + // Expectations + final int expectedNumOfNris = 1; + final int expectedNumOfAppUids = 2; + + // Arrange PackageManager mocks + final String testPackageName2 = "com.google.apps.dialer"; + final int testPackageNameUid2 = 456; + mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); + mockGetApplicationInfo(testPackageName2, testPackageNameUid2); + + // Build OemNetworkPreferences object + final int testOemPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; + final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() + .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref) + .addNetworkPreference(testPackageName2, testOemPref) + .build(); + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final ArraySet<ConnectivityService.NetworkRequestInfo> nris = + mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref); + + assertEquals(expectedNumOfNris, nris.size()); + assertEquals(expectedNumOfAppUids, + nris.iterator().next().mRequests.get(0).networkCapabilities.getUids().size()); + } + + @Test + public void testSetOemNetworkPreferenceNullListenerAndPrefParamThrowsNpe() { + mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true); + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; + + // Act on ConnectivityService.setOemNetworkPreference() + assertThrows(NullPointerException.class, + () -> mService.setOemNetworkPreference( + null, + null)); + } + + @Test + public void testSetOemNetworkPreferenceFailsForNonAutomotive() + throws PackageManager.NameNotFoundException, RemoteException { + mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false); + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; + + // Act on ConnectivityService.setOemNetworkPreference() + assertThrows(UnsupportedOperationException.class, + () -> mService.setOemNetworkPreference( + createDefaultOemNetworkPreferences(networkPref), + mOnSetOemNetworkPreferenceListener)); + } } diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp index c04ddd78e69b..1dedc19f0c88 100644 --- a/tests/vcn/Android.bp +++ b/tests/vcn/Android.bp @@ -21,7 +21,6 @@ android_test { "services.core", ], libs: [ - "android.net.ipsec.ike.stubs.module_lib", "android.test.runner", "android.test.base", "android.test.mock", diff --git a/tests/vcn/java/android/net/vcn/VcnControlPlaneIkeConfigTest.java b/tests/vcn/java/android/net/vcn/VcnControlPlaneIkeConfigTest.java new file mode 100644 index 000000000000..36f5e41462e8 --- /dev/null +++ b/tests/vcn/java/android/net/vcn/VcnControlPlaneIkeConfigTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.vcn; + +import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP; +import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12; +import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.ipsec.ike.ChildSaProposal; +import android.net.ipsec.ike.IkeFqdnIdentification; +import android.net.ipsec.ike.IkeSaProposal; +import android.net.ipsec.ike.IkeSessionParams; +import android.net.ipsec.ike.SaProposal; +import android.net.ipsec.ike.TunnelModeChildSessionParams; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VcnControlPlaneIkeConfigTest { + private static final IkeSessionParams IKE_PARAMS; + private static final TunnelModeChildSessionParams CHILD_PARAMS; + + static { + IkeSaProposal ikeProposal = + new IkeSaProposal.Builder() + .addEncryptionAlgorithm( + ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128) + .addDhGroup(DH_GROUP_2048_BIT_MODP) + .addPseudorandomFunction(PSEUDORANDOM_FUNCTION_AES128_XCBC) + .build(); + + Context mockContext = mock(Context.class); + ConnectivityManager mockConnectManager = mock(ConnectivityManager.class); + doReturn(mockConnectManager) + .when(mockContext) + .getSystemService(Context.CONNECTIVITY_SERVICE); + doReturn(mock(Network.class)).when(mockConnectManager).getActiveNetwork(); + + final String serverHostname = "192.0.2.100"; + final String testLocalId = "test.client.com"; + final String testRemoteId = "test.server.com"; + final byte[] psk = "psk".getBytes(); + + IKE_PARAMS = + new IkeSessionParams.Builder(mockContext) + .setServerHostname(serverHostname) + .addSaProposal(ikeProposal) + .setLocalIdentification(new IkeFqdnIdentification(testLocalId)) + .setRemoteIdentification(new IkeFqdnIdentification(testRemoteId)) + .setAuthPsk(psk) + .build(); + + ChildSaProposal childProposal = + new ChildSaProposal.Builder() + .addEncryptionAlgorithm( + ENCRYPTION_ALGORITHM_AES_GCM_12, SaProposal.KEY_LEN_AES_128) + .build(); + CHILD_PARAMS = + new TunnelModeChildSessionParams.Builder().addSaProposal(childProposal).build(); + } + + // Package private for use in VcnGatewayConnectionConfigTest + static VcnControlPlaneIkeConfig buildTestConfig() { + return new VcnControlPlaneIkeConfig(IKE_PARAMS, CHILD_PARAMS); + } + + @Test + public void testGetters() { + final VcnControlPlaneIkeConfig config = buildTestConfig(); + assertEquals(IKE_PARAMS, config.getIkeSessionParams()); + assertEquals(CHILD_PARAMS, config.getChildSessionParams()); + } + + @Test + public void testConstructConfigWithoutIkeParams() { + try { + new VcnControlPlaneIkeConfig(null, CHILD_PARAMS); + fail("Expect to fail because ikeParams was null"); + } catch (NullPointerException expected) { + } + } + + @Test + public void testBuilderConfigWithoutChildParams() { + try { + new VcnControlPlaneIkeConfig(IKE_PARAMS, null); + fail("Expect to fail because childParams was null"); + } catch (NullPointerException expected) { + } + } +} diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java index 3e659d0bc128..5b17aadc50a6 100644 --- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java @@ -18,6 +18,7 @@ package android.net.vcn; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; import android.net.NetworkCapabilities; @@ -57,17 +58,22 @@ public class VcnGatewayConnectionConfigTest { }; public static final int MAX_MTU = 1360; + public static final VcnControlPlaneConfig CONTROL_PLANE_CONFIG = + VcnControlPlaneIkeConfigTest.buildTestConfig(); + // Public for use in VcnGatewayConnectionTest public static VcnGatewayConnectionConfig buildTestConfig() { return buildTestConfigWithExposedCaps(EXPOSED_CAPS); } + private static VcnGatewayConnectionConfig.Builder newBuilder() { + return new VcnGatewayConnectionConfig.Builder(CONTROL_PLANE_CONFIG); + } + // Public for use in VcnGatewayConnectionTest public static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(int... exposedCaps) { final VcnGatewayConnectionConfig.Builder builder = - new VcnGatewayConnectionConfig.Builder() - .setRetryInterval(RETRY_INTERVALS_MS) - .setMaxMtu(MAX_MTU); + newBuilder().setRetryInterval(RETRY_INTERVALS_MS).setMaxMtu(MAX_MTU); for (int caps : exposedCaps) { builder.addExposedCapability(caps); @@ -81,9 +87,19 @@ public class VcnGatewayConnectionConfigTest { } @Test + public void testBuilderRequiresNonNullControlPlaneConfig() { + try { + new VcnGatewayConnectionConfig.Builder(null).build(); + + fail("Expected exception due to invalid control plane config"); + } catch (NullPointerException e) { + } + } + + @Test public void testBuilderRequiresNonEmptyExposedCaps() { try { - new VcnGatewayConnectionConfig.Builder() + newBuilder() .addRequiredUnderlyingCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .build(); @@ -95,9 +111,7 @@ public class VcnGatewayConnectionConfigTest { @Test public void testBuilderRequiresNonEmptyUnderlyingCaps() { try { - new VcnGatewayConnectionConfig.Builder() - .addExposedCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .build(); + newBuilder().addExposedCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build(); fail("Expected exception due to invalid required underlying capabilities"); } catch (IllegalArgumentException e) { @@ -107,7 +121,7 @@ public class VcnGatewayConnectionConfigTest { @Test public void testBuilderRequiresNonNullRetryInterval() { try { - new VcnGatewayConnectionConfig.Builder().setRetryInterval(null); + newBuilder().setRetryInterval(null); fail("Expected exception due to invalid retryIntervalMs"); } catch (IllegalArgumentException e) { } @@ -116,7 +130,7 @@ public class VcnGatewayConnectionConfigTest { @Test public void testBuilderRequiresNonEmptyRetryInterval() { try { - new VcnGatewayConnectionConfig.Builder().setRetryInterval(new long[0]); + newBuilder().setRetryInterval(new long[0]); fail("Expected exception due to invalid retryIntervalMs"); } catch (IllegalArgumentException e) { } @@ -125,8 +139,7 @@ public class VcnGatewayConnectionConfigTest { @Test public void testBuilderRequiresValidMtu() { try { - new VcnGatewayConnectionConfig.Builder() - .setMaxMtu(VcnGatewayConnectionConfig.MIN_MTU_V6 - 1); + newBuilder().setMaxMtu(VcnGatewayConnectionConfig.MIN_MTU_V6 - 1); fail("Expected exception due to invalid mtu"); } catch (IllegalArgumentException e) { } @@ -144,6 +157,9 @@ public class VcnGatewayConnectionConfigTest { Arrays.sort(underlyingCaps); assertArrayEquals(UNDERLYING_CAPS, underlyingCaps); + assertEquals(CONTROL_PLANE_CONFIG, config.getControlPlaneConfig()); + assertFalse(CONTROL_PLANE_CONFIG == config.getControlPlaneConfig()); + assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMs()); assertEquals(MAX_MTU, config.getMaxMtu()); } diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java index 7dada9d1b6d4..708767605508 100644 --- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java +++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java @@ -22,28 +22,40 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.Context; import android.net.LinkProperties; import android.net.NetworkCapabilities; +import android.net.vcn.VcnManager.VcnStatusCallback; +import android.net.vcn.VcnManager.VcnStatusCallbackBinder; import android.net.vcn.VcnManager.VcnUnderlyingNetworkPolicyListener; +import android.os.ParcelUuid; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import java.net.UnknownHostException; +import java.util.UUID; import java.util.concurrent.Executor; public class VcnManagerTest { + private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0)); + private static final int[] UNDERLYING_NETWORK_CAPABILITIES = { + NetworkCapabilities.NET_CAPABILITY_IMS, NetworkCapabilities.NET_CAPABILITY_INTERNET + }; private static final Executor INLINE_EXECUTOR = Runnable::run; private IVcnManagementService mMockVcnManagementService; private VcnUnderlyingNetworkPolicyListener mMockPolicyListener; + private VcnStatusCallback mMockStatusCallback; private Context mContext; private VcnManager mVcnManager; @@ -52,6 +64,7 @@ public class VcnManagerTest { public void setUp() { mMockVcnManagementService = mock(IVcnManagementService.class); mMockPolicyListener = mock(VcnUnderlyingNetworkPolicyListener.class); + mMockStatusCallback = mock(VcnStatusCallback.class); mContext = getContext(); mVcnManager = new VcnManager(mContext, mMockVcnManagementService); @@ -132,4 +145,74 @@ public class VcnManagerTest { public void testGetUnderlyingNetworkPolicyNullLinkProperties() throws Exception { mVcnManager.getUnderlyingNetworkPolicy(new NetworkCapabilities(), null); } + + @Test + public void testRegisterVcnStatusCallback() throws Exception { + mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback); + + verify(mMockVcnManagementService) + .registerVcnStatusCallback(eq(SUB_GROUP), notNull(), any()); + } + + @Test(expected = IllegalStateException.class) + public void testRegisterVcnStatusCallbackAlreadyRegistered() throws Exception { + mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback); + mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback); + } + + @Test(expected = NullPointerException.class) + public void testRegisterVcnStatusCallbackNullSubscriptionGroup() throws Exception { + mVcnManager.registerVcnStatusCallback(null, INLINE_EXECUTOR, mMockStatusCallback); + } + + @Test(expected = NullPointerException.class) + public void testRegisterVcnStatusCallbackNullExecutor() throws Exception { + mVcnManager.registerVcnStatusCallback(SUB_GROUP, null, mMockStatusCallback); + } + + @Test(expected = NullPointerException.class) + public void testRegisterVcnStatusCallbackNullCallback() throws Exception { + mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, null); + } + + @Test + public void testUnregisterVcnStatusCallback() throws Exception { + mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback); + + mVcnManager.unregisterVcnStatusCallback(mMockStatusCallback); + + verify(mMockVcnManagementService).unregisterVcnStatusCallback(any()); + } + + @Test + public void testUnregisterUnknownVcnStatusCallback() throws Exception { + mVcnManager.unregisterVcnStatusCallback(mMockStatusCallback); + + verifyNoMoreInteractions(mMockVcnManagementService); + } + + @Test(expected = NullPointerException.class) + public void testUnregisterNullVcnStatusCallback() throws Exception { + mVcnManager.unregisterVcnStatusCallback(null); + } + + @Test + public void testVcnStatusCallbackBinder() throws Exception { + IVcnStatusCallback cbBinder = + new VcnStatusCallbackBinder(INLINE_EXECUTOR, mMockStatusCallback); + + cbBinder.onEnteredSafeMode(); + verify(mMockStatusCallback).onEnteredSafeMode(); + + cbBinder.onGatewayConnectionError( + UNDERLYING_NETWORK_CAPABILITIES, + VcnManager.VCN_ERROR_CODE_NETWORK_ERROR, + "java.net.UnknownHostException", + "exception_message"); + verify(mMockStatusCallback) + .onGatewayConnectionError( + eq(UNDERLYING_NETWORK_CAPABILITIES), + eq(VcnManager.VCN_ERROR_CODE_NETWORK_ERROR), + any(UnknownHostException.class)); + } } diff --git a/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkSpecifierTest.java b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkSpecifierTest.java new file mode 100644 index 000000000000..2110d6ee7c86 --- /dev/null +++ b/tests/vcn/java/android/net/vcn/VcnUnderlyingNetworkSpecifierTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.vcn; + +import static com.android.testutils.ParcelUtils.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.net.TelephonyNetworkSpecifier; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VcnUnderlyingNetworkSpecifierTest { + private static final int[] TEST_SUB_IDS = new int[] {1, 2, 3, 5}; + + @Test + public void testGetSubIds() { + final VcnUnderlyingNetworkSpecifier specifier = + new VcnUnderlyingNetworkSpecifier(TEST_SUB_IDS); + + assertEquals(TEST_SUB_IDS, specifier.getSubIds()); + } + + @Test + public void testParceling() { + final VcnUnderlyingNetworkSpecifier specifier = + new VcnUnderlyingNetworkSpecifier(TEST_SUB_IDS); + assertParcelSane(specifier, 1); + } + + @Test + public void testCanBeSatisfiedByTelephonyNetworkSpecifier() { + final TelephonyNetworkSpecifier telSpecifier = + new TelephonyNetworkSpecifier(TEST_SUB_IDS[0]); + + final VcnUnderlyingNetworkSpecifier specifier = + new VcnUnderlyingNetworkSpecifier(TEST_SUB_IDS); + assertTrue(specifier.canBeSatisfiedBy(telSpecifier)); + } +} diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index c290bff188c1..45b2381ce06d 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -26,6 +26,7 @@ import static com.android.server.vcn.VcnTestUtils.setupSystemService; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -42,9 +43,11 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.Context; import android.net.ConnectivityManager; @@ -52,6 +55,7 @@ import android.net.LinkProperties; import android.net.NetworkCapabilities; import android.net.NetworkCapabilities.Transport; import android.net.TelephonyNetworkSpecifier; +import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; import android.net.vcn.VcnConfigTest; @@ -70,7 +74,9 @@ import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.server.VcnManagementService.VcnSafemodeCallback; +import com.android.internal.util.LocationPermissionChecker; +import com.android.server.VcnManagementService.VcnCallback; +import com.android.server.VcnManagementService.VcnStatusCallbackInfo; import com.android.server.vcn.TelephonySubscriptionTracker; import com.android.server.vcn.Vcn; import com.android.server.vcn.VcnContext; @@ -147,14 +153,17 @@ public class VcnManagementServiceTest { mock(PersistableBundleUtils.LockingReadWriteHelper.class); private final TelephonySubscriptionTracker mSubscriptionTracker = mock(TelephonySubscriptionTracker.class); + private final LocationPermissionChecker mLocationPermissionChecker = + mock(LocationPermissionChecker.class); - private final ArgumentCaptor<VcnSafemodeCallback> mSafemodeCallbackCaptor = - ArgumentCaptor.forClass(VcnSafemodeCallback.class); + private final ArgumentCaptor<VcnCallback> mVcnCallbackCaptor = + ArgumentCaptor.forClass(VcnCallback.class); private final VcnManagementService mVcnMgmtSvc; private final IVcnUnderlyingNetworkPolicyListener mMockPolicyListener = mock(IVcnUnderlyingNetworkPolicyListener.class); + private final IVcnStatusCallback mMockStatusCallback = mock(IVcnStatusCallback.class); private final IBinder mMockIBinder = mock(IBinder.class); public VcnManagementServiceTest() throws Exception { @@ -171,6 +180,7 @@ public class VcnManagementServiceTest { doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName(); + doReturn(mMockContext).when(mVcnContext).getContext(); doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper(); doReturn(TEST_UID).when(mMockDeps).getBinderCallingUid(); doReturn(mVcnContext) @@ -188,6 +198,9 @@ public class VcnManagementServiceTest { doReturn(mConfigReadWriteHelper) .when(mMockDeps) .newPersistableBundleLockingReadWriteHelper(any()); + doReturn(mLocationPermissionChecker) + .when(mMockDeps) + .newLocationPermissionChecker(eq(mMockContext)); // Setup VCN instance generation doAnswer((invocation) -> { @@ -206,6 +219,7 @@ public class VcnManagementServiceTest { mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps); doReturn(mMockIBinder).when(mMockPolicyListener).asBinder(); + doReturn(mMockIBinder).when(mMockStatusCallback).asBinder(); // Make sure the profiles are loaded. mTestLooper.dispatchAll(); @@ -707,24 +721,138 @@ public class VcnManagementServiceTest { verify(mMockPolicyListener).onPolicyChanged(); } - @Test - public void testVcnSafemodeCallbackOnEnteredSafemode() throws Exception { - TelephonySubscriptionSnapshot snapshot = - triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); + private void verifyVcnCallback( + @NonNull ParcelUuid subGroup, @NonNull TelephonySubscriptionSnapshot snapshot) + throws Exception { verify(mMockDeps) .newVcn( eq(mVcnContext), - eq(TEST_UUID_1), + eq(subGroup), eq(TEST_VCN_CONFIG), eq(snapshot), - mSafemodeCallbackCaptor.capture()); + mVcnCallbackCaptor.capture()); mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); - VcnSafemodeCallback safemodeCallback = mSafemodeCallbackCaptor.getValue(); - safemodeCallback.onEnteredSafemode(); + VcnCallback vcnCallback = mVcnCallbackCaptor.getValue(); + vcnCallback.onEnteredSafeMode(); - assertFalse(mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1).isActive()); verify(mMockPolicyListener).onPolicyChanged(); } + + @Test + public void testVcnCallbackOnEnteredSafeMode() throws Exception { + TelephonySubscriptionSnapshot snapshot = + triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); + + verifyVcnCallback(TEST_UUID_1, snapshot); + } + + private void triggerVcnStatusCallbackOnEnteredSafeMode( + @NonNull ParcelUuid subGroup, + @NonNull String pkgName, + int uid, + boolean hasPermissionsforSubGroup, + boolean hasLocationPermission) + throws Exception { + TelephonySubscriptionSnapshot snapshot = + triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(subGroup)); + + doReturn(hasPermissionsforSubGroup) + .when(snapshot) + .packageHasPermissionsForSubscriptionGroup(eq(subGroup), eq(pkgName)); + + doReturn(hasLocationPermission) + .when(mLocationPermissionChecker) + .checkLocationPermission(eq(pkgName), any(), eq(uid), any()); + + mVcnMgmtSvc.registerVcnStatusCallback(subGroup, mMockStatusCallback, pkgName); + + // Trigger systemReady() to set up LocationPermissionChecker + mVcnMgmtSvc.systemReady(); + + verifyVcnCallback(subGroup, snapshot); + } + + @Test + public void testVcnStatusCallbackOnEnteredSafeModeWithCarrierPrivileges() throws Exception { + triggerVcnStatusCallbackOnEnteredSafeMode( + TEST_UUID_1, + TEST_PACKAGE_NAME, + TEST_UID, + true /* hasPermissionsforSubGroup */, + true /* hasLocationPermission */); + + verify(mMockStatusCallback, times(1)).onEnteredSafeMode(); + } + + @Test + public void testVcnStatusCallbackOnEnteredSafeModeWithoutCarrierPrivileges() throws Exception { + triggerVcnStatusCallbackOnEnteredSafeMode( + TEST_UUID_1, + TEST_PACKAGE_NAME, + TEST_UID, + false /* hasPermissionsforSubGroup */, + true /* hasLocationPermission */); + + verify(mMockStatusCallback, never()).onEnteredSafeMode(); + } + + @Test + public void testVcnStatusCallbackOnEnteredSafeModeWithoutLocationPermission() throws Exception { + triggerVcnStatusCallbackOnEnteredSafeMode( + TEST_UUID_1, + TEST_PACKAGE_NAME, + TEST_UID, + true /* hasPermissionsforSubGroup */, + false /* hasLocationPermission */); + + verify(mMockStatusCallback, never()).onEnteredSafeMode(); + } + + @Test + public void testRegisterVcnStatusCallback() throws Exception { + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + + Map<IBinder, VcnStatusCallbackInfo> callbacks = mVcnMgmtSvc.getAllStatusCallbacks(); + VcnStatusCallbackInfo cbInfo = callbacks.get(mMockIBinder); + + assertNotNull(cbInfo); + assertEquals(TEST_UUID_1, cbInfo.mSubGroup); + assertEquals(mMockStatusCallback, cbInfo.mCallback); + assertEquals(TEST_PACKAGE_NAME, cbInfo.mPkgName); + assertEquals(TEST_UID, cbInfo.mUid); + verify(mMockIBinder).linkToDeath(eq(cbInfo), anyInt()); + } + + @Test(expected = IllegalStateException.class) + public void testRegisterVcnStatusCallbackDuplicate() { + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + } + + @Test + public void testUnregisterVcnStatusCallback() { + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + Map<IBinder, VcnStatusCallbackInfo> callbacks = mVcnMgmtSvc.getAllStatusCallbacks(); + VcnStatusCallbackInfo cbInfo = callbacks.get(mMockIBinder); + + mVcnMgmtSvc.unregisterVcnStatusCallback(mMockStatusCallback); + assertTrue(mVcnMgmtSvc.getAllStatusCallbacks().isEmpty()); + verify(mMockIBinder).unlinkToDeath(eq(cbInfo), anyInt()); + } + + @Test(expected = SecurityException.class) + public void testRegisterVcnStatusCallbackInvalidPackage() { + doThrow(new SecurityException()).when(mAppOpsMgr).checkPackage(TEST_UID, TEST_PACKAGE_NAME); + + mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME); + } + + @Test + public void testUnregisterVcnStatusCallbackNeverRegistered() { + mVcnMgmtSvc.unregisterVcnStatusCallback(mMockStatusCallback); + + assertTrue(mVcnMgmtSvc.getAllStatusCallbacks().isEmpty()); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index e7154802f1f2..69c21b967917 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -20,6 +20,9 @@ import static android.net.IpSecManager.DIRECTION_IN; import static android.net.IpSecManager.DIRECTION_OUT; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR; +import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR; +import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR; import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration; import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; @@ -39,6 +42,11 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import android.net.LinkProperties; import android.net.NetworkAgent; import android.net.NetworkCapabilities; +import android.net.ipsec.ike.exceptions.AuthenticationFailedException; +import android.net.ipsec.ike.exceptions.IkeException; +import android.net.ipsec.ike.exceptions.IkeInternalException; +import android.net.ipsec.ike.exceptions.TemporaryFailureException; +import android.net.vcn.VcnManager.VcnErrorCode; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -48,6 +56,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import java.io.IOException; +import java.net.UnknownHostException; import java.util.Collections; /** Tests for VcnGatewayConnection.ConnectedState */ @@ -75,8 +85,8 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection } @Test - public void testEnterStateDoesNotCancelSafemodeAlarm() { - verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + public void testEnterStateDoesNotCancelSafeModeAlarm() { + verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */); } @Test @@ -144,7 +154,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testChildOpenedRegistersNetwork() throws Exception { // Verify scheduled but not canceled when entering ConnectedState - verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */); final VcnChildSessionConfiguration mMockChildSessionConfig = mock(VcnChildSessionConfiguration.class); @@ -188,17 +198,17 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection assertTrue(nc.hasCapability(cap)); } - // Now that Vcn Network is up, notify it as validated and verify the Safemode alarm is + // Now that Vcn Network is up, notify it as validated and verify the SafeMode alarm is // canceled mGatewayConnection.mNetworkAgent.onValidationStatus( NetworkAgent.VALIDATION_STATUS_VALID, null /* redirectUri */); - verify(mSafemodeTimeoutAlarm).cancel(); + verify(mSafeModeTimeoutAlarm).cancel(); } @Test public void testChildSessionClosedTriggersDisconnect() throws Exception { // Verify scheduled but not canceled when entering ConnectedState - verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */); getChildSessionCallback().onClosed(); mTestLooper.dispatchAll(); @@ -206,14 +216,33 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */); - // Since network never validated, verify mSafemodeTimeoutAlarm not canceled - verifyNoMoreInteractions(mSafemodeTimeoutAlarm); + // Since network never validated, verify mSafeModeTimeoutAlarm not canceled + verifyNoMoreInteractions(mSafeModeTimeoutAlarm); + + // The child session was closed without exception, so verify that the GatewayStatusCallback + // was not notified + verifyNoMoreInteractions(mGatewayStatusCallback); + } + + @Test + public void testChildSessionClosedExceptionallyNotifiesGatewayStatusCallback() + throws Exception { + final IkeInternalException exception = new IkeInternalException(mock(IOException.class)); + getChildSessionCallback().onClosedExceptionally(exception); + mTestLooper.dispatchAll(); + + verify(mGatewayStatusCallback) + .onGatewayConnectionError( + eq(mConfig.getRequiredUnderlyingCapabilities()), + eq(VCN_ERROR_CODE_INTERNAL_ERROR), + any(), + any()); } @Test public void testIkeSessionClosedTriggersDisconnect() throws Exception { // Verify scheduled but not canceled when entering ConnectedState - verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */); getIkeSessionCallback().onClosed(); mTestLooper.dispatchAll(); @@ -221,7 +250,44 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState()); verify(mIkeSession).close(); - // Since network never validated, verify mSafemodeTimeoutAlarm not canceled - verifyNoMoreInteractions(mSafemodeTimeoutAlarm); + // Since network never validated, verify mSafeModeTimeoutAlarm not canceled + verifyNoMoreInteractions(mSafeModeTimeoutAlarm); + + // IkeSession closed with no error, so verify that the GatewayStatusCallback was not + // notified + verifyNoMoreInteractions(mGatewayStatusCallback); + } + + private void verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback( + IkeException cause, @VcnErrorCode int expectedErrorType) { + getIkeSessionCallback().onClosedExceptionally(cause); + mTestLooper.dispatchAll(); + + verify(mIkeSession).close(); + + verify(mGatewayStatusCallback) + .onGatewayConnectionError( + eq(mConfig.getRequiredUnderlyingCapabilities()), + eq(expectedErrorType), + any(), + any()); + } + + @Test + public void testIkeSessionClosedExceptionallyAuthenticationFailure() throws Exception { + verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback( + new AuthenticationFailedException("vcn test"), VCN_ERROR_CODE_CONFIG_ERROR); + } + + @Test + public void testIkeSessionClosedExceptionallyDnsFailure() throws Exception { + verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback( + new IkeInternalException(new UnknownHostException()), VCN_ERROR_CODE_NETWORK_ERROR); + } + + @Test + public void testIkeSessionClosedExceptionallyInternalFailure() throws Exception { + verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback( + new TemporaryFailureException("vcn test"), VCN_ERROR_CODE_INTERNAL_ERROR); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java index 07282c920088..17ae19e086cf 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java @@ -108,7 +108,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio } @Test - public void testSafemodeTimeoutNotifiesCallback() { - verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState); + public void testSafeModeTimeoutNotifiesCallback() { + verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java index 49ce54d4f684..9ea641f52e48 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -79,7 +79,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState()); - verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */); } @Test @@ -100,6 +100,6 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect assertNull(mGatewayConnection.getCurrentState()); verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any()); - verifySafemodeTimeoutAlarmAndGetCallback(true /* expectCanceled */); + verifySafeModeTimeoutAlarmAndGetCallback(true /* expectCanceled */); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java index 22eab2a1aa65..7385204993c0 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java @@ -82,7 +82,7 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec } @Test - public void testSafemodeTimeoutNotifiesCallback() { - verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState); + public void testSafeModeTimeoutNotifiesCallback() { + verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java index 6c2607586629..5b0850b03f1a 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java @@ -93,7 +93,7 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect } @Test - public void testSafemodeTimeoutNotifiesCallback() { - verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState); + public void testSafeModeTimeoutNotifiesCallback() { + verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index ac9ec0663df2..a660735470a4 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -110,7 +110,7 @@ public class VcnGatewayConnectionTestBase { @NonNull protected final WakeupMessage mTeardownTimeoutAlarm; @NonNull protected final WakeupMessage mDisconnectRequestAlarm; @NonNull protected final WakeupMessage mRetryTimeoutAlarm; - @NonNull protected final WakeupMessage mSafemodeTimeoutAlarm; + @NonNull protected final WakeupMessage mSafeModeTimeoutAlarm; @NonNull protected final IpSecService mIpSecSvc; @NonNull protected final ConnectivityManager mConnMgr; @@ -131,7 +131,7 @@ public class VcnGatewayConnectionTestBase { mTeardownTimeoutAlarm = mock(WakeupMessage.class); mDisconnectRequestAlarm = mock(WakeupMessage.class); mRetryTimeoutAlarm = mock(WakeupMessage.class); - mSafemodeTimeoutAlarm = mock(WakeupMessage.class); + mSafeModeTimeoutAlarm = mock(WakeupMessage.class); mIpSecSvc = mock(IpSecService.class); setupIpSecManager(mContext, mIpSecSvc); @@ -154,7 +154,7 @@ public class VcnGatewayConnectionTestBase { setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM); setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM); setUpWakeupMessage(mRetryTimeoutAlarm, VcnGatewayConnection.RETRY_TIMEOUT_ALARM); - setUpWakeupMessage(mSafemodeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM); + setUpWakeupMessage(mSafeModeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM); doReturn(ELAPSED_REAL_TIME).when(mDeps).getElapsedRealTime(); } @@ -259,23 +259,23 @@ public class VcnGatewayConnectionTestBase { expectCanceled); } - protected Runnable verifySafemodeTimeoutAlarmAndGetCallback(boolean expectCanceled) { + protected Runnable verifySafeModeTimeoutAlarmAndGetCallback(boolean expectCanceled) { return verifyWakeupMessageSetUpAndGetCallback( VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM, - mSafemodeTimeoutAlarm, + mSafeModeTimeoutAlarm, TimeUnit.SECONDS.toMillis(VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS), expectCanceled); } - protected void verifySafemodeTimeoutNotifiesCallback(@NonNull State expectedState) { - // Safemode timer starts when VcnGatewayConnection exits DisconnectedState (the initial + protected void verifySafeModeTimeoutNotifiesCallback(@NonNull State expectedState) { + // SafeMode timer starts when VcnGatewayConnection exits DisconnectedState (the initial // state) final Runnable delayedEvent = - verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */); delayedEvent.run(); mTestLooper.dispatchAll(); - verify(mGatewayStatusCallback).onEnteredSafemode(); + verify(mGatewayStatusCallback).onEnteredSafeMode(); assertEquals(expectedState, mGatewayConnection.getCurrentState()); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java index 66cbf84619ab..9d3368271243 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java @@ -34,7 +34,7 @@ import android.net.vcn.VcnGatewayConnectionConfigTest; import android.os.ParcelUuid; import android.os.test.TestLooper; -import com.android.server.VcnManagementService.VcnSafemodeCallback; +import com.android.server.VcnManagementService.VcnCallback; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener; @@ -56,7 +56,7 @@ public class VcnTest { private VcnContext mVcnContext; private TelephonySubscriptionSnapshot mSubscriptionSnapshot; private VcnNetworkProvider mVcnNetworkProvider; - private VcnSafemodeCallback mVcnSafemodeCallback; + private VcnCallback mVcnCallback; private Vcn.Dependencies mDeps; private ArgumentCaptor<VcnGatewayStatusCallback> mGatewayStatusCallbackCaptor; @@ -72,7 +72,7 @@ public class VcnTest { mVcnContext = mock(VcnContext.class); mSubscriptionSnapshot = mock(TelephonySubscriptionSnapshot.class); mVcnNetworkProvider = mock(VcnNetworkProvider.class); - mVcnSafemodeCallback = mock(VcnSafemodeCallback.class); + mVcnCallback = mock(VcnCallback.class); mDeps = mock(Vcn.Dependencies.class); mTestLooper = new TestLooper(); @@ -104,7 +104,7 @@ public class VcnTest { TEST_SUB_GROUP, mConfig, mSubscriptionSnapshot, - mVcnSafemodeCallback, + mVcnCallback, mDeps); } @@ -148,7 +148,7 @@ public class VcnTest { } @Test - public void testGatewayEnteringSafemodeNotifiesVcn() { + public void testGatewayEnteringSafeModeNotifiesVcn() { final NetworkRequestListener requestListener = verifyAndGetRequestListener(); for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) { startVcnGatewayWithCapabilities(requestListener, capability); @@ -168,16 +168,17 @@ public class VcnTest { any(), mGatewayStatusCallbackCaptor.capture()); - // Doesn't matter which callback this gets - any Gateway entering Safemode should shut down + // Doesn't matter which callback this gets - any Gateway entering safe mode should shut down // all Gateways final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue(); - statusCallback.onEnteredSafemode(); + statusCallback.onEnteredSafeMode(); mTestLooper.dispatchAll(); + assertFalse(mVcn.isActive()); for (final VcnGatewayConnection gatewayConnection : gatewayConnections) { verify(gatewayConnection).teardownAsynchronously(); } verify(mVcnNetworkProvider).unregisterListener(requestListener); - verify(mVcnSafemodeCallback).onEnteredSafemode(); + verify(mVcnCallback).onEnteredSafeMode(); } } |