Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bb0c2a2a authored by Felipe Leme's avatar Felipe Leme
Browse files

Added new APIs to let ContentCaptureService enable / disable the feature.

Bug: 123286662
Test: atest ChildlessActivityTest#testSetContentCaptureFeatureEnabled_disabledByApi \
        ChildlessActivityTest#testSetContentCaptureFeatureEnabled_disabledThenReEnabledByApi
Test: atest CtsContentCaptureServiceTestCases # for sanity check

Change-Id: Ideefb4c8e122e5f3f55dd7de8085212b2d8ce073
parent 91ddecac
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -9203,6 +9203,7 @@ package android.view.contentcapture {
  public final class ContentCaptureManager {
    method public boolean isContentCaptureFeatureEnabled();
    method public void setContentCaptureFeatureEnabled(boolean);
  }
  public final class UserDataRemovalRequest implements android.os.Parcelable {
+58 −2
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package android.view.contentcapture;

import static android.view.contentcapture.ContentCaptureHelper.DEBUG;
import static android.view.contentcapture.ContentCaptureHelper.VERBOSE;

import android.annotation.NonNull;
@@ -52,6 +53,13 @@ public final class ContentCaptureManager {

    private static final String TAG = ContentCaptureManager.class.getSimpleName();

    /** @hide */
    public static final int RESULT_CODE_TRUE = 1;
    /** @hide */
    public static final int RESULT_CODE_FALSE = 2;
    /** @hide */
    public static final int RESULT_CODE_NOT_SERVICE = -1;

    /**
     * Timeout for calls to system_server.
     */
@@ -184,6 +192,10 @@ public final class ContentCaptureManager {
     * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
     */
    public void setContentCaptureEnabled(boolean enabled) {
        if (DEBUG) {
            Log.d(TAG, "setContentCaptureEnabled(): setting to " + enabled + " for " + mContext);
        }

        synchronized (mLock) {
            mFlags |= enabled ? 0 : ContentCaptureContext.FLAG_DISABLED_BY_APP;
        }
@@ -195,6 +207,9 @@ public final class ContentCaptureManager {
     * <p>This method is typically used by the Content Capture Service settings page, so it can
     * provide a toggle to enable / disable it.
     *
     * @throws SecurityException if caller is not the app that owns the Content Capture service
     * associated with the user.
     *
     * @hide
     */
    @SystemApi
@@ -202,13 +217,54 @@ public final class ContentCaptureManager {
        if (mService == null) return false;

        final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
        final int resultCode;
        try {
            mService.isContentCaptureFeatureEnabled(resultReceiver);
            return resultReceiver.getIntResult() == 1;
            resultCode = resultReceiver.getIntResult();
        } catch (RemoteException e) {
            // Unable to retrieve component name in a reasonable amount of time.
            throw e.rethrowFromSystemServer();
        }
        switch (resultCode) {
            case RESULT_CODE_TRUE:
                return true;
            case RESULT_CODE_FALSE:
                return false;
            case RESULT_CODE_NOT_SERVICE:
                throw new SecurityException("caller is not user's ContentCapture service");
            default:
                throw new IllegalStateException("received invalid result: " + resultCode);
        }
    }

    /**
     * Sets whether Content Capture is enabled for the given user.
     *
     * @throws SecurityException if caller is not the app that owns the Content Capture service
     * associated with the user.
     *
     * @hide
     */
    @SystemApi
    public void setContentCaptureFeatureEnabled(boolean enabled) {
        if (DEBUG) Log.d(TAG, "setContentCaptureFeatureEnabled(): setting to " + enabled);

        final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
        final int resultCode;
        try {
            mService.setContentCaptureFeatureEnabled(enabled, resultReceiver);
            resultCode = resultReceiver.getIntResult();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        switch (resultCode) {
            case RESULT_CODE_TRUE:
                // Our work is done here, in our void existance...
                return;
            case RESULT_CODE_NOT_SERVICE:
                throw new SecurityException("caller is not user's ContentCapture service");
            default:
                throw new IllegalStateException("received invalid result: " + resultCode);
        }
    }

    /**
+5 −0
Original line number Diff line number Diff line
@@ -67,4 +67,9 @@ oneway interface IContentCaptureManager {
     * Returns whether the content capture feature is enabled for the calling user.
     */
    void isContentCaptureFeatureEnabled(in IResultReceiver result);

    /**
     * Sets whether the content capture feature is enabled for the given user.
     */
    void setContentCaptureFeatureEnabled(boolean enabled, in IResultReceiver result);
}
+85 −1
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import android.app.ActivityManagerInternal;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.os.Binder;
@@ -40,6 +42,7 @@ import android.provider.Settings;
import android.util.LocalLog;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.IContentCaptureManager;
import android.view.contentcapture.UserDataRemovalRequest;

@@ -278,6 +281,57 @@ public final class ContentCaptureManagerService extends
        return mAm;
    }

    @GuardedBy("mLock")
    private boolean assertCalledByServiceLocked(@NonNull String methodName, @UserIdInt int userId,
            int callingUid, @NonNull IResultReceiver result) {
        final boolean isService = isCalledByServiceLocked(methodName, userId, callingUid);
        if (isService) return true;

        try {
            result.send(ContentCaptureManager.RESULT_CODE_NOT_SERVICE,
                    /* resultData= */ null);
        } catch (RemoteException e) {
            Slog.w(mTag, "Unable to send isContentCaptureFeatureEnabled(): " + e);
        }
        return false;
    }

    @GuardedBy("mLock")
    private boolean isCalledByServiceLocked(@NonNull String methodName, @UserIdInt int userId,
            int callingUid) {

        final String serviceName = mServiceNameResolver.getServiceName(userId);
        if (serviceName == null) {
            Slog.e(mTag, methodName + ": called by UID " + callingUid
                    + ", but there's no service set for user " + userId);
            return false;
        }

        final ComponentName serviceComponent  = ComponentName.unflattenFromString(serviceName);
        if (serviceComponent == null) {
            Slog.w(mTag, methodName + ": invalid service name: " + serviceName);
            return false;
        }

        final String servicePackageName = serviceComponent.getPackageName();

        final PackageManager pm = getContext().getPackageManager();
        final int serviceUid;
        try {
            serviceUid = pm.getPackageUidAsUser(servicePackageName, UserHandle.getCallingUserId());
        } catch (NameNotFoundException e) {
            Slog.w(mTag, methodName + ": could not verify UID for " + serviceName);
            return false;
        }
        if (callingUid != serviceUid) {
            Slog.e(mTag, methodName + ": called by UID " + callingUid + ", but service UID is "
                    + serviceUid);
            return false;
        }

        return true;
    }

    @Override // from AbstractMasterSystemService
    protected void dumpLocked(String prefix, PrintWriter pw) {
        super.dumpLocked(prefix, pw);
@@ -352,15 +406,45 @@ public final class ContentCaptureManagerService extends
            final int userId = UserHandle.getCallingUserId();
            boolean enabled;
            synchronized (mLock) {
                final boolean isService = assertCalledByServiceLocked(
                        "isContentCaptureFeatureEnabled()", userId, Binder.getCallingUid(), result);
                if (!isService) return;

                enabled = !isDisabledBySettingsLocked(userId);
            }
            try {
                result.send(enabled ? 1 : 0, /* resultData= */null);
                result.send(enabled ? ContentCaptureManager.RESULT_CODE_TRUE
                        : ContentCaptureManager.RESULT_CODE_FALSE, /* resultData= */null);
            } catch (RemoteException e) {
                Slog.w(mTag, "Unable to send isContentCaptureFeatureEnabled(): " + e);
            }
        }

        @Override
        public void setContentCaptureFeatureEnabled(boolean enabled,
                @NonNull IResultReceiver result) {
            final int userId = UserHandle.getCallingUserId();
            final boolean isService;
            synchronized (mLock) {
                isService = assertCalledByServiceLocked("setContentCaptureFeatureEnabled()", userId,
                        Binder.getCallingUid(), result);
            }
            if (!isService) return;

            final long token = Binder.clearCallingIdentity();
            try {
                Settings.Secure.putStringForUser(getContext().getContentResolver(),
                        Settings.Secure.CONTENT_CAPTURE_ENABLED, Boolean.toString(enabled), userId);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
            try {
                result.send(ContentCaptureManager.RESULT_CODE_TRUE, /* resultData= */null);
            } catch (RemoteException e) {
                Slog.w(mTag, "Unable to send setContentCaptureFeatureEnabled(): " + e);
            }
        }

        @Override
        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
            if (!DumpUtils.checkDumpPermission(getContext(), mTag, pw)) return;