Merge "Add additional constraint in DPMS for Automotive" into sc-dev
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 8d2ac02..330a19e0 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 @@
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 6650e67..fd35537 100644
--- a/apex/jobscheduler/framework/Android.bp
+++ b/apex/jobscheduler/framework/Android.bp
@@ -22,6 +22,7 @@
],
},
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 7851087..7c7b210 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 @@
@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 @@
* 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 @@
* @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 @@
* 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 @@
* @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 @@
}
/**
+ * 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 2c51935..2f21ce3 100644
--- a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
+++ b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
@@ -41,4 +41,5 @@
@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 657c368..3bc7b30 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 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 @@
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 @@
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 @@
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 f6a1b8a..559a434 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.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.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.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.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 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 @@
final LocalLog mLog = new LocalLog(TAG);
AppOpsManager mAppOps;
+ IAppOpsService mAppOpsService;
DeviceIdleInternal mLocalDeviceIdleController;
private UsageStatsManagerInternal mUsageStatsManagerInternal;
private ActivityManagerInternal mActivityManagerInternal;
@@ -253,10 +264,8 @@
"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 @@
@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 @@
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 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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
}
/**
+ * 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 @@
}
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 @@
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 @@
} 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 @@
}
/**
+ * 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 @@
// 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 @@
// 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 @@
// 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 @@
}
}
+ /**
+ * 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 @@
}
}
- private boolean isExemptFromBatterySaver(Alarm alarm) {
+ private static boolean isExemptFromBatterySaver(Alarm alarm) {
if (alarm.alarmClock != null) {
return true;
}
@@ -3142,7 +3382,7 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
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 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 @@
}
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 @@
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 @@
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 @@
* 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 @@
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 @@
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 @@
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 3cefe65..164781a 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 @@
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 @@
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 96f3bcc..fdbc086 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 @@
pw.println();
for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
- pw.print(" ");
mJobRestrictions.get(i).dumpConstants(pw);
pw.println();
}
@@ -3067,7 +3066,6 @@
job.dump(pw, " ", true, nowElapsed);
-
pw.print(" Restricted due to:");
final boolean isRestricted = checkIfRestricted(job) != null;
if (isRestricted) {
@@ -3161,39 +3159,28 @@
}
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(": ");
- 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));
+ pw.print("Slot #"); pw.print(i); pw.print(": ");
+ jsc.dumpLocked(pw, nowElapsed);
- pw.print(" Active at ");
+ final JobStatus job = jsc.getRunningJobLocked();
+ if (job != null) {
+ pw.increaseIndent();
+ job.dump(pw, " ", false, nowElapsed);
+ pw.print("Evaluated priority: ");
+ pw.println(JobInfo.getPriorityString(job.lastEvaluatedPriority));
+
+ 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 da6f9fe..0aca246 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.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 @@
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 3bad889..5d64579 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -26,10 +26,24 @@
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 0e64a9d..a21b693 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -145,6 +145,7 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 84e6f22..deff7b3 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -14,6 +14,7 @@
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 @@
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 @@
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 @@
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.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 @@
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 @@
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 @@
}
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 @@
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 @@
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.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 @@
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 @@
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 c03f660..ff96f92 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1232,11 +1232,6 @@
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.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 b85b186..1906ee4 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -795,7 +795,8 @@
// 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 @@
*
* @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 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 @@
OP_LOADER_USAGE_STATS,
OP_MANAGE_ONGOING_CALLS,
OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
+ OP_SCHEDULE_EXACT_ALARM,
};
/**
@@ -1751,6 +1766,7 @@
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 @@
OPSTR_MANAGE_CREDENTIALS,
OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
OPSTR_RECORD_AUDIO_OUTPUT,
+ OPSTR_SCHEDULE_EXACT_ALARM,
};
/**
@@ -1978,6 +1995,7 @@
"MANAGE_CREDENTIALS",
"USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER",
"RECORD_AUDIO_OUTPUT",
+ "SCHEDULE_EXACT_ALARM",
};
/**
@@ -2093,6 +2111,7 @@
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 @@
null, // MANAGE_CREDENTIALS
null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
null, // RECORD_AUDIO_OUTPUT
+ null, // SCHEDULE_EXACT_ALARM
};
/**
@@ -2322,6 +2342,7 @@
null, // MANAGE_CREDENTIALS
null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
null, // RECORD_AUDIO_OUTPUT
+ null, // SCHEDULE_EXACT_ALARM
};
/**
@@ -2435,6 +2456,7 @@
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 @@
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 7410a1c..dfc105a 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -428,6 +428,13 @@
*/
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 @@
* 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 @@
mAppTraceRetriever = retriever;
}
+ /**
+ * @see mNativeTombstoneRetriever
+ *
+ * @hide
+ */
+ public void setNativeTombstoneRetriever(final IParcelFileDescriptorRetriever retriever) {
+ mNativeTombstoneRetriever = retriever;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -878,6 +910,12 @@
} else {
dest.writeInt(0);
}
+ if (mNativeTombstoneRetriever != null) {
+ dest.writeInt(1);
+ dest.writeStrongBinder(mNativeTombstoneRetriever.asBinder());
+ } else {
+ dest.writeInt(0);
+ }
}
/** @hide */
@@ -906,6 +944,7 @@
mState = other.mState;
mTraceFile = other.mTraceFile;
mAppTraceRetriever = other.mAppTraceRetriever;
+ mNativeTombstoneRetriever = other.mNativeTombstoneRetriever;
}
private ApplicationExitInfo(@NonNull Parcel in) {
@@ -928,6 +967,10 @@
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 @@
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 96751db..033cffe 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -52,6 +52,7 @@
private boolean mSharedElementTransitionStarted;
private Activity mActivity;
+ private boolean mIsTaskRoot;
private boolean mHasStopped;
private boolean mIsCanceled;
private ObjectAnimator mBackgroundAnimator;
@@ -252,7 +253,7 @@
cancel();
break;
case MSG_ALLOW_RETURN_TRANSITION:
- if (!mIsCanceled) {
+ if (!mIsCanceled && !mIsTaskRoot) {
mPendingExitNames = mAllSharedElementNames;
}
break;
@@ -343,6 +344,9 @@
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 f7097fa..cd84e56 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -551,7 +551,7 @@
@Override
public boolean isReturnTransitionAllowed() {
- return !mActivity.isTopOfTask();
+ return true;
}
@Override
diff --git a/core/java/android/speech/tts/ITextToSpeechSession.aidl b/core/java/android/app/IParcelFileDescriptorRetriever.aidl
similarity index 61%
copy from core/java/android/speech/tts/ITextToSpeechSession.aidl
copy to core/java/android/app/IParcelFileDescriptorRetriever.aidl
index b2afeb0..7e808e7 100644
--- a/core/java/android/speech/tts/ITextToSpeechSession.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,20 +14,18 @@
* limitations under the License.
*/
-package android.speech.tts;
+package android.app;
+
+import android.os.ParcelFileDescriptor;
/**
- * TextToSpeech session interface. Allows to control remote TTS service session once connected.
+ * An interface used to lazily provide a ParcelFileDescriptor to apps.
*
- * @see ITextToSpeechManager
- * @see ITextToSpeechSessionCallback
- *
- * {@hide}
+ * @hide
*/
-oneway interface ITextToSpeechSession {
-
+interface IParcelFileDescriptorRetriever {
/**
- * Disconnects the client from the TTS provider.
+ * Retrieve the ParcelFileDescriptor.
*/
- void disconnect();
-}
\ No newline at end of file
+ ParcelFileDescriptor getPfd();
+}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 28242b0..59e5144 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9851,6 +9851,84 @@
}
/**
+ * 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 @@
}
}
}
+
/**
* Returns true if the caller is running on a device where the admin can grant
* permissions related to device sensors.
@@ -13459,4 +13538,22 @@
}
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 94388cf..8a87b16 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -267,6 +267,9 @@
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 @@
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 0000000..7cbd66d
--- /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 d000f3b..496ca82 100644
--- a/core/java/android/app/people/IPeopleManager.aidl
+++ b/core/java/android/app/people/IPeopleManager.aidl
@@ -18,6 +18,7 @@
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 @@
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 d348edb..108437e 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.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 @@
private static final String LOG_TAG = PeopleManager.class.getSimpleName();
- @NonNull
- private final Context mContext;
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public Map<ConversationListener, Pair<Executor, IConversationListener>>
+ mConversationListeners = new HashMap<>();
@NonNull
- private final IPeopleManager mService;
+ private Context mContext;
+
+ @NonNull
+ private IPeopleManager mService;
/**
* @hide
@@ -56,6 +71,15 @@
}
/**
+ * @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 @@
* 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 @@
*
* @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 @@
*
* @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 @@
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 @@
}
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 10b00f2..5166943 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4583,14 +4583,6 @@
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 f9980bc..567501c 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2065,6 +2065,13 @@
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 @@
/** {@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 a3c3500..fdb00c6 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1356,15 +1356,11 @@
/**
* 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 @@
* 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 @@
* 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 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 5f5697a..5b28e00 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -237,7 +237,8 @@
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 @@
* 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 1c35608..ff1a17e 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -19,6 +19,7 @@
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.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 @@
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 @@
@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 @@
@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);
+ // 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);
+ }
- 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 0dfd5db..d8c9dbc 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.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 @@
*/
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 c854ac98..7639c5d 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.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 @@
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 a14a910..2433186 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.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 @@
*/
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 fa7a62c..f8395a1 100644
--- a/core/java/android/hardware/biometrics/ITestSession.aidl
+++ b/core/java/android/hardware/biometrics/ITestSession.aidl
@@ -18,7 +18,7 @@
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/hardware/biometrics/ITestSessionCallback.aidl b/core/java/android/hardware/biometrics/ITestSessionCallback.aidl
new file mode 100644
index 0000000..3d9517f
--- /dev/null
+++ b/core/java/android/hardware/biometrics/ITestSessionCallback.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.hardware.biometrics;
+
+/**
+ * ITestSession callback for FingerprintManager and BiometricManager.
+ * @hide
+ */
+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 0f595b7..16ab900 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2908,6 +2908,29 @@
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 886a8c1..a9bcdeff 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -574,12 +574,23 @@
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 a3e7e2d..6e7c701 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.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 @@
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 @@
// 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 a614ebf..9d086cf 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -154,7 +154,8 @@
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 @@
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 8888247..054c0d0 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.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 @@
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 @@
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 ebb3021..a65f36b 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -24,6 +24,7 @@
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 @@
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 4961195..92882c4 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -60,7 +60,8 @@
// 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/ConnectivityMetricsEvent.aidl
rename to core/java/android/net/ConnectivityMetricsEvent.aidl
diff --git a/packages/Connectivity/framework/src/android/net/InterfaceConfiguration.aidl b/core/java/android/net/InterfaceConfiguration.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/InterfaceConfiguration.aidl
rename to core/java/android/net/InterfaceConfiguration.aidl
diff --git a/packages/Connectivity/framework/src/android/net/UidRange.aidl b/core/java/android/net/UidRange.aidl
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/UidRange.aidl
rename to 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 4f293ee..6a3cb42 100644
--- a/core/java/android/net/vcn/IVcnManagementService.aidl
+++ b/core/java/android/net/vcn/IVcnManagementService.aidl
@@ -18,6 +18,7 @@
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 @@
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 0000000..555e9b5
--- /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 5eb4ba6..52cc218 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -20,6 +20,7 @@
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 @@
* 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 @@
@NonNull String packageName,
@NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) {
mPackageName = packageName;
- mGatewayConnectionConfigs = Collections.unmodifiableSet(gatewayConnectionConfigs);
+ mGatewayConnectionConfigs =
+ Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs));
validate();
}
@@ -96,11 +97,7 @@
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 @@
}
};
- /**
- * 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 @@
*
* @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 @@
* 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 0000000..0c6ccfe
--- /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 0000000..2f6e1f6
--- /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 cead2f1..94dff91 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -21,6 +21,8 @@
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 @@
* 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 @@
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 @@
/** 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 @@
/** @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 @@
}
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 @@
}
/**
+ * 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 @@
* <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 @@
* Retrieves the configured retry intervals.
*
* @see Builder#setRetryInterval(long[])
- * @hide
*/
@NonNull
public long[] getRetryInterval() {
@@ -317,7 +331,7 @@
*
* <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 @@
/**
* 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 PersistableBundle toPersistableBundle() {
final PersistableBundle result = new PersistableBundle();
+ final PersistableBundle ctrlPlaneConfigBundle = mCtrlPlaneConfig.toPersistableBundle();
final PersistableBundle exposedCapsBundle =
PersistableBundleUtils.fromList(
new ArrayList<>(mExposedCapabilities),
@@ -356,6 +370,7 @@
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 @@
/**
* 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 @@
// 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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* 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 @@
* @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 @@
* 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 1a38338..aea0ea9 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -17,12 +17,15 @@
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.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 @@
/**
* 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 @@
* 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 @@
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 @@
*
* @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 @@
}
}
- // TODO: Make setVcnConfig(), clearVcnConfig() Public API
/**
* Clears the VCN configuration for a given subscription group.
*
@@ -145,9 +145,8 @@
* @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 @@
}
}
+ /** @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 @@
@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 0000000..a975637
--- /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 b39c182..7437e037 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -113,6 +113,7 @@
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 a828077..bb40d90 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -29,21 +29,11 @@
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 @@
private UidBatteryConsumer(@NonNull Builder builder) {
super(builder.mPowerComponentsBuilder.build());
mUid = builder.mUid;
- mSystemComponent = builder.mSystemComponent;
mPackageWithHighestDrain = builder.mPackageWithHighestDrain;
}
@@ -95,7 +84,6 @@
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 ea1ce37..8bdfd3d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2300,6 +2300,19 @@
}
/**
+ * 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 f2fe719..a078e04 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -36,6 +36,7 @@
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 @@
@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 @@
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 @@
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 0000000..35e63d4
--- /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 0000000..49507220
--- /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 084cc2f..6d677f3 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -20,6 +20,7 @@
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 @@
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 f306805..084b18e 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.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.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 @@
/**
* 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 @@
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 8105b65..ad9e8b3 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 @@
}
/**
+ * @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 @@
* @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 @@
@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 @@
}
@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 91e091c..e134c29 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -525,6 +525,13 @@
*/
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 f3a7856..074d5f1 100644
--- a/core/java/android/provider/SimPhonebookContract.java
+++ b/core/java/android/provider/SimPhonebookContract.java
@@ -262,7 +262,7 @@
@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 f013976e..7996f09 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -5286,7 +5286,8 @@
* 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 a79b197..5a89cdf 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -188,6 +188,7 @@
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 f91e122..cc1cded 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -43,7 +43,7 @@
* @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 @@
* @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 7158ba2..8e5292d 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 @@
*
* {@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 d760810..26afdaa 100644
--- a/core/java/android/speech/IRecognitionServiceManagerCallback.aidl
+++ b/core/java/android/speech/IRecognitionServiceManagerCallback.aidl
@@ -25,5 +25,5 @@
*/
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 c97dbfe..fd584f1 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -105,17 +105,6 @@
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 @@
* 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 @@
@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 @@
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 @@
@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 de879c6..850f997 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -20,8 +20,8 @@
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.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 @@
/** 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 @@
/** 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 @@
}
};
- /**
- * 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 @@
}
/**
- * 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 @@
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();
+
+ if (DBG) {
+ Slog.i(TAG, "#startListening called");
+ if (mService == null) {
+ Slog.i(TAG, "Connection is not established yet");
}
}
- 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;
- }
-
- 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 @@
}
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 @@
}
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 @@
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 @@
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/ITextToSpeechManager.aidl b/core/java/android/speech/tts/ITextToSpeechManager.aidl
deleted file mode 100644
index e6b63df..0000000
--- a/core/java/android/speech/tts/ITextToSpeechManager.aidl
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.speech.tts;
-
-import android.speech.tts.ITextToSpeechSessionCallback;
-
-/**
- * TextToSpeechManagerService interface. Allows opening {@link TextToSpeech} session with the
- * specified provider proxied by the system service.
- *
- * @hide
- */
-oneway interface ITextToSpeechManager {
- void createSession(in String engine, in ITextToSpeechSessionCallback managerCallback);
-}
diff --git a/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl b/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
deleted file mode 100644
index 545622a..0000000
--- a/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.speech.tts;
-import android.speech.tts.ITextToSpeechSession;
-
-/**
- * Callback interface for a session created by {@link ITextToSpeechManager} API.
- *
- * @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
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 5d66dc7..7a18538 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -35,7 +35,6 @@
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.Map;
import java.util.MissingResourceException;
import java.util.Set;
-import java.util.concurrent.Executor;
/**
*
@@ -697,8 +695,6 @@
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 @@
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 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 @@
mUtteranceProgressListener = null;
mEnginesHelper = new TtsEngines(mContext);
-
- mIsSystem = isSystem;
-
initTts();
}
@@ -865,14 +842,10 @@
}
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 @@
}
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 @@
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 @@
};
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 @@
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 @@
}
@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 @@
mServiceConnection = Connection.this;
mEstablished = false;
- mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask();
+ mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
mOnSetupConnectionAsyncTask.execute();
}
}
@@ -2311,7 +2277,7 @@
*
* @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 @@
}
@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 void disconnect() {
+ mContext.unbindService(this);
+ clearServiceConnection();
+ }
+
public boolean isEstablished() {
return mService != null && mEstablished;
}
@@ -2371,91 +2342,6 @@
}
}
- // 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 77f9c1a..e7ceada 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -1840,13 +1840,15 @@
*
* @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 87820a8..e599888 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -12706,12 +12706,12 @@
// 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 @@
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 @@
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 @@
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 @@
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 45d8128..97f727b 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -124,15 +124,15 @@
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 @@
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 @@
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 f7fad2c..4299f09 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -91,15 +91,24 @@
* 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 95999a7..fe3042d 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -74,6 +74,7 @@
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 @@
* @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 fe4106d..c61802d 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 e7c4947..59c260c 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 f723426..1020c14 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 dd048f3..faa2157 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 1ebcf77..22dce9b 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 0000000..a2afc77
--- /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 8f81ea2..7dca0cb 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 @@
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 @@
// 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 @@
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 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 @@
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 fa92b6d..2d6ae2e 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 84da930..5633de3 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 2b0d7e5..c79c12c 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -279,8 +279,8 @@
* }
*/
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 @@
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 @@
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 @@
mUnlockedDeviceRequired = unlockedDeviceRequired;
mCriticalToDeviceEncryption = criticalToDeviceEncryption;
mMaxUsageCount = maxUsageCount;
+ mAttestKeyAlias = attestKeyAlias;
}
/**
@@ -869,6 +872,18 @@
}
/**
+ * 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 @@
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 @@
mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired();
mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption();
mMaxUsageCount = sourceSpec.getMaxUsageCount();
+ mAttestKeyAlias = sourceSpec.getAttestKeyAlias();
}
/**
@@ -1695,6 +1712,28 @@
}
/**
+ * 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 @@
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 293ab05..7b0fa91 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -70,6 +70,7 @@
PURPOSE_VERIFY,
PURPOSE_WRAP_KEY,
PURPOSE_AGREE_KEY,
+ PURPOSE_ATTEST_KEY,
})
public @interface PurposeEnum {}
@@ -113,6 +114,13 @@
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 @@
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 @@
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 aaa3715..fe92270 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -588,7 +588,8 @@
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 1f2f853..c20cf01 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -110,6 +110,7 @@
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 @@
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 @@
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 4d27c34..b3bfd6a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -20,7 +20,9 @@
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.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.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.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 @@
private int mKeymasterAlgorithm = -1;
private int mKeySizeBits;
private SecureRandom mRng;
+ private KeyDescriptor mAttestKeyDescriptor;
private int[] mKeymasterPurposes;
private int[] mKeymasterBlockModes;
@@ -197,83 +204,9 @@
// 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 @@
mJcaKeyAlgorithm = jcaKeyAlgorithm;
mRng = random;
mKeyStore = KeyStore2.getInstance();
+
+ mAttestKeyDescriptor = buildAndCheckAttestKeyDescriptor(spec);
+ checkAttestKeyPurpose(spec);
+
success = true;
} finally {
if (!success) {
@@ -350,6 +287,156 @@
}
}
+ 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 @@
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 73371e7..56fe126 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 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 71331df..843e27f 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 @@
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 c3970e3..4a2a032 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 @@
// 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 2c6c7b3..d80699d 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 385d465..678b0ad 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -445,6 +445,7 @@
"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 ce9b288..5d3f6f2 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -40,6 +40,7 @@
"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 8fddf71..1fddac4 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -463,9 +463,7 @@
}
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::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::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 @@
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 155f6df..eac3f22 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -208,29 +208,21 @@
*/
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 0000000..27a038d
--- /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 0000000..7e6786f
--- /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 05bae5c..d9c9eee 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 @@
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 @@
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 3c86b28..bcec0fa 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 @@
}
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 ee7c4d8..b288402 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -194,28 +194,20 @@
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 7951537..a1ba70a 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 @@
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 f77ca2a..dae3c94 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 @@
// 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 e2fdf2f..09c6a4f 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 e741490..abfc8bf 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -26,3 +26,7 @@
# 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 49f9d66..adb8a54c 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -38,6 +38,7 @@
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 @@
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 a330bc0..029edeb 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.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 @@
/**
* Network type to string.
+ * @hide
*/
public static String networkTypeToString(@NetworkType int value) {
switch (value) {
@@ -92,25 +102,34 @@
*
* @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 @@
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 @@
/** @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 @@
* 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 @@
/**
* 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 94e55b4..1cadf3b 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 070b4e4..4aa61662 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 @@
/**
* 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 @@
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 @@
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 @@
/**
* 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 @@
/**
* Gets number of local bytes read.
*/
+ @IntRange(from = -1)
public long getLocalBytesRead() {
return mLocalBytesRead;
}
@@ -277,6 +316,7 @@
/**
* Gets network transfer duration in milliseconds.
*/
+ @IntRange(from = -1)
public long getNetworkTransferDurationMillis() {
return mNetworkTransferDurationMillis;
}
@@ -415,34 +455,33 @@
*/
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 @@
/**
* 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 @@
/**
* 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 4cb957f..4ee8a45 100644
--- a/media/java/android/media/metrics/PlaybackSession.java
+++ b/media/java/android/media/metrics/PlaybackSession.java
@@ -45,7 +45,6 @@
/**
* Reports playback metrics.
- * @hide
*/
public void reportPlaybackMetrics(@NonNull PlaybackMetrics metrics) {
mManager.reportPlaybackMetrics(mId, metrics);
@@ -60,9 +59,8 @@
/**
* Reports network event.
- * @hide
*/
- public void reportNetworkEvent(NetworkEvent event) {
+ public void reportNetworkEvent(@NonNull NetworkEvent event) {
mManager.reportNetworkEvent(mId, event);
}
@@ -75,9 +73,8 @@
/**
* 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 fff0e36..ef25357 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 @@
/**
* 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 @@
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 @@
@Nullable String sampleMimeType,
@Nullable String codecName,
int bitrate,
- long timeSincePlaybackCreatedMillis,
+ long timeSinceCreatedMillis,
int type,
@Nullable String language,
@Nullable String languageRegion,
@@ -110,7 +120,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
};
-
-
- // 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 @@
&& 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 @@
@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 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 @@
return this;
}
+ /**
+ * Sets track change reason.
+ */
public @NonNull Builder setTrackChangeReason(@TrackChangeReason int value) {
checkNotUsed();
mBuilderFieldsSet |= 0x2;
@@ -363,6 +418,9 @@
return this;
}
+ /**
+ * Sets container MIME type.
+ */
public @NonNull Builder setContainerMimeType(@NonNull String value) {
checkNotUsed();
mBuilderFieldsSet |= 0x4;
@@ -370,6 +428,9 @@
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 @@
return this;
}
+ /**
+ * Sets codec name.
+ */
public @NonNull Builder setCodecName(@NonNull String value) {
checkNotUsed();
mBuilderFieldsSet |= 0x10;
@@ -384,27 +448,33 @@
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;
+ mTimeSinceCreatedMillis = value;
return this;
}
- public @NonNull Builder setTrackType(@TrackType int value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x80;
- mType = 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 @@
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 @@
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 @@
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 740bc2d..c0185dc 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -2525,7 +2525,9 @@
/**
* 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 @@
/**
* 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 4972529..6160b81 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -64,6 +64,7 @@
"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 0e8719e..22c3572 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 @@
jclass classId;
};
+struct LogMessageFields {
+ jmethodID init;
+ jclass classId;
+};
+
struct fields_t {
jfieldID context;
jmethodID post_event;
@@ -208,6 +214,7 @@
jmethodID createFromParcelId;
jclass parcelCreatorClassId;
KeyStatusFields keyStatus;
+ LogMessageFields logMessage;
};
static fields_t gFields;
@@ -224,6 +231,19 @@
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 @@
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 @@
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 @@
{ "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 72589e3..c30d4bf 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
@@ -165,6 +165,7 @@
protected void onStop() {
super.onStop();
if (!isFinishing() && !isChangingConfigurations()) {
+ Log.i(LOG_TAG, "onStop() - cancelling");
cancel();
}
}
@@ -195,7 +196,6 @@
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 606cd57..67d4b41 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -309,7 +309,7 @@
}
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 8db8d76..73e1511 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/CaptivePortalData.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/ConnectivityDiagnosticsManager.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/DhcpInfo.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/IpConfiguration.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/IpPrefix.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/KeepalivePacketData.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/LinkAddress.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/LinkProperties.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/MacAddress.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/Network.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/NetworkAgentConfig.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/NetworkCapabilities.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/NetworkInfo.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/NetworkRequest.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/ProxyInfo.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/RouteInfo.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/StaticIpConfiguration.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/TestNetworkInterface.aidl
rename to 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
similarity index 100%
rename from packages/Connectivity/framework/src/android/net/apf/ApfCapabilities.aidl
rename to 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 81ca9ea..228de03 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 @@
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 dbd25ce..a15ceb6 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
similarity index 90%
rename from packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml
rename to packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
index 1f6b24b..dd35dd9 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 b7a9fafd..604ab72 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 a928b75..2a5784a 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 cd82b80..2391803 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 739738a..9ea7aa3 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 bb4a20e..3300495 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 dc341274..059bda3 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 825ea25..4e06491 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_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 @@
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 cdf9858..97d6e97 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -17,14 +17,16 @@
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 @@
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 @@
// 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 @@
}
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 @@
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 886c372..8cb1bc4 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -17,7 +17,7 @@
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 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 01e1c63..a4a781d 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -18,7 +18,7 @@
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 @@
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 40c2386..fc89783 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.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 @@
*/
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 @@
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 @@
structure = it
} ?: run(this::finish)
+ backToGlobalActions = intent.getBooleanExtra(
+ ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
+ true
+ )
+
bindViews()
bindButtons()
@@ -100,7 +109,11 @@
}
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 b282157..1c2f17c 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.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 @@
@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 @@
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 @@
override fun onBackPressed() {
if (!fromProviderSelector) {
- globalActionsComponent.handleShowGlobalActionsMenu()
+ openControlsOrigin()
}
animateExitAndFinish()
}
@@ -129,6 +133,11 @@
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 @@
)
}
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 db68d17..537334a 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 @@
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 9448877..20bdf60 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 @@
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 762362c..c94d85a 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 @@
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 d65d169..2873cd3 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.Nullable;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
import android.view.View;
/**
@@ -56,7 +57,7 @@
/**
* Message to display
*/
- public @NonNull CharSequence getMessage() {
+ public @Nullable CharSequence getMessage() {
return mMessage;
}
@@ -88,6 +89,17 @@
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 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 @@
}
/**
- * 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 @@
* 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 2e599de..d4678f3 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 @@
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 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 @@
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 dab4d0b..1660dea 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 @@
@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 87252ff..a0bf584 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 @@
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 @@
}
void onAttach(TintedIconManager iconManager) {
+ mTintedIconManager = iconManager;
int fillColor = Utils.getColorAttrDefaultColor(getContext(),
android.R.attr.textColorPrimary);
@@ -268,6 +270,9 @@
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 c70a93b..7e2d27a 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 @@
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 @@
}
/**
- * 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 @@
}
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 @@
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 @@
@Override
public void onUserSwitchComplete(int userId) {
if (mVisible) {
- updateOwnerInfo();
updateIndication(false);
}
}
@@ -1057,7 +1063,7 @@
@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 d562726..138c811 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 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 @@
@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 8aadef8..2f9fa9e 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.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
-import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintSet;
@@ -405,6 +404,7 @@
// 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 @@
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 @@
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 024a0b1..525f220 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.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 @@
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 @@
// 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 9e78a66..0a3e833 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 @@
"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 522a42b..1f222d8 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 0000000..b44fb8e
--- /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 f7f8d03..aa385ef 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 @@
// 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 54b7453..e2e4af2 100644
--- a/packages/services/CameraExtensionsProxy/Android.bp
+++ b/packages/services/CameraExtensionsProxy/Android.bp
@@ -18,6 +18,7 @@
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 3154628..8aae8e6 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -6,7 +6,7 @@
}
filegroup {
- name: "services-all-sources",
+ name: "services-non-updatable-sources",
srcs: [
":services.core-sources",
":services.core-sources-am-wm",
@@ -33,12 +33,19 @@
":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 @@
"services.startop",
"services.systemcaptions",
"services.translation",
- "services.texttospeech",
"services.usage",
"services.usb",
"services.voiceinteraction",
@@ -125,9 +131,8 @@
// 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 @@
" --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 @@
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 0000000..3c72d38
--- /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 0000000..b46d21e
--- /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 0000000..d802177
--- /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 10b00d3..76c8d30 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 @@
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 @@
@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 @@
mBleStateBroadcastReceiver, mBleStateBroadcastReceiver.mIntentFilter);
initBleScanning();
} else {
- Log.w(LOG_TAG, "No BluetoothAdapter available");
+ Slog.w(LOG_TAG, "No BluetoothAdapter available");
}
}
}
@@ -324,6 +327,7 @@
}
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 @@
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 @@
@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 @@
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 @@
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 @@
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 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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
}
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 @@
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 @@
}
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 @@
}
private void grantDeviceProfile(Association association) {
+ Slog.i(LOG_TAG, "grantDeviceProfile(association = " + association + ")");
+
if (association.getDeviceProfile() != null) {
mRoleManager.addRoleHolderAsUser(
association.getDeviceProfile(),
@@ -1030,7 +1036,7 @@
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 @@
}
void onDeviceDisconnected(String address) {
+ Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")");
+
mCurrentlyConnectedDevices.remove(address);
onDeviceDisappeared(address);
@@ -1059,13 +1067,13 @@
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 @@
@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 @@
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 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 @@
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 @@
@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 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 @@
}
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 @@
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 @@
}
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 @@
}
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 @@
}
void startBleScan() {
- Log.i(LOG_TAG, "startBleScan()");
+ Slog.i(LOG_TAG, "startBleScan()");
List<ScanFilter> filters = getBleScanFilters();
if (filters.isEmpty()) {
@@ -1274,7 +1291,7 @@
}
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 @@
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 @@
}
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 154e183..5077cc6 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3721,7 +3721,12 @@
// 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 27210da..329ab99 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 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.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 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 @@
*
* @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 @@
@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 @@
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 @@
@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 @@
// 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 @@
mContext.getSystemService(ConnectivityManager.class)
.registerNetworkProvider(mNetworkProvider);
mTelephonySubscriptionTracker.register();
+ mLocationPermissionChecker = mDeps.newLocationPermissionChecker(mVcnContext.getContext());
}
private void enforcePrimaryUser() {
@@ -440,12 +458,10 @@
// 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 @@
}
}
+ /** 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 @@
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;
- /** VcnSafemodeCallback is used by Vcns to notify VcnManagementService on entering Safemode. */
- private class VcnSafemodeCallbackImpl implements VcnSafemodeCallback {
- @NonNull private final ParcelUuid mSubGroup;
-
- private VcnSafemodeCallbackImpl(@NonNull final ParcelUuid subGroup) {
- mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
+ private VcnStatusCallbackInfo(
+ @NonNull ParcelUuid subGroup,
+ @NonNull IVcnStatusCallback callback,
+ @NonNull String pkgName,
+ int uid) {
+ mSubGroup = subGroup;
+ mCallback = callback;
+ mPkgName = pkgName;
+ mUid = uid;
}
@Override
- public void onEnteredSafemode() {
+ 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);
+ }
+ }
+
+ /** 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 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() {
synchronized (mLock) {
// Ignore if this subscription group doesn't exist anymore
if (!mVcns.containsKey(mSubGroup)) {
@@ -695,6 +833,40 @@
}
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 0b1c115..5ee0e04 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_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.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 @@
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 @@
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 @@
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 17be210..b85d729 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.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.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 @@
* 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 @@
} 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 52bb55f..fc28bfb 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 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 @@
* 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 @@
= 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
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);
}
- // TODO(b/180029015): Store typeToIds in mEnergyConsumerTypeToIdMap.
+
+ 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);
+ }
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 b15a886..e19745e 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.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 @@
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 614c5f1..00a4e43 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.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 @@
*/
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 e062695..16f82af 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 @@
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 @@
@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 f37cf18..06b049b 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.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 @@
}
@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 8253927..6dbd590 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.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 @@
*/
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 @@
return null;
}
- return provider.createTestSession(sensorId, opPackageName);
+ return provider.createTestSession(sensorId, callback, opPackageName);
}
@Override
@@ -386,7 +388,22 @@
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 cc24b89..88edfbf 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.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.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 @@
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 @@
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 @@
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 897ebd7..a5e6ddb 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 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.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 @@
@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 @@
};
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 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 9680e4e..c6696aed 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 @@
// 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 1b6b9d7..1d8f210 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.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 @@
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 @@
@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 @@
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 @@
}
@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 @@
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 @@
@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 1cb5031..48796c1 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 @@
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 4925ce0..3434acb 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.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 @@
}
}
- @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 d519d60..e8668ed 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 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 @@
@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 @@
}
};
- 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 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 e46661a..ee8823e 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.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 @@
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 @@
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 @@
});
}
+ @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 @@
});
}
- private void scheduleInternalCleanup(int userId) {
+ private void scheduleInternalCleanup(int userId,
+ @Nullable BaseClientMonitor.Callback callback) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
@@ -750,13 +766,14 @@
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 @@
@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 d63791c..3ae2011 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 @@
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 34a9099..32e9409 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.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 @@
}
@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 b0e42cd..396dd5f 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.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 @@
*/
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 @@
return null;
}
- return provider.createTestSession(sensorId, opPackageName);
+ return provider.createTestSession(sensorId, callback, opPackageName);
}
@Override
@@ -499,7 +501,21 @@
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 f672ae5..dfec2e3 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.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.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 @@
@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 @@
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 ea9c709..20b3254 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 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 @@
@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 @@
};
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 @@
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 2a0e984..0de3f4f 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 @@
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 0bd2f24..598cc89 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.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 @@
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 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 @@
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 @@
}
@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 @@
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 @@
@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 4a99a7b..c622208 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 @@
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 c83c0fb..a98e7db 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.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 @@
}
}
- @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 312ee0a..766a882 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 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 @@
@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 @@
};
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 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 7a74c6a..6e22a79 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.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 @@
private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
@Override
public void onUserSwitching(int newUserId) {
- scheduleInternalCleanup(newUserId);
+ scheduleInternalCleanup(newUserId, null /* callback */);
}
};
@@ -437,7 +438,7 @@
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 @@
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 @@
boolean success) {
if (success) {
// Update authenticatorIds
- scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId());
+ scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
+ true /* force */);
}
}
});
@@ -636,7 +645,25 @@
});
}
- 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 @@
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 @@
@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 f6a22f5..2f360f3 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 @@
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 db7f4bc..6776810 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 @@
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 @@
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 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 8e84613..f44e069 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.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 @@
}
@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 09c0937..01e839d 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.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 @@
@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 fa1fb48..d014f14 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 @@
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 @@
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 ee3427f..a1e6136 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 @@
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 @@
return oneTouchPlay(pw);
case "vendorcommand":
return vendorCommand(pw);
+ case "cec_setting":
+ return cecSetting(pw);
}
getErrPrintWriter().println("Unhandled command: " + cmd);
@@ -157,4 +163,39 @@
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 bd577f3..c4c0f68 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 @@
int durationMs) {
Slog.i(mTag, "setTemporaryService(" + userId + ") to " + componentName + " for "
+ durationMs + "ms");
- if (mServiceNameResolver == null) {
- return;
- }
enforceCallingPermissionForManagement();
Objects.requireNonNull(componentName);
@@ -407,9 +404,6 @@
enforceCallingPermissionForManagement();
synchronized (mLock) {
- if (mServiceNameResolver == null) {
- return false;
- }
final boolean changed = mServiceNameResolver.setDefaultServiceEnabled(userId, enabled);
if (!changed) {
if (verbose) {
@@ -440,10 +434,6 @@
public final boolean isDefaultServiceEnabled(@UserIdInt int userId) {
enforceCallingPermissionForManagement();
- if (mServiceNameResolver == null) {
- return false;
- }
-
synchronized (mLock) {
return mServiceNameResolver.isDefaultServiceEnabled(userId);
}
@@ -968,10 +958,6 @@
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 5eec315..6deb19a 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 @@
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 0000000..85de4bb
--- /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 d3c853d..6249a06 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.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 @@
* 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 @@
*/
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 @@
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 @@
}
}
- /* 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 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 @@
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 @@
* Sends a message to the client associated with this object.
*
* @param message the message that came from a nanoapp
+ * @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) {
- mMessageChannelNanoappIdSet.add(message.getNanoAppId());
+ /* 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 @@
* @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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
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 @@
} 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 0351edb..e3522f6 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.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 @@
}
}
- /* package */ ContextHubClientManager(
- Context context, IContextHubWrapper contextHubProxy) {
+ /* package */ ContextHubClientManager(Context context, IContextHubWrapper contextHubProxy) {
mContext = context;
mContextHubProxy = contextHubProxy;
}
@@ -155,13 +155,15 @@
*/
/* 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 @@
*/
/* 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 @@
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 @@
* 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 @@
}
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 @@
}
/**
+ * 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 @@
* @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 2eafe6a..0737db7 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.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.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 @@
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 void handleClientMsg(ContextHubMsg message) {
handleClientMessageCallback(mContextHubId, message,
Collections.emptyList() /* nanoappPermissions */,
- Collections.emptyList() /* messageContentPermissions */);
+ Collections.emptyList() /* messagePermissions */);
}
@Override
@@ -167,9 +171,9 @@
@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 @@
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 @@
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 @@
}
@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 @@
/**
* 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 @@
* @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 @@
@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 @@
}
ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
- return mClientManager.registerClient(contextHubInfo, clientCallback, attributionTag);
+ return mClientManager.registerClient(
+ contextHubInfo, clientCallback, attributionTag, mTransactionManager, packageName);
}
/**
@@ -766,7 +782,7 @@
ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
return mClientManager.registerClient(
- contextHubInfo, pendingIntent, nanoAppId, attributionTag);
+ contextHubInfo, pendingIntent, nanoAppId, attributionTag, mTransactionManager);
}
/**
@@ -907,6 +923,8 @@
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 @@
// 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 0000000..5ec85e6
--- /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 c1d63dd..3a5c220 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 @@
/**
* 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 @@
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 @@
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 @@
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 6a89079..736b654 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.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);
+ }
+
+ public synchronized void onSystemReady() {
+ mSystemReady = true;
+ mPowerManager = Objects.requireNonNull(mContext.getSystemService(PowerManager.class));
+ onRegistrationStateChanged();
}
@Override
- protected void registerInternal() {
- if (mReceiver == null) {
- mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- notifyDeviceIdleChanged();
- }
- };
+ protected synchronized void registerInternal() {
+ mRegistrationRequired = true;
+ onRegistrationStateChanged();
+ }
- mContext.registerReceiver(mReceiver,
- new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED), null,
- FgThread.getHandler());
+ @Override
+ protected synchronized void unregisterInternal() {
+ mRegistrationRequired = false;
+ onRegistrationStateChanged();
+ }
+
+ private void onRegistrationStateChanged() {
+ if (!mSystemReady) {
+ return;
}
- }
- @Override
- protected void unregisterInternal() {
- if (mReceiver != null) {
- mContext.unregisterReceiver(mReceiver);
+ 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 6f0e681..9874ecf 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 @@
*/
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 0d7bcb0..03ade5f 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 @@
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 685e9e6..8da2d67 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 @@
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 @@
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 @@
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 @@
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 4f4b4f2..639dda6 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 @@
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 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 @@
.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 2d540de..c4b6485 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.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 @@
*/
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 @@
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 a83edb7..9984bfa 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 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.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 @@
}
void onSystemReady() {
+ registerForUserRemoval();
+ registerForPackageRemoval();
+
// Scan existing tombstones.
mHandler.post(() -> {
final File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
@@ -94,8 +114,9 @@
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 @@
}
}
+ /**
+ * 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 @@
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 @@
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 @@
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 0ce2673..5cb9d8f 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.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 @@
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 @@
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 9588a27..ec643f5 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 @@
}
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 753f22d..24c27be 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 @@
}
@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 86a92d79..e5ed774 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 @@
// 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 6a441f1..4ccd57d 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_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 @@
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 db33e75..b5a9aca 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 @@
/**
* @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 6d9cb75..2a95416 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 @@
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 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 @@
// 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 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 37fc5a0..c4f29ea 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.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 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 @@
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 @@
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 b7285d5..bb52c1d 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 @@
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 @@
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 @@
}
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 @@
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 bd003d3..11b22a5 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 @@
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 @@
}
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 @@
}
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 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 6ec71b7..74bb993 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.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 @@
private final IBinder mService = new IFileIntegrityService.Stub() {
@Override
public boolean isApkVeritySupported() {
- return FileIntegrityService.isApkVeritySupported();
+ return VerityUtils.isFsVeritySupported();
}
@Override
@@ -69,7 +67,7 @@
checkCallerPermission(packageName);
try {
- if (!isApkVeritySupported()) {
+ if (!VerityUtils.isFsVeritySupported()) {
return false;
}
if (certificateBytes == null) {
@@ -110,11 +108,6 @@
}
};
- 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 09ee001..48a60387 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 @@
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 96248c3..0974537 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 @@
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 @@
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());
}
}
- void startListening(Intent recognizerIntent, IRecognitionListener listener, String packageName,
- String featureId) throws RemoteException {
- if (DEBUG) {
- Slog.i(TAG, "#startListening for package: " + packageName + ", feature=" + featureId);
- }
- run(service -> service.startListening(recognizerIntent, listener, packageName, featureId));
+ ComponentName getServiceComponentName() {
+ return mComponentName;
}
- void stopListening(IRecognitionListener listener, String packageName, String featureId)
- throws RemoteException {
+ void startListening(Intent recognizerIntent, IRecognitionListener listener, String packageName,
+ String featureId) {
+ if (DEBUG) {
+ 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));
+ }
+ }
+
+ 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 @@
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 592ba9e..dbe7354 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 @@
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 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 @@
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 bcaf174..2656a3d 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.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 @@
@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));
}
- }
- void createSessionLocked(IRecognitionServiceManagerCallback callback) {
- // TODO(b/176578753): check clients have record audio permission.
- // TODO(b/176578753): verify caller package is the one supplied
+ ComponentName serviceComponent = componentName;
+ if (onDevice) {
+ serviceComponent = getOnDeviceComponentNameLocked();
+ }
- RemoteSpeechRecognitionService service = ensureRemoteServiceLocked();
+ if (serviceComponent == null) {
+ tryRespondWithError(callback, SpeechRecognizer.ERROR_CLIENT);
+ return;
+ }
+
+ 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);
}
});
}
- @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.");
- }
- return null;
- }
- final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
- mRemoteService =
- new RemoteSpeechRecognitionService(getContext(), serviceComponent, mUserId);
+ 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);
}
- return mRemoteService;
}
- private static void tryRespondWithError(IRecognitionServiceManagerCallback callback) {
+ private void handleClientDeath(
+ int callingUid,
+ RemoteSpeechRecognitionService service, boolean invokeCancel) {
+ if (invokeCancel) {
+ service.shutdown();
+ }
+ removeService(callingUid, service);
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ 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;
+ }
+
+ 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);
+ }
+ }
+ }
+
+ 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 3726407..6ad30b5 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 @@
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 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 @@
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 @@
@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 @@
@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 @@
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 @@
mIsActive.set(false);
}
- private void handleEnterSafemode() {
+ private void handleEnterSafeMode() {
handleTeardown();
- mVcnSafemodeCallback.onEnteredSafemode();
+ mVcnCallback.onEnteredSafeMode();
}
private void handleNetworkRequested(
@@ -335,14 +330,31 @@
/** 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 12590eb..9ee072e 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_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.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 @@
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 @@
*
* @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 @@
@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 @@
cancelTeardownTimeoutAlarm();
cancelDisconnectRequestAlarm();
cancelRetryTimeoutAlarm();
- cancelSafemodeAlarm();
+ cancelSafeModeAlarm();
mUnderlyingNetworkTracker.teardown();
}
@@ -928,38 +933,91 @@
}
@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 @@
}
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 @@
Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState");
}
- cancelSafemodeAlarm();
+ cancelSafeModeAlarm();
}
@Override
@@ -1153,7 +1213,7 @@
@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 @@
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 @@
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 @@
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 @@
// Validated connection, clear failed attempt counter
mFailedAttempts = 0;
- cancelSafemodeAlarm();
+ cancelSafeModeAlarm();
}
protected void applyTransform(
@@ -1517,9 +1579,9 @@
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 @@
@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 @@
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 @@
}
}
- // 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 2e98c2c..589a8c3 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 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 @@
}
}
+ 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 @@
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 0000000..5d6d513
--- /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 392f27e..42cb96f 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 @@
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 @@
}, 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 66d5c77..d360916 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 @@
*/
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 e76597d..48ae8d6 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 @@
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 @@
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 @@
}
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 @@
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 @@
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 c688487..dd599c9 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.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 @@
/**
* 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
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 @@
return CODE_NONSYSTEM_USER_EXISTS;
}
- int currentForegroundUser = getCurrentForegroundUser();
+ int currentForegroundUser = getCurrentForegroundUserId();
if (callingUserId != currentForegroundUser
&& mInjector.userManagerIsHeadlessSystemUserMode()
&& currentForegroundUser == UserHandle.USER_SYSTEM) {
@@ -12962,6 +13039,11 @@
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 @@
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 @@
.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 @@
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 @@
.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 @@
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 @@
@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 dd2dd81..2b09d12 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -326,8 +326,6 @@
"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 @@
startAttentionService(context, t);
startRotationResolverService(context, t);
startSystemCaptionsManagerService(context, t);
- startTextToSpeechManagerService(context, t);
// System Speech Recognition Service
if (deviceHasConfigString(context,
@@ -2921,13 +2918,6 @@
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 9666337..e7d0121 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.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.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.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 @@
private static final String TAG = "PeopleService";
- private final DataManager mDataManager;
+ private DataManager mDataManager;
+ @VisibleForTesting
+ ConversationListenerHelper mConversationListenerHelper;
private PackageManagerInternal mPackageManagerInternal;
@@ -71,6 +77,8 @@
super(context);
mDataManager = new DataManager(context);
+ mConversationListenerHelper = new ConversationListenerHelper();
+ mDataManager.addConversationsListener(mConversationListenerHelper);
}
@Override
@@ -148,12 +156,14 @@
* @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 @@
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 1048dfd..75614d6 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.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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ /** 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 1254df9..9109881 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 @@
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.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.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_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.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 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.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 @@
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 @@
// "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 @@
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 @@
() -> 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 @@
// 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 @@
&& 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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
}
@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 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 @@
// 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 @@
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 @@
}
@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 42fa3d4..12894ae 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 @@
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 e80f065..b64528c 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.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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
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 5bb6a42..0e795a9 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 @@
}
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 d825dfd..46b797b 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 a946534..8d54ead 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.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 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 @@
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 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 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 5bb65ab..6add8d1 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 @@
}
@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 fcbd897..1958cb0 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 @@
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 @@
@Override
public int nativeGetVersion() {
- return HdmiControlManager.HDMI_CEC_VERSION_2_0;
+ return mCecVersion;
}
@Override
@@ -132,6 +133,10 @@
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 be584d7..462f3e3 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.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 @@
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 1f66c7c..67d6929 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 @@
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 a112b14..ecff409 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 @@
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 @@
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 50d9f61..7709edb 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.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.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 @@
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 @@
private ShortcutChangeCallback mShortcutChangeCallback;
private ShortcutInfo mShortcutInfo;
private TestInjector mInjector;
+ private TestLooper mLooper;
@Before
public void setUp() throws PackageManager.NameNotFoundException {
@@ -237,7 +243,8 @@
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 @@
}
@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 @@
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 03e60af..ddbe81c 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 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.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
+import java.util.Arrays;
import java.util.Random;
/**
@@ -63,15 +68,18 @@
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 @@
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 @@
}
@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 @@
// 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 15e045c..2fdd63e 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 @@
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 @@
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 @@
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 @@
}
}
+ @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 e4b865f..86d8eee 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 @@
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 bacc932..0000000
--- 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 f805904..0000000
--- 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 9015563..0000000
--- 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
similarity index 61%
rename from core/java/android/speech/tts/ITextToSpeechSession.aidl
rename to telecomm/java/android/telecom/CallScreeningService.aidl
index b2afeb0..87b5138 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 7988b03..deeb433 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.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 @@
.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,10 +167,11 @@
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 CallResponse {
+ public static class ParcelableCallResponse implements Parcelable {
private final boolean mShouldDisallowCall;
private final boolean mShouldRejectCall;
private final boolean mShouldSilenceCall;
@@ -168,13 +179,168 @@
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,
boolean shouldRejectCall,
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 @@
mShouldSkipNotification = shouldSkipNotification;
mShouldSilenceCall = shouldSilenceCall;
mShouldScreenCallViaAudioProcessing = shouldScreenCallViaAudioProcessing;
+ mCallComposerAttachmentsToShow = callComposerAttachmentsToShow;
}
/*
@@ -237,6 +404,49 @@
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 @@
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 @@
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 @@
mShouldSilenceCall,
mShouldSkipCallLog,
mShouldSkipNotification,
- mShouldScreenCallViaAudioProcessing);
+ mShouldScreenCallViaAudioProcessing,
+ mCallComposerAttachmentsToShow);
}
}
}
@@ -423,21 +667,12 @@
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 089a948..942a54e 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -3390,11 +3390,20 @@
* {@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 170ed3e..966ece3 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -759,6 +759,8 @@
@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 @@
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 @@
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 @@
}
}
- 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 feb2ca5..6c6097a 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -1204,15 +1204,25 @@
* 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 83c8f62..0f2e178 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 @@
* {@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 301c2bb..7599e18 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.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 @@
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 3f6162d..6d3fa81 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4601,8 +4601,9 @@
"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 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 @@
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 ee3a0ef..6a9b71d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4461,6 +4461,10 @@
* 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 @@
* 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 @@
@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 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 @@
* <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 @@
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 7773c0a..38b551b 100644
--- a/telephony/java/android/telephony/UiccAccessRule.java
+++ b/telephony/java/android/telephony/UiccAccessRule.java
@@ -35,6 +35,7 @@
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 @@
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 @@
}
/**
+ * 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 @@
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 070fd799..09c07d3 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -32,6 +32,7 @@
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 @@
* 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 @@
* @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 @@
*
* <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 @@
* @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 4435640e..a217d13 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.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 @@
* 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 @@
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 @@
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 @@
* 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 @@
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 @@
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 d4d5301..8eecbca 100644
--- a/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IOptionsRequestCallback.aidl
@@ -27,8 +27,9 @@
* 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 d9734a7..4967e5d 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 @@
* @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 @@
* 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 @@
* 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 ec98be6..908869b 100644
--- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
@@ -19,7 +19,6 @@
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 @@
* {@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 @@
/**
* 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 @@
/**
* Interface used by the framework to respond to OPTIONS requests.
- * @hide
*/
public interface OptionsResponseCallback {
/**
@@ -217,7 +215,7 @@
* 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 @@
/**
* 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 @@
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
* 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 6fbde50..15d19a4 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -125,6 +125,7 @@
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 2a693eb..1358410 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_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.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.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.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 @@
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 @@
@Mock EthernetManager mEthernetManager;
@Mock NetworkPolicyManager mNetworkPolicyManager;
@Mock KeyStore mKeyStore;
+ @Mock IOnSetOemNetworkPreferenceListener mOnSetOemNetworkPreferenceListener;
private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -9422,4 +9429,264 @@
}
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 c04ddd7..1dedc19 100644
--- a/tests/vcn/Android.bp
+++ b/tests/vcn/Android.bp
@@ -21,7 +21,6 @@
"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 0000000..36f5e41
--- /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 3e659d0..5b17aad 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -18,6 +18,7 @@
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 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 @@
}
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
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 7dada9d..7087676 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.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 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 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 0000000..2110d6e
--- /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 c290bff..45b2381 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 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.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.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 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 @@
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 @@
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 @@
doReturn(mConfigReadWriteHelper)
.when(mMockDeps)
.newPersistableBundleLockingReadWriteHelper(any());
+ doReturn(mLocationPermissionChecker)
+ .when(mMockDeps)
+ .newLocationPermissionChecker(eq(mMockContext));
// Setup VCN instance generation
doAnswer((invocation) -> {
@@ -206,6 +219,7 @@
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 @@
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 e715480..69c21b9 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_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 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.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 @@
}
@Test
- public void testEnterStateDoesNotCancelSafemodeAlarm() {
- verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+ public void testEnterStateDoesNotCancelSafeModeAlarm() {
+ verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
@@ -144,7 +154,7 @@
@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 @@
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 @@
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 @@
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 07282c9..17ae19e 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 @@
}
@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 49ce54d..9ea641f 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 @@
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
- verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+ verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
@@ -100,6 +100,6 @@
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 22eab2a..7385204 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 @@
}
@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 6c26075..5b0850b 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 @@
}
@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 ac9ec06..a660735 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 @@
@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 @@
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 @@
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 @@
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 66cbf84..9d33682 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.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 @@
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 @@
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 @@
TEST_SUB_GROUP,
mConfig,
mSubscriptionSnapshot,
- mVcnSafemodeCallback,
+ mVcnCallback,
mDeps);
}
@@ -148,7 +148,7 @@
}
@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 @@
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();
}
}