Merge "Remove OPTED_OUT Secure Setting based on API Council feedback." into qt-dev
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 21f5acb..e8cc96c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3558,6 +3558,12 @@
-->
<string name="config_defaultSystemCaptionsService" translatable="false"></string>
+ <!-- The component name for the system-wide captions manager service.
+ This service must be trusted, as the system binds to it and keeps it running.
+ Example: "com.android.captions/.SystemCaptionsManagerService"
+ -->
+ <string name="config_defaultSystemCaptionsManagerService" translatable="false"></string>
+
<!-- The package name for the incident report approver app.
This app is usually PermissionController or an app that replaces it. When
a bugreport or incident report with EXPLICT-level sharing flags is going to be
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a6841d4..664059a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3409,6 +3409,7 @@
<java-symbol type="string" name="config_defaultContentSuggestionsService" />
<java-symbol type="string" name="config_defaultAttentionService" />
<java-symbol type="string" name="config_defaultSystemCaptionsService" />
+ <java-symbol type="string" name="config_defaultSystemCaptionsManagerService" />
<java-symbol type="string" name="notification_channel_foreground_service" />
<java-symbol type="string" name="foreground_service_app_in_background" />
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index 6f31f9b..8f7d988 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -779,6 +779,7 @@
@Override
public void exit() {
+ mLaunchCaptivePortalAppBroadcastReceiver = null;
hideProvisioningNotification();
}
}
@@ -902,9 +903,10 @@
mLaunchCaptivePortalAppBroadcastReceiver = new CustomIntentReceiver(
ACTION_LAUNCH_CAPTIVE_PORTAL_APP, new Random().nextInt(),
CMD_LAUNCH_CAPTIVE_PORTAL_APP);
+ // Display the sign in notification.
+ // Only do this once for every time we enter MaybeNotifyState. b/122164725
+ showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction);
}
- // Display the sign in notification.
- showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction);
// Retest for captive portal occasionally.
sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index a4870d4..b1e2212 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -325,12 +325,11 @@
* Adds or updates a bubble associated with the provided notification entry.
*
* @param notif the notification associated with this bubble.
- * @param updatePosition whether this update should promote the bubble to the top of the stack.
*/
- public void updateBubble(NotificationEntry notif, boolean updatePosition) {
+ void updateBubble(NotificationEntry notif) {
if (mStackView != null && mBubbleData.getBubble(notif.key) != null) {
// It's an update
- mStackView.updateBubble(notif, updatePosition);
+ mStackView.updateBubble(notif);
} else {
if (mStackView == null) {
mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer);
@@ -403,7 +402,7 @@
return;
}
if (entry.isBubble() && mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) {
- updateBubble(entry, true /* updatePosition */);
+ updateBubble(entry);
}
}
@@ -416,7 +415,7 @@
&& alertAgain(entry, entry.notification.getNotification())) {
entry.setShowInShadeWhenBubble(true);
entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed
- updateBubble(entry, true /* updatePosition */);
+ updateBubble(entry);
mStackView.updateDotVisibility(entry.key);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index a4e1ad7..53e65e6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -595,15 +595,13 @@
/**
* Updates a bubble in the stack.
- *
- * @param entry the entry to update in the stack.
- * @param updatePosition whether this bubble should be moved to top of the stack.
+ * @param entry the entry to update in the stack.
*/
- public void updateBubble(NotificationEntry entry, boolean updatePosition) {
+ public void updateBubble(NotificationEntry entry) {
Bubble b = mBubbleData.getBubble(entry.key);
mBubbleData.updateBubble(entry.key, entry);
- if (updatePosition && !mIsExpanded) {
+ if (!mIsExpanded) {
// If alerting it gets promoted to top of the stack.
if (mBubbleContainer.indexOfChild(b.iconView) != 0) {
mBubbleContainer.moveViewTo(b.iconView, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 5e16721..20f539b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -160,7 +160,7 @@
@Test
public void testAddBubble() {
- mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
@@ -169,13 +169,13 @@
@Test
public void testHasBubbles() {
assertFalse(mBubbleController.hasBubbles());
- mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
}
@Test
public void testRemoveBubble() {
- mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
@@ -189,8 +189,8 @@
@Test
public void testDismissStack() {
- mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
- mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mRow.getEntry());
+ mBubbleController.updateBubble(mRow2.getEntry());
assertTrue(mBubbleController.hasBubbles());
mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
@@ -206,7 +206,7 @@
// Mark it as a bubble and add it explicitly
mEntryListener.onPendingEntryAdded(mRow.getEntry());
- mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mRow.getEntry());
// We should have bubbles & their notifs should show in the shade
assertTrue(mBubbleController.hasBubbles());
@@ -235,8 +235,8 @@
// Mark it as a bubble and add it explicitly
mEntryListener.onPendingEntryAdded(mRow.getEntry());
mEntryListener.onPendingEntryAdded(mRow2.getEntry());
- mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
- mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mRow.getEntry());
+ mBubbleController.updateBubble(mRow2.getEntry());
// We should have bubbles & their notifs should show in the shade
assertTrue(mBubbleController.hasBubbles());
@@ -272,7 +272,7 @@
public void testExpansionRemovesShowInShade() {
// Mark it as a bubble and add it explicitly
mEntryListener.onPendingEntryAdded(mRow.getEntry());
- mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mRow.getEntry());
// We should have bubbles & their notifs should show in the shade
assertTrue(mBubbleController.hasBubbles());
@@ -293,8 +293,8 @@
// Mark it as a bubble and add it explicitly
mEntryListener.onPendingEntryAdded(mRow.getEntry());
mEntryListener.onPendingEntryAdded(mRow2.getEntry());
- mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
- mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mRow.getEntry());
+ mBubbleController.updateBubble(mRow2.getEntry());
verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
// Expand
@@ -333,7 +333,7 @@
// Add the auto expand bubble
mEntryListener.onPendingEntryAdded(mAutoExpandRow.getEntry());
- mBubbleController.updateBubble(mAutoExpandRow.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mAutoExpandRow.getEntry());
// Expansion shouldn't change
verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
@@ -371,7 +371,7 @@
// Add the auto expand bubble
mEntryListener.onPendingEntryAdded(mAutoExpandRow.getEntry());
- mBubbleController.updateBubble(mAutoExpandRow.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mAutoExpandRow.getEntry());
// Expansion should change
verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
@@ -387,7 +387,7 @@
public void testSuppressNotif_FailsNotForeground() {
// Add the suppress notif bubble
mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
- mBubbleController.updateBubble(mSuppressNotifRow.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mSuppressNotifRow.getEntry());
// Should show in shade because we weren't forground
assertTrue(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
@@ -423,7 +423,7 @@
// Add the suppress notif bubble
mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
- mBubbleController.updateBubble(mSuppressNotifRow.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mSuppressNotifRow.getEntry());
// Should NOT show in shade because we were foreground
assertFalse(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
@@ -438,7 +438,7 @@
final String key = mRow.getEntry().key;
mEntryListener.onPendingEntryAdded(mRow.getEntry());
- mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mRow.getEntry());
// Simulate notification cancellation.
mEntryListener.onEntryRemoved(mRow.getEntry(), null /* notificationVisibility (unused) */,
@@ -464,22 +464,22 @@
@Test
public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
- mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_AGED);
verify(mDeleteIntent, never()).send();
}
@Test
public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
- mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
verify(mDeleteIntent, times(1)).send();
}
@Test
public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
- mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
- mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mRow.getEntry());
+ mBubbleController.updateBubble(mRow2.getEntry());
mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
verify(mDeleteIntent, times(2)).send();
}
diff --git a/services/Android.bp b/services/Android.bp
index 567efac..b08d1a8 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -31,6 +31,7 @@
"services.print",
"services.restrictions",
"services.startop",
+ "services.systemcaptions",
"services.usage",
"services.usb",
"services.voiceinteraction",
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 9b02c4e..757c2dc 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -129,7 +129,8 @@
public ContentCaptureManagerService(@NonNull Context context) {
super(context, new FrameworkResourcesServiceNameResolver(context,
com.android.internal.R.string.config_defaultContentCaptureService),
- UserManager.DISALLOW_CONTENT_CAPTURE, /* refreshServiceOnPackageUpdate= */ false);
+ UserManager.DISALLOW_CONTENT_CAPTURE,
+ /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_NO_REFRESH);
DeviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
ActivityThread.currentApplication().getMainExecutor(),
(namespace, key, value) -> onDeviceConfigChange(key, value));
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index feffe2f..0c681df 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -77,6 +77,7 @@
private static final String ATTR_VERSION = "version";
private static final String ATTR_NAME = "name";
private static final String ATTR_DURATION = "duration";
+ private static final String ATTR_EXPLICIT_HEALTH_CHECK_DURATION = "health-check-duration";
private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check";
private static PackageWatchdog sPackageWatchdog;
@@ -95,20 +96,22 @@
private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
// File containing the XML data of monitored packages /data/system/package-watchdog.xml
private final AtomicFile mPolicyFile;
- // Runnable to prune monitored packages that have expired
- private final Runnable mPackageCleanup;
private final ExplicitHealthCheckController mHealthCheckController;
// Flag to control whether explicit health checks are supported or not
@GuardedBy("mLock")
private boolean mIsHealthCheckEnabled = true;
@GuardedBy("mLock")
private boolean mIsPackagesReady;
- // Last SystemClock#uptimeMillis a package clean up was executed.
- // 0 if mPackageCleanup not running.
- private long mUptimeAtLastRescheduleMs;
- // Duration a package cleanup was last scheduled for.
- // 0 if mPackageCleanup not running.
- private long mDurationAtLastReschedule;
+ // SystemClock#uptimeMillis when we last executed #pruneObservers.
+ // 0 if no prune is scheduled.
+ @GuardedBy("mLock")
+ private long mUptimeAtLastPruneMs;
+ // Duration in millis that the last prune was scheduled for.
+ // Used along with #mUptimeAtLastPruneMs after scheduling a prune to determine the remaining
+ // duration before #pruneObservers will be executed.
+ // 0 if no prune is scheduled.
+ @GuardedBy("mLock")
+ private long mDurationAtLastPrune;
private PackageWatchdog(Context context) {
// Needs to be constructed inline
@@ -129,7 +132,6 @@
mPolicyFile = policyFile;
mShortTaskHandler = shortTaskHandler;
mLongTaskHandler = longTaskHandler;
- mPackageCleanup = this::rescheduleCleanup;
mHealthCheckController = controller;
loadFromFile();
}
@@ -171,9 +173,9 @@
if (internalObserver != null) {
internalObserver.mRegisteredObserver = observer;
}
- if (mDurationAtLastReschedule == 0) {
- // Nothing running, schedule
- rescheduleCleanup();
+ if (mDurationAtLastPrune == 0) {
+ // Nothing running, prune
+ pruneAndSchedule();
}
}
}
@@ -208,6 +210,7 @@
List<MonitoredPackage> packages = new ArrayList<>();
for (int i = 0; i < packageNames.size(); i++) {
+ // Health checks not available yet so health check state will start INACTIVE
packages.add(new MonitoredPackage(packageNames.get(i), durationMs, false));
}
@@ -225,9 +228,9 @@
}
}
registerHealthObserver(observer);
- // Always reschedule because we may need to expire packages
- // earlier than we are already scheduled for
- rescheduleCleanup();
+ // Always prune because we may have received packges requiring an earlier
+ // schedule than we are currently scheduled for.
+ pruneAndSchedule();
Slog.i(TAG, "Syncing health check requests, observing packages " + packageNames);
syncRequestsAsync();
saveToFileAsync();
@@ -312,15 +315,18 @@
});
}
- // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file?
+ // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also
+ // avoid holding lock?
// This currently adds about 7ms extra to shutdown thread
/** Writes the package information to file during shutdown. */
public void writeNow() {
- if (!mAllObservers.isEmpty()) {
- mLongTaskHandler.removeCallbacks(this::saveToFile);
- pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastRescheduleMs);
- saveToFile();
- Slog.i(TAG, "Last write to update package durations");
+ synchronized (mLock) {
+ if (!mAllObservers.isEmpty()) {
+ mLongTaskHandler.removeCallbacks(this::saveToFile);
+ pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastPruneMs);
+ saveToFile();
+ Slog.i(TAG, "Last write to update package durations");
+ }
}
}
@@ -450,9 +456,10 @@
private void onSupportedPackages(List<String> supportedPackages) {
boolean shouldUpdateFile = false;
+ boolean shouldPrune = false;
synchronized (mLock) {
- Slog.i(TAG, "Received supported packages " + supportedPackages);
+ Slog.d(TAG, "Received supported packages " + supportedPackages);
Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
while (oit.hasNext()) {
ObserverInternal observer = oit.next();
@@ -461,12 +468,31 @@
while (pit.hasNext()) {
MonitoredPackage monitoredPackage = pit.next();
String packageName = monitoredPackage.mName;
- if (!monitoredPackage.mHasPassedHealthCheck
- && !supportedPackages.contains(packageName)) {
- // Hasn't passed health check but health check is not supported
- Slog.i(TAG, packageName + " does not support health checks, passing");
+ int healthCheckState = monitoredPackage.getHealthCheckState();
+
+ if (healthCheckState != MonitoredPackage.STATE_PASSED) {
+ // Have to update file, we will either transition state or reduce
+ // health check duration
shouldUpdateFile = true;
- monitoredPackage.mHasPassedHealthCheck = true;
+
+ if (supportedPackages.contains(packageName)) {
+ // Supports health check, transition to ACTIVE if not already.
+ // We need to prune packages earlier than already scheduled.
+ shouldPrune = true;
+
+ // TODO: Get healthCheckDuration from supportedPackages
+ long healthCheckDuration = monitoredPackage.mDurationMs;
+ monitoredPackage.mHealthCheckDurationMs = Math.min(healthCheckDuration,
+ monitoredPackage.mDurationMs);
+ Slog.i(TAG, packageName + " health check state is now: ACTIVE("
+ + monitoredPackage.mHealthCheckDurationMs + "ms)");
+ } else {
+ // Does not support health check, transistion to PASSED
+ monitoredPackage.mHasPassedHealthCheck = true;
+ Slog.i(TAG, packageName + " health check state is now: PASSED");
+ }
+ } else {
+ Slog.i(TAG, packageName + " does not support health check, state: PASSED");
}
}
}
@@ -475,6 +501,9 @@
if (shouldUpdateFile) {
saveToFileAsync();
}
+ if (shouldPrune) {
+ pruneAndSchedule();
+ }
}
private Set<String> getPackagesPendingHealthChecksLocked() {
@@ -496,59 +525,64 @@
return packages;
}
- /** Reschedules handler to prune expired packages from observers. */
- private void rescheduleCleanup() {
+ /** Executes {@link #pruneObservers} and schedules the next execution. */
+ private void pruneAndSchedule() {
synchronized (mLock) {
- long nextDurationToScheduleMs = getEarliestPackageExpiryLocked();
+ long nextDurationToScheduleMs = getNextPruneScheduleMillisLocked();
if (nextDurationToScheduleMs == Long.MAX_VALUE) {
- Slog.i(TAG, "No monitored packages, ending package cleanup");
- mDurationAtLastReschedule = 0;
- mUptimeAtLastRescheduleMs = 0;
+ Slog.i(TAG, "No monitored packages, ending prune");
+ mDurationAtLastPrune = 0;
+ mUptimeAtLastPruneMs = 0;
return;
}
long uptimeMs = SystemClock.uptimeMillis();
- // O if mPackageCleanup not running
- long elapsedDurationMs = mUptimeAtLastRescheduleMs == 0
- ? 0 : uptimeMs - mUptimeAtLastRescheduleMs;
- // Less than O if mPackageCleanup unexpectedly didn't run yet even though
- // and we are past the last duration scheduled to run
- long remainingDurationMs = mDurationAtLastReschedule - elapsedDurationMs;
- if (mUptimeAtLastRescheduleMs == 0
+ // O if not running
+ long elapsedDurationMs = mUptimeAtLastPruneMs == 0
+ ? 0 : uptimeMs - mUptimeAtLastPruneMs;
+ // Less than O if unexpectedly didn't run yet even though
+ // we are past the last duration scheduled to run
+ long remainingDurationMs = mDurationAtLastPrune - elapsedDurationMs;
+ if (mUptimeAtLastPruneMs == 0
|| remainingDurationMs <= 0
|| nextDurationToScheduleMs < remainingDurationMs) {
// First schedule or an earlier reschedule
pruneObservers(elapsedDurationMs);
- mShortTaskHandler.removeCallbacks(mPackageCleanup);
- mShortTaskHandler.postDelayed(mPackageCleanup, nextDurationToScheduleMs);
- mDurationAtLastReschedule = nextDurationToScheduleMs;
- mUptimeAtLastRescheduleMs = uptimeMs;
+ // We don't use Handler#hasCallbacks because we want to update the schedule delay
+ mShortTaskHandler.removeCallbacks(this::pruneAndSchedule);
+ mShortTaskHandler.postDelayed(this::pruneAndSchedule, nextDurationToScheduleMs);
+ mDurationAtLastPrune = nextDurationToScheduleMs;
+ mUptimeAtLastPruneMs = uptimeMs;
}
}
}
/**
- * Returns the earliest time a package should expire.
+ * Returns the next time in millis to schedule a prune.
+ *
* @returns Long#MAX_VALUE if there are no observed packages.
*/
- private long getEarliestPackageExpiryLocked() {
+ private long getNextPruneScheduleMillisLocked() {
long shortestDurationMs = Long.MAX_VALUE;
for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex).mPackages;
for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
- long duration = packages.valueAt(pIndex).mDurationMs;
+ MonitoredPackage mp = packages.valueAt(pIndex);
+ long duration = Math.min(mp.mDurationMs, mp.mHealthCheckDurationMs);
if (duration < shortestDurationMs) {
shortestDurationMs = duration;
}
}
}
- Slog.v(TAG, "Earliest package time is " + shortestDurationMs);
+ Slog.i(TAG, "Next prune will be scheduled in " + shortestDurationMs + "ms");
return shortestDurationMs;
}
/**
* Removes {@code elapsedMs} milliseconds from all durations on monitored packages.
- * Discards expired packages and discards observers without any packages.
+ *
+ * <p> Prunes all observers with {@link ObserverInternal#prunePackages} and discards observers
+ * without any packages left.
*/
private void pruneObservers(long elapsedMs) {
if (elapsedMs == 0) {
@@ -559,8 +593,8 @@
Iterator<ObserverInternal> it = mAllObservers.values().iterator();
while (it.hasNext()) {
ObserverInternal observer = it.next();
- List<MonitoredPackage> failedPackages =
- observer.updateMonitoringDurations(elapsedMs);
+ Set<MonitoredPackage> failedPackages =
+ observer.prunePackages(elapsedMs);
if (!failedPackages.isEmpty()) {
onHealthCheckFailed(observer, failedPackages);
}
@@ -570,32 +604,34 @@
}
}
}
- Slog.i(TAG, "Syncing health check requests pruned packages");
+ Slog.i(TAG, "Syncing health check requests, pruned observers");
syncRequestsAsync();
saveToFileAsync();
}
private void onHealthCheckFailed(ObserverInternal observer,
- List<MonitoredPackage> failedPackages) {
+ Set<MonitoredPackage> failedPackages) {
mLongTaskHandler.post(() -> {
synchronized (mLock) {
PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
if (registeredObserver != null) {
PackageManager pm = mContext.getPackageManager();
- for (int i = 0; i < failedPackages.size(); i++) {
- String packageName = failedPackages.get(i).mName;
+ Iterator<MonitoredPackage> it = failedPackages.iterator();
+ while (it.hasNext()) {
+ String failedPackage = it.next().mName;
long versionCode = 0;
- Slog.i(TAG, "Explicit health check failed for package " + packageName);
+ Slog.i(TAG, "Explicit health check failed for package " + failedPackage);
try {
versionCode = pm.getPackageInfo(
- packageName, 0 /* flags */).getLongVersionCode();
+ failedPackage, 0 /* flags */).getLongVersionCode();
} catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "Explicit health check failed but could not find package "
- + packageName);
+ + failedPackage);
// TODO(b/120598832): Skip. We only continue to pass tests for now since
// the tests don't install any packages
}
- registeredObserver.execute(new VersionedPackage(packageName, versionCode));
+ registeredObserver.execute(
+ new VersionedPackage(failedPackage, versionCode));
}
}
}
@@ -670,34 +706,38 @@
}
private void saveToFileAsync() {
- // TODO(b/120598832): Use Handler#hasCallbacks instead of removing and posting
- mLongTaskHandler.removeCallbacks(this::saveToFile);
- mLongTaskHandler.post(this::saveToFile);
+ if (!mLongTaskHandler.hasCallbacks(this::saveToFile)) {
+ mLongTaskHandler.post(this::saveToFile);
+ }
}
/**
* Represents an observer monitoring a set of packages along with the failure thresholds for
* each package.
+ *
+ * <p> Note, the PackageWatchdog#mLock must always be held when reading or writing
+ * instances of this class.
*/
- static class ObserverInternal {
+ //TODO(b/120598832): Remove 'm' from non-private fields
+ private static class ObserverInternal {
public final String mName;
//TODO(b/120598832): Add getter for mPackages
- public final ArrayMap<String, MonitoredPackage> mPackages;
+ @GuardedBy("mLock")
+ public final ArrayMap<String, MonitoredPackage> mPackages = new ArrayMap<>();
@Nullable
+ @GuardedBy("mLock")
public PackageHealthObserver mRegisteredObserver;
ObserverInternal(String name, List<MonitoredPackage> packages) {
mName = name;
- mPackages = new ArrayMap<>();
updatePackages(packages);
}
/**
- * Writes important details to file. Doesn't persist any package failure thresholds.
- *
- * <p>Note that this method is <b>not</b> thread safe. It should only be called from
- * #saveToFile which runs on a single threaded handler.
+ * Writes important {@link MonitoredPackage} details for this observer to file.
+ * Does not persist any package failure thresholds.
*/
+ @GuardedBy("mLock")
public boolean write(XmlSerializer out) {
try {
out.startTag(null, TAG_OBSERVER);
@@ -707,6 +747,8 @@
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATTR_NAME, p.mName);
out.attribute(null, ATTR_DURATION, String.valueOf(p.mDurationMs));
+ out.attribute(null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION,
+ String.valueOf(p.mHealthCheckDurationMs));
out.attribute(null, ATTR_PASSED_HEALTH_CHECK,
String.valueOf(p.mHasPassedHealthCheck));
out.endTag(null, TAG_PACKAGE);
@@ -719,56 +761,68 @@
}
}
+ @GuardedBy("mLock")
public void updatePackages(List<MonitoredPackage> packages) {
- synchronized (mName) {
- for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
- MonitoredPackage p = packages.get(pIndex);
- mPackages.put(p.mName, p);
- }
+ for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
+ MonitoredPackage p = packages.get(pIndex);
+ mPackages.put(p.mName, p);
}
}
/**
* Reduces the monitoring durations of all packages observed by this observer by
- * {@code elapsedMs}. If any duration is less than 0, the package is removed from
- * observation.
+ * {@code elapsedMs}. If any duration is less than 0, the package is removed from
+ * observation. If any health check duration is less than 0, the health check result
+ * is evaluated.
*
- * @returns a {@link List} of packages that were removed from the observer without explicit
+ * @returns a {@link Set} of packages that were removed from the observer without explicit
* health check passing, or an empty list if no package expired for which an explicit health
* check was still pending
*/
- public List<MonitoredPackage> updateMonitoringDurations(long elapsedMs) {
- List<MonitoredPackage> removedPackages = new ArrayList<>();
- synchronized (mName) {
- Iterator<MonitoredPackage> it = mPackages.values().iterator();
- while (it.hasNext()) {
- MonitoredPackage p = it.next();
- long newDuration = p.mDurationMs - elapsedMs;
- if (newDuration > 0) {
- p.mDurationMs = newDuration;
- } else {
- if (!p.mHasPassedHealthCheck) {
- removedPackages.add(p);
- }
- it.remove();
+ @GuardedBy("mLock")
+ private Set<MonitoredPackage> prunePackages(long elapsedMs) {
+ Set<MonitoredPackage> failedPackages = new ArraySet<>();
+ Iterator<MonitoredPackage> it = mPackages.values().iterator();
+ while (it.hasNext()) {
+ MonitoredPackage p = it.next();
+ int healthCheckState = p.getHealthCheckState();
+
+ // Handle health check timeouts
+ if (healthCheckState == MonitoredPackage.STATE_ACTIVE) {
+ // Only reduce duration if state is active
+ p.mHealthCheckDurationMs -= elapsedMs;
+ // Check duration after reducing duration
+ if (p.mHealthCheckDurationMs <= 0) {
+ failedPackages.add(p);
}
}
- return removedPackages;
+
+ // Handle package expiry
+ p.mDurationMs -= elapsedMs;
+ // Check duration after reducing duration
+ if (p.mDurationMs <= 0) {
+ if (healthCheckState == MonitoredPackage.STATE_INACTIVE) {
+ Slog.w(TAG, "Package " + p.mName
+ + " expiring without starting health check, failing");
+ failedPackages.add(p);
+ }
+ it.remove();
+ }
}
+ return failedPackages;
}
/**
* Increments failure counts of {@code packageName}.
* @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
*/
+ @GuardedBy("mLock")
public boolean onPackageFailure(String packageName) {
- synchronized (mName) {
- MonitoredPackage p = mPackages.get(packageName);
- if (p != null) {
- return p.onFailure();
- }
- return false;
+ MonitoredPackage p = mPackages.get(packageName);
+ if (p != null) {
+ return p.onFailure();
}
+ return false;
}
/**
@@ -796,11 +850,14 @@
String packageName = parser.getAttributeValue(null, ATTR_NAME);
long duration = Long.parseLong(
parser.getAttributeValue(null, ATTR_DURATION));
+ long healthCheckDuration = Long.parseLong(
+ parser.getAttributeValue(null,
+ ATTR_EXPLICIT_HEALTH_CHECK_DURATION));
boolean hasPassedHealthCheck = Boolean.parseBoolean(
parser.getAttributeValue(null, ATTR_PASSED_HEALTH_CHECK));
if (!TextUtils.isEmpty(packageName)) {
packages.add(new MonitoredPackage(packageName, duration,
- hasPassedHealthCheck));
+ healthCheckDuration, hasPassedHealthCheck));
}
} catch (NumberFormatException e) {
Slog.wtf(TAG, "Skipping package for observer " + observerName, e);
@@ -819,21 +876,50 @@
}
}
- /** Represents a package along with the time it should be monitored for. */
- static class MonitoredPackage {
+ /**
+ * Represents a package along with the time it should be monitored for.
+ *
+ * <p> Note, the PackageWatchdog#mLock must always be held when reading or writing
+ * instances of this class.
+ */
+ //TODO(b/120598832): Remove 'm' from non-private fields
+ private static class MonitoredPackage {
+ // Health check states
+ // mName has not passed health check but has requested a health check
+ public static int STATE_ACTIVE = 0;
+ // mName has not passed health check and has not requested a health check
+ public static int STATE_INACTIVE = 1;
+ // mName has passed health check
+ public static int STATE_PASSED = 2;
+
public final String mName;
// Whether an explicit health check has passed
+ @GuardedBy("mLock")
public boolean mHasPassedHealthCheck;
// System uptime duration to monitor package
+ @GuardedBy("mLock")
public long mDurationMs;
+ // System uptime duration to check the result of an explicit health check
+ // Initially, MAX_VALUE until we get a value from the health check service
+ // and request health checks.
+ @GuardedBy("mLock")
+ public long mHealthCheckDurationMs = Long.MAX_VALUE;
// System uptime of first package failure
+ @GuardedBy("mLock")
private long mUptimeStartMs;
// Number of failures since mUptimeStartMs
+ @GuardedBy("mLock")
private int mFailures;
MonitoredPackage(String name, long durationMs, boolean hasPassedHealthCheck) {
+ this(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck);
+ }
+
+ MonitoredPackage(String name, long durationMs, long healthCheckDurationMs,
+ boolean hasPassedHealthCheck) {
mName = name;
mDurationMs = durationMs;
+ mHealthCheckDurationMs = healthCheckDurationMs;
mHasPassedHealthCheck = hasPassedHealthCheck;
}
@@ -842,7 +928,8 @@
*
* @return {@code true} if failure count exceeds a threshold, {@code false} otherwise
*/
- public synchronized boolean onFailure() {
+ @GuardedBy("mLock")
+ public boolean onFailure() {
final long now = SystemClock.uptimeMillis();
final long duration = now - mUptimeStartMs;
if (duration > TRIGGER_DURATION_MS) {
@@ -860,5 +947,20 @@
}
return failed;
}
+
+ /**
+ * Returns any of the health check states of {@link #STATE_ACTIVE},
+ * {@link #STATE_INACTIVE} or {@link #STATE_PASSED}
+ */
+ @GuardedBy("mLock")
+ public int getHealthCheckState() {
+ if (mHasPassedHealthCheck) {
+ return STATE_PASSED;
+ } else if (mHealthCheckDurationMs == Long.MAX_VALUE) {
+ return STATE_INACTIVE;
+ } else {
+ return STATE_ACTIVE;
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index 098b0e9..9782f30 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -15,6 +15,7 @@
*/
package com.android.server.infra;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -45,6 +46,8 @@
import com.android.server.SystemService;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -75,6 +78,30 @@
public abstract class AbstractMasterSystemService<M extends AbstractMasterSystemService<M, S>,
S extends AbstractPerUserSystemService<S, M>> extends SystemService {
+ /** On a package update, does not refresh the per-user service in the cache. */
+ public static final int PACKAGE_UPDATE_POLICY_NO_REFRESH = 0;
+
+ /**
+ * On a package update, removes any existing per-user services in the cache.
+ *
+ * <p>This does not immediately recreate these services. It is assumed they will be recreated
+ * for the next user request.
+ */
+ public static final int PACKAGE_UPDATE_POLICY_REFRESH_LAZY = 1;
+
+ /**
+ * On a package update, removes and recreates any existing per-user services in the cache.
+ */
+ public static final int PACKAGE_UPDATE_POLICY_REFRESH_EAGER = 2;
+
+ @IntDef(flag = true, prefix = { "PACKAGE_UPDATE_POLICY_" }, value = {
+ PACKAGE_UPDATE_POLICY_NO_REFRESH,
+ PACKAGE_UPDATE_POLICY_REFRESH_LAZY,
+ PACKAGE_UPDATE_POLICY_REFRESH_EAGER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PackageUpdatePolicy {}
+
/**
* Log tag
*/
@@ -127,8 +154,11 @@
/**
* Whether the per-user service should be removed from the cache when its apk is updated.
+ *
+ * <p>One of {@link #PACKAGE_UPDATE_POLICY_NO_REFRESH},
+ * {@link #PACKAGE_UPDATE_POLICY_REFRESH_LAZY} or {@link #PACKAGE_UPDATE_POLICY_REFRESH_EAGER}.
*/
- private final boolean mRefreshServiceOnPackageUpdate;
+ private final @PackageUpdatePolicy int mPackageUpdatePolicy;
/**
* Name of the service packages whose APK are being updated, keyed by user id.
@@ -154,7 +184,7 @@
@Nullable ServiceNameResolver serviceNameResolver,
@Nullable String disallowProperty) {
this(context, serviceNameResolver, disallowProperty,
- /* refreshServiceOnPackageUpdate=*/ true);
+ /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_REFRESH_LAZY);
}
/**
@@ -167,17 +197,19 @@
* @param disallowProperty when not {@code null}, defines a {@link UserManager} restriction that
* disables the service. <b>NOTE: </b> you'll also need to add it to
* {@code UserRestrictionsUtils.USER_RESTRICTIONS}.
- * @param refreshServiceOnPackageUpdate when {@code true}, the
- * {@link AbstractPerUserSystemService} is removed from the cache (and re-added) when the
- * service package is updated; when {@code false}, the service is untouched during the
- * update.
+ * @param packageUpdatePolicy when {@link #PACKAGE_UPDATE_POLICY_REFRESH_LAZY}, the
+ * {@link AbstractPerUserSystemService} is removed from the cache when the service
+ * package is updated; when {@link #PACKAGE_UPDATE_POLICY_REFRESH_EAGER}, the
+ * {@link AbstractPerUserSystemService} is removed from the cache and immediately
+ * re-added when the service package is updated; when
+ * {@link #PACKAGE_UPDATE_POLICY_NO_REFRESH}, the service is untouched during the update.
*/
protected AbstractMasterSystemService(@NonNull Context context,
@Nullable ServiceNameResolver serviceNameResolver,
- @Nullable String disallowProperty, boolean refreshServiceOnPackageUpdate) {
+ @Nullable String disallowProperty, @PackageUpdatePolicy int packageUpdatePolicy) {
super(context);
- mRefreshServiceOnPackageUpdate = refreshServiceOnPackageUpdate;
+ mPackageUpdatePolicy = packageUpdatePolicy;
mServiceNameResolver = serviceNameResolver;
if (mServiceNameResolver != null) {
@@ -645,7 +677,7 @@
final int size = mServicesCache.size();
pw.print(prefix); pw.print("Debug: "); pw.print(realDebug);
pw.print(" Verbose: "); pw.println(realVerbose);
- pw.print("Refresh on package update: "); pw.println(mRefreshServiceOnPackageUpdate);
+ pw.print("Refresh on package update: "); pw.println(mPackageUpdatePolicy);
if (mUpdatingPackageNames != null) {
pw.print("Packages being updated: "); pw.println(mUpdatingPackageNames);
}
@@ -701,12 +733,21 @@
}
mUpdatingPackageNames.put(userId, packageName);
onServicePackageUpdatingLocked(userId);
- if (mRefreshServiceOnPackageUpdate) {
+ if (mPackageUpdatePolicy != PACKAGE_UPDATE_POLICY_NO_REFRESH) {
if (debug) {
- Slog.d(mTag, "Removing service for user " + userId + " because package "
- + activePackageName + " is being updated");
+ Slog.d(mTag, "Removing service for user " + userId
+ + " because package " + activePackageName
+ + " is being updated");
}
removeCachedServiceLocked(userId);
+
+ if (mPackageUpdatePolicy == PACKAGE_UPDATE_POLICY_REFRESH_EAGER) {
+ if (debug) {
+ Slog.d(mTag, "Eagerly recreating service for user "
+ + userId);
+ }
+ getServiceForUserLocked(userId);
+ }
} else {
if (debug) {
Slog.d(mTag, "Holding service for user " + userId + " while package "
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9d09c4c..106e642 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -254,6 +254,8 @@
"com.android.server.autofill.AutofillManagerService";
private static final String CONTENT_CAPTURE_MANAGER_SERVICE_CLASS =
"com.android.server.contentcapture.ContentCaptureManagerService";
+ private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
+ "com.android.server.systemcaptions.SystemCaptionsManagerService";
private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
"com.android.server.timezone.RulesManagerService$Lifecycle";
private static final String IOT_SERVICE_CLASS =
@@ -1232,6 +1234,8 @@
startContentCaptureService(context);
startAttentionService(context);
+ startSystemCaptionsManagerService(context);
+
// App prediction manager service
traceBeginAndSlog("StartAppPredictionService");
mSystemServiceManager.startService(APP_PREDICTION_MANAGER_SERVICE_CLASS);
@@ -2225,6 +2229,19 @@
}, BOOT_TIMINGS_TRACE_LOG);
}
+ private void startSystemCaptionsManagerService(@NonNull Context context) {
+ String serviceName = context.getString(
+ com.android.internal.R.string.config_defaultSystemCaptionsManagerService);
+ if (TextUtils.isEmpty(serviceName)) {
+ Slog.d(TAG, "SystemCaptionsManagerService disabled because resource is not overlaid");
+ return;
+ }
+
+ traceBeginAndSlog("StartSystemCaptionsManagerService");
+ mSystemServiceManager.startService(SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS);
+ traceEnd();
+ }
+
private void startContentCaptureService(@NonNull Context context) {
// First check if it was explicitly enabled by DeviceConfig
boolean explicitlyEnabled = false;
@@ -2273,7 +2290,7 @@
traceEnd();
}
- static final void startSystemUi(Context context, WindowManagerService windowManager) {
+ private static void startSystemUi(Context context, WindowManagerService windowManager) {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.android.systemui",
"com.android.systemui.SystemUIService"));
diff --git a/services/systemcaptions/Android.bp b/services/systemcaptions/Android.bp
new file mode 100644
index 0000000..4e190b6
--- /dev/null
+++ b/services/systemcaptions/Android.bp
@@ -0,0 +1,5 @@
+java_library_static {
+ name: "services.systemcaptions",
+ srcs: ["java/**/*.java"],
+ libs: ["services.core"],
+}
diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
new file mode 100644
index 0000000..5480b6c
--- /dev/null
+++ b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.systemcaptions;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+/** Manages the connection to the remote system captions manager service. */
+final class RemoteSystemCaptionsManagerService {
+
+ private static final String TAG = RemoteSystemCaptionsManagerService.class.getSimpleName();
+
+ private static final String SERVICE_INTERFACE =
+ "android.service.systemcaptions.SystemCaptionsManagerService";
+
+ private final Object mLock = new Object();
+
+ private final Context mContext;
+ private final Intent mIntent;
+ private final ComponentName mComponentName;
+ private final int mUserId;
+ private final boolean mVerbose;
+ private final Handler mHandler;
+
+ private final RemoteServiceConnection mServiceConnection = new RemoteServiceConnection();
+
+ @GuardedBy("mLock")
+ @Nullable private IBinder mService;
+
+ @GuardedBy("mLock")
+ private boolean mBinding = false;
+
+ @GuardedBy("mLock")
+ private boolean mDestroyed = false;
+
+ RemoteSystemCaptionsManagerService(
+ Context context, ComponentName componentName, int userId, boolean verbose) {
+ mContext = context;
+ mComponentName = componentName;
+ mUserId = userId;
+ mVerbose = verbose;
+ mIntent = new Intent(SERVICE_INTERFACE).setComponent(componentName);
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ void initialize() {
+ if (mVerbose) {
+ Slog.v(TAG, "initialize()");
+ }
+ ensureBound();
+ }
+
+ void destroy() {
+ if (mVerbose) {
+ Slog.v(TAG, "destroy()");
+ }
+
+ synchronized (mLock) {
+ if (mDestroyed) {
+ if (mVerbose) {
+ Slog.v(TAG, "destroy(): Already destroyed");
+ }
+ return;
+ }
+ mDestroyed = true;
+ ensureUnboundLocked();
+ }
+ }
+
+ boolean isDestroyed() {
+ synchronized (mLock) {
+ return mDestroyed;
+ }
+ }
+
+ private void ensureBound() {
+ synchronized (mLock) {
+ if (mService != null || mBinding) {
+ return;
+ }
+
+ if (mVerbose) {
+ Slog.v(TAG, "ensureBound(): binding");
+ }
+ mBinding = true;
+
+ int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
+ boolean willBind = mContext.bindServiceAsUser(mIntent, mServiceConnection, flags,
+ mHandler, new UserHandle(mUserId));
+ if (!willBind) {
+ Slog.w(TAG, "Could not bind to " + mIntent + " with flags " + flags);
+ mBinding = false;
+ mService = null;
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void ensureUnboundLocked() {
+ if (mService == null && !mBinding) {
+ return;
+ }
+
+ mBinding = false;
+ mService = null;
+
+ if (mVerbose) {
+ Slog.v(TAG, "ensureUnbound(): unbinding");
+ }
+ mContext.unbindService(mServiceConnection);
+ }
+
+ private class RemoteServiceConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mLock) {
+ if (mVerbose) {
+ Slog.v(TAG, "onServiceConnected()");
+ }
+ if (mDestroyed || !mBinding) {
+ Slog.wtf(TAG, "onServiceConnected() dispatched after unbindService");
+ return;
+ }
+ mBinding = false;
+ mService = service;
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (mLock) {
+ if (mVerbose) {
+ Slog.v(TAG, "onServiceDisconnected()");
+ }
+ mBinding = true;
+ mService = null;
+ }
+ }
+ }
+}
diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerPerUserService.java b/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerPerUserService.java
new file mode 100644
index 0000000..b503670
--- /dev/null
+++ b/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerPerUserService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.systemcaptions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+/** Manages the captions manager service on a per-user basis. */
+final class SystemCaptionsManagerPerUserService extends
+ AbstractPerUserSystemService<SystemCaptionsManagerPerUserService,
+ SystemCaptionsManagerService> {
+
+ private static final String TAG = SystemCaptionsManagerPerUserService.class.getSimpleName();
+
+ @Nullable
+ @GuardedBy("mLock")
+ private RemoteSystemCaptionsManagerService mRemoteService;
+
+ SystemCaptionsManagerPerUserService(
+ @NonNull SystemCaptionsManagerService master,
+ @NonNull Object lock, boolean disabled, @UserIdInt int userId) {
+ super(master, lock, userId);
+ }
+
+ @Override
+ @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);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void initializeLocked() {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "initialize()");
+ }
+
+ RemoteSystemCaptionsManagerService service = getRemoteServiceLocked();
+ if (service == null && mMaster.verbose) {
+ Slog.v(TAG, "initialize(): Failed to init remote server");
+ }
+ }
+
+ @GuardedBy("mLock")
+ void destroyLocked() {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "destroyLocked()");
+ }
+
+ if (mRemoteService != null) {
+ mRemoteService.destroy();
+ mRemoteService = null;
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private RemoteSystemCaptionsManagerService getRemoteServiceLocked() {
+ if (mRemoteService == null) {
+ String serviceName = getComponentNameLocked();
+ if (serviceName == null) {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "getRemoteServiceLocked(): Not set");
+ }
+ return null;
+ }
+
+ ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+ mRemoteService = new RemoteSystemCaptionsManagerService(
+ getContext(),
+ serviceComponent,
+ mUserId,
+ mMaster.verbose);
+ if (mMaster.verbose) {
+ Slog.v(TAG, "getRemoteServiceLocked(): initialize for user " + mUserId);
+ }
+ mRemoteService.initialize();
+ }
+
+ return mRemoteService;
+ }
+}
diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java b/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java
new file mode 100644
index 0000000..27a116c
--- /dev/null
+++ b/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.systemcaptions;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+
+/** A system service to bind to a remote system captions manager service. */
+public final class SystemCaptionsManagerService extends
+ AbstractMasterSystemService<SystemCaptionsManagerService,
+ SystemCaptionsManagerPerUserService> {
+
+ public SystemCaptionsManagerService(@NonNull Context context) {
+ super(context,
+ new FrameworkResourcesServiceNameResolver(
+ context,
+ com.android.internal.R.string.config_defaultSystemCaptionsManagerService),
+ /*disallowProperty=*/ null,
+ /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
+ }
+
+ @Override
+ public void onStart() {
+ // Do nothing. This service does not publish any local or system services.
+ }
+
+ @Override
+ protected SystemCaptionsManagerPerUserService newServiceLocked(
+ @UserIdInt int resolvedUserId, boolean disabled) {
+ SystemCaptionsManagerPerUserService perUserService =
+ new SystemCaptionsManagerPerUserService(this, mLock, disabled, resolvedUserId);
+ perUserService.initializeLocked();
+ return perUserService;
+ }
+
+ @Override
+ protected void onServiceRemoved(
+ SystemCaptionsManagerPerUserService service, @UserIdInt int userId) {
+ synchronized (mLock) {
+ service.destroyLocked();
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index d2c0705..8c92e84 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1045,6 +1045,7 @@
mIsEmergencyOnly = false;
mLteEarfcnRsrpBoost = 0;
mNrFrequencyRange = FREQUENCY_RANGE_UNKNOWN;
+ mNetworkRegistrationInfos.clear();
addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
.setDomain(NetworkRegistrationInfo.DOMAIN_CS)
.setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 33bb4cc..b308982 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -661,8 +661,10 @@
if (mIsEnabled) {
packages.retainAll(mSupportedPackages);
mRequestedPackages.addAll(packages);
+ mSupportedConsumer.accept(mSupportedPackages);
+ } else {
+ mSupportedConsumer.accept(Collections.emptyList());
}
- mSupportedConsumer.accept(mSupportedPackages);
}
public void setSupportedPackages(List<String> packages) {