diff --git a/api/system-current.txt b/api/system-current.txt index 47e2b5bd5cfe4..01fadfc4e94dc 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6448,6 +6448,7 @@ package android.service.contentcapture { method public void onUserDataRemovalRequest(@NonNull android.view.contentcapture.UserDataRemovalRequest); method public final void setContentCaptureWhitelist(@Nullable java.util.Set, @Nullable java.util.Set); field public static final String SERVICE_INTERFACE = "android.service.contentcapture.ContentCaptureService"; + field public static final String SERVICE_META_DATA = "android.content_capture"; } public final class SnapshotData implements android.os.Parcelable { diff --git a/api/test-current.txt b/api/test-current.txt index 0fcf72702f1b9..9e8b02ae63e89 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -2413,6 +2413,7 @@ package android.service.contentcapture { method public void onUserDataRemovalRequest(@NonNull android.view.contentcapture.UserDataRemovalRequest); method public final void setContentCaptureWhitelist(@Nullable java.util.Set, @Nullable java.util.Set); field public static final String SERVICE_INTERFACE = "android.service.contentcapture.ContentCaptureService"; + field public static final String SERVICE_META_DATA = "android.content_capture"; } public final class SnapshotData implements android.os.Parcelable { diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index 6f4114d1d81ad..df113979bacf4 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -78,6 +78,21 @@ public abstract class ContentCaptureService extends Service { public static final String SERVICE_INTERFACE = "android.service.contentcapture.ContentCaptureService"; + /** + * Name under which a ContentCaptureService component publishes information about itself. + * + *

This meta-data should reference an XML resource containing a + * <{@link + * android.R.styleable#ContentCaptureService content-capture-service}> tag. + * + *

This is a a sample XML file configuring a ContentCaptureService: + *

 <content-capture-service
+     *     android:settingsActivity="foo.bar.SettingsActivity"
+     *     . . .
+     * />
+ */ + public static final String SERVICE_META_DATA = "android.content_capture"; + private Handler mHandler; private IContentCaptureServiceCallback mCallback; diff --git a/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java b/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java new file mode 100644 index 0000000000000..6ecd82f50fdbf --- /dev/null +++ b/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java @@ -0,0 +1,171 @@ +/* + * 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 android.service.contentcapture; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.AppGlobals; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ServiceInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.RemoteException; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.PrintWriter; + +/** + * {@link ServiceInfo} and meta-data about an {@link ContentCaptureService}. + * + * @hide + */ +public final class ContentCaptureServiceInfo { + + private static final String TAG = ContentCaptureServiceInfo.class.getSimpleName(); + private static final String XML_TAG_SERVICE = "content-capture-service"; + + private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, boolean isTemp, + @UserIdInt int userId) throws PackageManager.NameNotFoundException { + int flags = PackageManager.GET_META_DATA; + if (!isTemp) { + flags |= PackageManager.MATCH_SYSTEM_ONLY; + } + + ServiceInfo si = null; + try { + si = AppGlobals.getPackageManager().getServiceInfo(comp, flags, userId); + } catch (RemoteException e) { + } + if (si == null) { + throw new NameNotFoundException("Could not get serviceInfo for " + + (isTemp ? " (temp)" : "(default system)") + + " " + comp.flattenToShortString()); + } + return si; + } + + @NonNull + private final ServiceInfo mServiceInfo; + + @Nullable + private final String mSettingsActivity; + + public ContentCaptureServiceInfo(@NonNull Context context, @NonNull ComponentName comp, + boolean isTemporaryService, @UserIdInt int userId) + throws PackageManager.NameNotFoundException { + this(context, getServiceInfoOrThrow(comp, isTemporaryService, userId)); + } + + private ContentCaptureServiceInfo(@NonNull Context context, @NonNull ServiceInfo si) { + // Check for permissions. + if (!Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE.equals(si.permission)) { + Slog.w(TAG, "ContentCaptureService from '" + si.packageName + + "' does not require permission " + + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE); + throw new SecurityException("Service does not require permission " + + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE); + } + + mServiceInfo = si; + + // Get the metadata, if declared. + final XmlResourceParser parser = si.loadXmlMetaData(context.getPackageManager(), + ContentCaptureService.SERVICE_META_DATA); + if (parser == null) { + mSettingsActivity = null; + return; + } + + String settingsActivity = null; + + try { + final Resources resources = context.getPackageManager().getResourcesForApplication( + si.applicationInfo); + + int type = 0; + while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { + type = parser.next(); + } + + if (XML_TAG_SERVICE.equals(parser.getName())) { + final AttributeSet allAttributes = Xml.asAttributeSet(parser); + TypedArray afsAttributes = null; + try { + afsAttributes = resources.obtainAttributes(allAttributes, + com.android.internal.R.styleable.ContentCaptureService); + settingsActivity = afsAttributes.getString( + R.styleable.ContentCaptureService_settingsActivity); + } finally { + if (afsAttributes != null) { + afsAttributes.recycle(); + } + } + } else { + Log.e(TAG, "Meta-data does not start with content-capture-service tag"); + } + } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) { + Log.e(TAG, "Error parsing auto fill service meta-data", e); + } + + mSettingsActivity = settingsActivity; + } + + public ServiceInfo getServiceInfo() { + return mServiceInfo; + } + + @Nullable + public String getSettingsActivity() { + return mSettingsActivity; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getClass().getSimpleName()); + builder.append("[").append(mServiceInfo); + builder.append(", settings:").append(mSettingsActivity); + return builder.toString(); + } + + /** + * Dumps it! + */ + public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { + pw.print(prefix); + pw.print("Component: "); + pw.println(getServiceInfo().getComponentName()); + pw.print(prefix); + pw.print("Settings: "); + pw.println(mSettingsActivity); + } +} diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 743496fdffb5e..9d48fe384f3f5 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8108,7 +8108,7 @@ + + + + + + + + + + diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 96123468d245e..22e586594ba56 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -889,8 +889,6 @@ final class AutofillManagerServiceImpl } else { pw.println(); mInfo.dump(prefix2, pw); - pw.print(prefix); pw.print("Service Label: "); pw.println(getServiceLabelLocked()); - pw.print(prefix); pw.print("Target SDK: "); pw.println(getTargedSdkLocked()); } pw.print(prefix); pw.print("Default component: "); pw.println(getContext() .getString(R.string.config_defaultAutofillService)); diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index c423f9c87d298..d7d97b39959f6 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -27,12 +27,10 @@ import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTE import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE; -import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; -import android.app.AppGlobals; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; import android.content.ComponentName; @@ -44,12 +42,12 @@ import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; -import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.service.contentcapture.ActivityEvent; import android.service.contentcapture.ActivityEvent.ActivityEventType; import android.service.contentcapture.ContentCaptureService; +import android.service.contentcapture.ContentCaptureServiceInfo; import android.service.contentcapture.IContentCaptureServiceCallback; import android.service.contentcapture.SnapshotData; import android.util.ArrayMap; @@ -107,6 +105,9 @@ final class ContentCapturePerUserService @GuardedBy("mLock") private boolean mZombie; + @GuardedBy("mLock") + private ContentCaptureServiceInfo mInfo; + // TODO(b/111276913): add mechanism to prune stale sessions, similar to Autofill's ContentCapturePerUserService(@NonNull ContentCaptureManagerService master, @@ -144,33 +145,9 @@ final class ContentCapturePerUserService @Override // from PerUserSystemService protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) throws NameNotFoundException { - - int flags = PackageManager.GET_META_DATA; - final boolean isTemp = isTemporaryServiceSetLocked(); - if (!isTemp) { - flags |= PackageManager.MATCH_SYSTEM_ONLY; - } - - ServiceInfo si; - try { - si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, flags, mUserId); - } catch (RemoteException e) { - Slog.w(TAG, "Could not get service for " + serviceComponent + ": " + e); - return null; - } - if (si == null) { - Slog.w(TAG, "Could not get serviceInfo for " + (isTemp ? " (temp)" : "(default system)") - + " " + serviceComponent.flattenToShortString()); - return null; - } - if (!Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE.equals(si.permission)) { - Slog.w(TAG, "ContentCaptureService from '" + si.packageName - + "' does not require permission " - + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE); - throw new SecurityException("Service does not require permission " - + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE); - } - return si; + mInfo = new ContentCaptureServiceInfo(getContext(), serviceComponent, + isTemporaryServiceSetLocked(), mUserId); + return mInfo.getServiceInfo(); } @Override // from PerUserSystemService @@ -490,9 +467,16 @@ final class ContentCapturePerUserService protected void dumpLocked(String prefix, PrintWriter pw) { super.dumpLocked(prefix, pw); + final String prefix2 = prefix + " "; + pw.print(prefix); pw.print("Service Info: "); + if (mInfo == null) { + pw.println("N/A"); + } else { + pw.println(); + mInfo.dump(prefix2, pw); + } pw.print(prefix); pw.print("Zombie: "); pw.println(mZombie); - final String prefix2 = prefix + " "; if (mRemoteService != null) { pw.print(prefix); pw.println("remote service:"); mRemoteService.dump(prefix2, pw); diff --git a/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java b/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java index cd9ebcda009cb..ac07e9d6b0b73 100644 --- a/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java +++ b/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java @@ -327,6 +327,10 @@ public abstract class AbstractPerUserSystemService