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
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user