| /* |
| * Copyright (C) 2018 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.contentcapture; |
| |
| import static android.service.contentcapture.ContentCaptureService.setClientState; |
| import static android.view.contentcapture.ContentCaptureSession.NO_SESSION_ID; |
| import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED; |
| import static android.view.contentcapture.ContentCaptureSession.STATE_DUPLICATED_ID; |
| import static android.view.contentcapture.ContentCaptureSession.STATE_INTERNAL_ERROR; |
| import static android.view.contentcapture.ContentCaptureSession.STATE_NOT_WHITELISTED; |
| import static android.view.contentcapture.ContentCaptureSession.STATE_NO_SERVICE; |
| |
| import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeServiceEvent; |
| import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeSessionEvent; |
| import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeSetWhitelistEvent; |
| import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT; |
| import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA; |
| import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.UserIdInt; |
| import android.app.ActivityManagerInternal; |
| import android.app.assist.AssistContent; |
| import android.app.assist.AssistStructure; |
| import android.content.ComponentName; |
| import android.content.ContentCaptureOptions; |
| import android.content.pm.ActivityPresentationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.pm.ServiceInfo; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| 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.FlushMetrics; |
| import android.service.contentcapture.IContentCaptureServiceCallback; |
| import android.service.contentcapture.IDataShareCallback; |
| import android.service.contentcapture.SnapshotData; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| import android.util.SparseBooleanArray; |
| import android.view.contentcapture.ContentCaptureCondition; |
| import android.view.contentcapture.DataRemovalRequest; |
| import android.view.contentcapture.DataShareRequest; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.os.IResultReceiver; |
| import com.android.internal.util.FrameworkStatsLog; |
| import com.android.server.LocalServices; |
| import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks; |
| import com.android.server.infra.AbstractPerUserSystemService; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Per-user instance of {@link ContentCaptureManagerService}. |
| */ |
| final class ContentCapturePerUserService |
| extends |
| AbstractPerUserSystemService<ContentCapturePerUserService, ContentCaptureManagerService> |
| implements ContentCaptureServiceCallbacks { |
| |
| private static final String TAG = ContentCapturePerUserService.class.getSimpleName(); |
| |
| @GuardedBy("mLock") |
| private final SparseArray<ContentCaptureServerSession> mSessions = new SparseArray<>(); |
| |
| /** |
| * Reference to the remote service. |
| * |
| * <p>It's set in the constructor, but it's also updated when the service's updated in the |
| * master's cache (for example, because a temporary service was set). |
| */ |
| @GuardedBy("mLock") |
| @Nullable |
| RemoteContentCaptureService mRemoteService; |
| |
| private final ContentCaptureServiceRemoteCallback mRemoteServiceCallback = |
| new ContentCaptureServiceRemoteCallback(); |
| |
| /** |
| * List of conditions keyed by package. |
| */ |
| @GuardedBy("mLock") |
| private final ArrayMap<String, ArraySet<ContentCaptureCondition>> mConditionsByPkg = |
| new ArrayMap<>(); |
| |
| /** |
| * When {@code true}, remote service died but service state is kept so it's restored after |
| * the system re-binds to it. |
| */ |
| @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, |
| @NonNull Object lock, boolean disabled, @UserIdInt int userId) { |
| super(master, lock, userId); |
| updateRemoteServiceLocked(disabled); |
| } |
| |
| /** |
| * Updates the reference to the remote service. |
| */ |
| private void updateRemoteServiceLocked(boolean disabled) { |
| if (mMaster.verbose) Slog.v(TAG, "updateRemoteService(disabled=" + disabled + ")"); |
| if (mRemoteService != null) { |
| if (mMaster.debug) Slog.d(TAG, "updateRemoteService(): destroying old remote service"); |
| mRemoteService.destroy(); |
| mRemoteService = null; |
| resetContentCaptureWhitelistLocked(); |
| } |
| |
| // Updates the component name |
| final ComponentName serviceComponentName = updateServiceInfoLocked(); |
| |
| if (serviceComponentName == null) { |
| if (mMaster.debug) Slog.d(TAG, "updateRemoteService(): no service component name"); |
| return; |
| } |
| |
| if (!disabled) { |
| if (mMaster.debug) { |
| Slog.d(TAG, "updateRemoteService(): creating new remote service for " |
| + serviceComponentName); |
| } |
| mRemoteService = new RemoteContentCaptureService(mMaster.getContext(), |
| ContentCaptureService.SERVICE_INTERFACE, serviceComponentName, |
| mRemoteServiceCallback, mUserId, this, mMaster.isBindInstantServiceAllowed(), |
| mMaster.verbose, mMaster.mDevCfgIdleUnbindTimeoutMs); |
| } |
| } |
| |
| @Override // from PerUserSystemService |
| protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) |
| throws NameNotFoundException { |
| mInfo = new ContentCaptureServiceInfo(getContext(), serviceComponent, |
| isTemporaryServiceSetLocked(), mUserId); |
| return mInfo.getServiceInfo(); |
| } |
| |
| @Override // from PerUserSystemService |
| @GuardedBy("mLock") |
| protected boolean updateLocked(boolean disabled) { |
| final boolean disabledStateChanged = super.updateLocked(disabled); |
| if (disabledStateChanged) { |
| // update session content capture enabled state. |
| for (int i = 0; i < mSessions.size(); i++) { |
| mSessions.valueAt(i).setContentCaptureEnabledLocked(!disabled); |
| } |
| } |
| destroyLocked(); |
| updateRemoteServiceLocked(disabled); |
| return disabledStateChanged; |
| } |
| |
| @Override // from ContentCaptureServiceCallbacks |
| public void onServiceDied(@NonNull RemoteContentCaptureService service) { |
| // Don't do anything; eventually the system will bind to it again... |
| Slog.w(TAG, "remote service died: " + service); |
| synchronized (mLock) { |
| mZombie = true; |
| } |
| } |
| |
| /** |
| * Called after the remote service connected, it's used to restore state from a 'zombie' |
| * service (i.e., after it died). |
| */ |
| void onConnected() { |
| synchronized (mLock) { |
| if (mZombie) { |
| // Sanity check - shouldn't happen |
| if (mRemoteService == null) { |
| Slog.w(TAG, "Cannot ressurect sessions because remote service is null"); |
| return; |
| } |
| |
| mZombie = false; |
| resurrectSessionsLocked(); |
| } |
| } |
| } |
| |
| private void resurrectSessionsLocked() { |
| final int numSessions = mSessions.size(); |
| if (mMaster.debug) { |
| Slog.d(TAG, "Ressurrecting remote service (" + mRemoteService + ") on " |
| + numSessions + " sessions"); |
| } |
| |
| for (int i = 0; i < numSessions; i++) { |
| final ContentCaptureServerSession session = mSessions.valueAt(i); |
| session.resurrectLocked(); |
| } |
| } |
| |
| void onPackageUpdatingLocked() { |
| final int numSessions = mSessions.size(); |
| if (mMaster.debug) { |
| Slog.d(TAG, "Pausing " + numSessions + " sessions while package is updating"); |
| } |
| for (int i = 0; i < numSessions; i++) { |
| final ContentCaptureServerSession session = mSessions.valueAt(i); |
| session.pauseLocked(); |
| } |
| } |
| |
| void onPackageUpdatedLocked() { |
| updateRemoteServiceLocked(!isEnabledLocked()); |
| resurrectSessionsLocked(); |
| } |
| |
| @GuardedBy("mLock") |
| public void startSessionLocked(@NonNull IBinder activityToken, |
| @NonNull ActivityPresentationInfo activityPresentationInfo, int sessionId, int uid, |
| int flags, @NonNull IResultReceiver clientReceiver) { |
| if (activityPresentationInfo == null) { |
| Slog.w(TAG, "basic activity info is null"); |
| setClientState(clientReceiver, STATE_DISABLED | STATE_INTERNAL_ERROR, |
| /* binder= */ null); |
| return; |
| } |
| final int taskId = activityPresentationInfo.taskId; |
| final int displayId = activityPresentationInfo.displayId; |
| final ComponentName componentName = activityPresentationInfo.componentName; |
| final boolean whiteListed = mMaster.mGlobalContentCaptureOptions.isWhitelisted(mUserId, |
| componentName) || mMaster.mGlobalContentCaptureOptions.isWhitelisted(mUserId, |
| componentName.getPackageName()); |
| final ComponentName serviceComponentName = getServiceComponentName(); |
| final boolean enabled = isEnabledLocked(); |
| if (mMaster.mRequestsHistory != null) { |
| final String historyItem = |
| "id=" + sessionId + " uid=" + uid |
| + " a=" + ComponentName.flattenToShortString(componentName) |
| + " t=" + taskId + " d=" + displayId |
| + " s=" + ComponentName.flattenToShortString(serviceComponentName) |
| + " u=" + mUserId + " f=" + flags + (enabled ? "" : " (disabled)") |
| + " w=" + whiteListed; |
| mMaster.mRequestsHistory.log(historyItem); |
| } |
| |
| if (!enabled) { |
| // TODO: it would be better to split in differet reasons, like |
| // STATE_DISABLED_NO and STATE_DISABLED_BY_DEVICE_POLICY |
| setClientState(clientReceiver, STATE_DISABLED | STATE_NO_SERVICE, |
| /* binder= */ null); |
| // Log metrics. |
| writeSessionEvent(sessionId, |
| FrameworkStatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED, |
| STATE_DISABLED | STATE_NO_SERVICE, serviceComponentName, |
| componentName, /* isChildSession= */ false); |
| return; |
| } |
| if (serviceComponentName == null) { |
| // TODO(b/111276913): this happens when the system service is starting, we should |
| // probably handle it in a more elegant way (like waiting for boot_complete or |
| // something like that |
| if (mMaster.debug) { |
| Slog.d(TAG, "startSession(" + activityToken + "): hold your horses"); |
| } |
| return; |
| } |
| |
| if (!whiteListed) { |
| if (mMaster.debug) { |
| Slog.d(TAG, "startSession(" + componentName + "): package or component " |
| + "not whitelisted"); |
| } |
| setClientState(clientReceiver, STATE_DISABLED | STATE_NOT_WHITELISTED, |
| /* binder= */ null); |
| // Log metrics. |
| writeSessionEvent(sessionId, |
| FrameworkStatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED, |
| STATE_DISABLED | STATE_NOT_WHITELISTED, serviceComponentName, |
| componentName, /* isChildSession= */ false); |
| return; |
| } |
| |
| final ContentCaptureServerSession existingSession = mSessions.get(sessionId); |
| if (existingSession != null) { |
| Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken |
| + ": ignoring because it already exists for " + existingSession.mActivityToken); |
| setClientState(clientReceiver, STATE_DISABLED | STATE_DUPLICATED_ID, |
| /* binder=*/ null); |
| // Log metrics. |
| writeSessionEvent(sessionId, |
| FrameworkStatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED, |
| STATE_DISABLED | STATE_DUPLICATED_ID, |
| serviceComponentName, componentName, /* isChildSession= */ false); |
| return; |
| } |
| |
| if (mRemoteService == null) { |
| updateRemoteServiceLocked(/* disabled= */ false); // already checked for isEnabled |
| } |
| |
| if (mRemoteService == null) { |
| Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken |
| + ": ignoring because service is not set"); |
| setClientState(clientReceiver, STATE_DISABLED | STATE_NO_SERVICE, |
| /* binder= */ null); |
| // Log metrics. |
| writeSessionEvent(sessionId, |
| FrameworkStatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED, |
| STATE_DISABLED | STATE_NO_SERVICE, serviceComponentName, |
| componentName, /* isChildSession= */ false); |
| return; |
| } |
| |
| // Make sure service is bound, just in case the initial connection failed somehow |
| mRemoteService.ensureBoundLocked(); |
| |
| final ContentCaptureServerSession newSession = new ContentCaptureServerSession(mLock, |
| activityToken, this, componentName, clientReceiver, taskId, displayId, sessionId, |
| uid, flags); |
| if (mMaster.verbose) { |
| Slog.v(TAG, "startSession(): new session for " |
| + ComponentName.flattenToShortString(componentName) + " and id " + sessionId); |
| } |
| mSessions.put(sessionId, newSession); |
| newSession.notifySessionStartedLocked(clientReceiver); |
| } |
| |
| @GuardedBy("mLock") |
| public void finishSessionLocked(int sessionId) { |
| if (!isEnabledLocked()) { |
| return; |
| } |
| |
| final ContentCaptureServerSession session = mSessions.get(sessionId); |
| if (session == null) { |
| if (mMaster.debug) { |
| Slog.d(TAG, "finishSession(): no session with id" + sessionId); |
| } |
| return; |
| } |
| if (mMaster.verbose) Slog.v(TAG, "finishSession(): id=" + sessionId); |
| session.removeSelfLocked(/* notifyRemoteService= */ true); |
| } |
| |
| @GuardedBy("mLock") |
| public void removeDataLocked(@NonNull DataRemovalRequest request) { |
| if (!isEnabledLocked()) { |
| return; |
| } |
| assertCallerLocked(request.getPackageName()); |
| mRemoteService.onDataRemovalRequest(request); |
| } |
| |
| @GuardedBy("mLock") |
| public void onDataSharedLocked(@NonNull DataShareRequest request, |
| IDataShareCallback.Stub dataShareCallback) { |
| if (!isEnabledLocked()) { |
| return; |
| } |
| assertCallerLocked(request.getPackageName()); |
| mRemoteService.onDataShareRequest(request, dataShareCallback); |
| } |
| |
| @GuardedBy("mLock") |
| @Nullable |
| public ComponentName getServiceSettingsActivityLocked() { |
| if (mInfo == null) return null; |
| |
| final String activityName = mInfo.getSettingsActivity(); |
| if (activityName == null) return null; |
| |
| final String packageName = mInfo.getServiceInfo().packageName; |
| return new ComponentName(packageName, activityName); |
| } |
| |
| /** |
| * Asserts the component is owned by the caller. |
| */ |
| @GuardedBy("mLock") |
| private void assertCallerLocked(@NonNull String packageName) { |
| final PackageManager pm = getContext().getPackageManager(); |
| final int callingUid = Binder.getCallingUid(); |
| final int packageUid; |
| try { |
| packageUid = pm.getPackageUidAsUser(packageName, UserHandle.getCallingUserId()); |
| } catch (NameNotFoundException e) { |
| throw new SecurityException("Could not verify UID for " + packageName); |
| } |
| if (callingUid != packageUid && !LocalServices.getService(ActivityManagerInternal.class) |
| .hasRunningActivity(callingUid, packageName)) { |
| final String[] packages = pm.getPackagesForUid(callingUid); |
| final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid; |
| Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid |
| + ") passed package (" + packageName + ") owned by UID " + packageUid); |
| |
| throw new SecurityException("Invalid package: " + packageName); |
| } |
| } |
| |
| @GuardedBy("mLock") |
| public boolean sendActivityAssistDataLocked(@NonNull IBinder activityToken, |
| @NonNull Bundle data) { |
| final int id = getSessionId(activityToken); |
| if (id != NO_SESSION_ID) { |
| final ContentCaptureServerSession session = mSessions.get(id); |
| final Bundle assistData = data.getBundle(ASSIST_KEY_DATA); |
| final AssistStructure assistStructure = data.getParcelable(ASSIST_KEY_STRUCTURE); |
| final AssistContent assistContent = data.getParcelable(ASSIST_KEY_CONTENT); |
| final SnapshotData snapshotData = new SnapshotData(assistData, |
| assistStructure, assistContent); |
| session.sendActivitySnapshotLocked(snapshotData); |
| return true; |
| } else { |
| Slog.e(TAG, "Failed to notify activity assist data for activity: " + activityToken); |
| } |
| return false; |
| } |
| |
| @GuardedBy("mLock") |
| public void removeSessionLocked(int sessionId) { |
| mSessions.remove(sessionId); |
| } |
| |
| @GuardedBy("mLock") |
| public boolean isContentCaptureServiceForUserLocked(int uid) { |
| return uid == getServiceUidLocked(); |
| } |
| |
| @GuardedBy("mLock") |
| private ContentCaptureServerSession getSession(@NonNull IBinder activityToken) { |
| for (int i = 0; i < mSessions.size(); i++) { |
| final ContentCaptureServerSession session = mSessions.valueAt(i); |
| if (session.mActivityToken.equals(activityToken)) { |
| return session; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Destroys the service and all state associated with it. |
| * |
| * <p>Called when the service was disabled (for example, if the settings change). |
| */ |
| @GuardedBy("mLock") |
| public void destroyLocked() { |
| if (mMaster.debug) Slog.d(TAG, "destroyLocked()"); |
| if (mRemoteService != null) { |
| mRemoteService.destroy(); |
| } |
| destroySessionsLocked(); |
| } |
| |
| @GuardedBy("mLock") |
| void destroySessionsLocked() { |
| final int numSessions = mSessions.size(); |
| for (int i = 0; i < numSessions; i++) { |
| final ContentCaptureServerSession session = mSessions.valueAt(i); |
| session.destroyLocked(/* notifyRemoteService= */ true); |
| } |
| mSessions.clear(); |
| } |
| |
| @GuardedBy("mLock") |
| void listSessionsLocked(ArrayList<String> output) { |
| final int numSessions = mSessions.size(); |
| for (int i = 0; i < numSessions; i++) { |
| final ContentCaptureServerSession session = mSessions.valueAt(i); |
| output.add(session.toShortString()); |
| } |
| } |
| |
| @GuardedBy("mLock") |
| @Nullable |
| ArraySet<ContentCaptureCondition> getContentCaptureConditionsLocked( |
| @NonNull String packageName) { |
| return mConditionsByPkg.get(packageName); |
| } |
| |
| @GuardedBy("mLock") |
| void onActivityEventLocked(@NonNull ComponentName componentName, @ActivityEventType int type) { |
| if (mRemoteService == null) { |
| if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): no remote service"); |
| return; |
| } |
| final ActivityEvent event = new ActivityEvent(componentName, type); |
| |
| if (mMaster.verbose) Slog.v(mTag, "onActivityEvent(): " + event); |
| |
| mRemoteService.onActivityLifecycleEvent(event); |
| } |
| |
| @Override |
| 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); |
| |
| if (mRemoteService != null) { |
| pw.print(prefix); pw.println("remote service:"); |
| mRemoteService.dump(prefix2, pw); |
| } |
| |
| if (mSessions.size() == 0) { |
| pw.print(prefix); pw.println("no sessions"); |
| } else { |
| final int sessionsSize = mSessions.size(); |
| pw.print(prefix); pw.print("number sessions: "); pw.println(sessionsSize); |
| for (int i = 0; i < sessionsSize; i++) { |
| pw.print(prefix); pw.print("#"); pw.println(i); |
| final ContentCaptureServerSession session = mSessions.valueAt(i); |
| session.dumpLocked(prefix2, pw); |
| pw.println(); |
| } |
| } |
| } |
| |
| /** |
| * Returns the session id associated with the given activity. |
| */ |
| @GuardedBy("mLock") |
| private int getSessionId(@NonNull IBinder activityToken) { |
| for (int i = 0; i < mSessions.size(); i++) { |
| ContentCaptureServerSession session = mSessions.valueAt(i); |
| if (session.isActivitySession(activityToken)) { |
| return mSessions.keyAt(i); |
| } |
| } |
| return NO_SESSION_ID; |
| } |
| |
| /** |
| * Resets the content capture whitelist. |
| */ |
| @GuardedBy("mLock") |
| private void resetContentCaptureWhitelistLocked() { |
| if (mMaster.verbose) { |
| Slog.v(TAG, "resetting content capture whitelist"); |
| } |
| mMaster.mGlobalContentCaptureOptions.resetWhitelist(mUserId); |
| } |
| |
| private final class ContentCaptureServiceRemoteCallback extends |
| IContentCaptureServiceCallback.Stub { |
| |
| @Override |
| public void setContentCaptureWhitelist(List<String> packages, |
| List<ComponentName> activities) { |
| // TODO(b/122595322): add CTS test for when it's null |
| if (mMaster.verbose) { |
| Slog.v(TAG, "setContentCaptureWhitelist(" + (packages == null |
| ? "null_packages" : packages.size() + " packages") |
| + ", " + (activities == null |
| ? "null_activities" : activities.size() + " activities") + ")" |
| + " for user " + mUserId); |
| } |
| mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities); |
| writeSetWhitelistEvent(getServiceComponentName(), packages, activities); |
| |
| // Must disable session that are not the whitelist anymore... |
| final int numSessions = mSessions.size(); |
| if (numSessions <= 0) return; |
| |
| // ...but without holding the lock on mGlobalContentCaptureOptions |
| final SparseBooleanArray blacklistedSessions = new SparseBooleanArray(numSessions); |
| |
| for (int i = 0; i < numSessions; i++) { |
| final ContentCaptureServerSession session = mSessions.valueAt(i); |
| final boolean whitelisted = mMaster.mGlobalContentCaptureOptions |
| .isWhitelisted(mUserId, session.appComponentName); |
| if (!whitelisted) { |
| final int sessionId = mSessions.keyAt(i); |
| if (mMaster.debug) { |
| Slog.d(TAG, "marking session " + sessionId + " (" + session.appComponentName |
| + ") for un-whitelisting"); |
| } |
| blacklistedSessions.append(sessionId, true); |
| } |
| } |
| final int numBlacklisted = blacklistedSessions.size(); |
| |
| if (numBlacklisted <= 0) return; |
| |
| synchronized (mLock) { |
| for (int i = 0; i < numBlacklisted; i++) { |
| final int sessionId = blacklistedSessions.keyAt(i); |
| if (mMaster.debug) Slog.d(TAG, "un-whitelisting " + sessionId); |
| final ContentCaptureServerSession session = mSessions.get(sessionId); |
| session.setContentCaptureEnabledLocked(false); |
| } |
| } |
| } |
| |
| @Override |
| public void setContentCaptureConditions(String packageName, |
| List<ContentCaptureCondition> conditions) { |
| if (mMaster.verbose) { |
| Slog.v(TAG, "setContentCaptureConditions(" + packageName + "): " |
| + (conditions == null ? "null" : conditions.size() + " conditions")); |
| } |
| synchronized (mLock) { |
| if (conditions == null) { |
| mConditionsByPkg.remove(packageName); |
| } else { |
| mConditionsByPkg.put(packageName, new ArraySet<>(conditions)); |
| } |
| } |
| } |
| |
| @Override |
| public void disableSelf() { |
| if (mMaster.verbose) Slog.v(TAG, "disableSelf()"); |
| |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| Settings.Secure.putStringForUser(getContext().getContentResolver(), |
| Settings.Secure.CONTENT_CAPTURE_ENABLED, "0", mUserId); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| writeServiceEvent(FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__SET_DISABLED, |
| getServiceComponentName()); |
| } |
| |
| @Override |
| public void writeSessionFlush(int sessionId, ComponentName app, FlushMetrics flushMetrics, |
| ContentCaptureOptions options, int flushReason) { |
| ContentCaptureMetricsLogger.writeSessionFlush(sessionId, getServiceComponentName(), app, |
| flushMetrics, options, flushReason); |
| } |
| } |
| } |