Add system captions manager service
This service connects to a remote system captions manager service. This
service is responsible for enabling system captions when the user
requests them. As the system binds to it, this service will be
persistent.
Bug: 128925852
Test: Manual. I created an implementation of the service.
Change-Id: Iafde1bb68f4754d8167624f47c6833d43c0ec336
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ec2a6ae..7b0c631 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3564,6 +3564,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 3e23640..e455625 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3411,6 +3411,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/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/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();
+ }
+ }
+}