Merge changes from topics "dsiable_settings_2", "disable_cc_settings"

* changes:
  Added new APIs to let ContentCaptureService enable / disable the feature.
  Added new APIs to let Settings enable / disable ContentCapture
This commit is contained in:
Adam He
2019-01-29 22:46:24 +00:00
committed by Android (Google) Code Review
9 changed files with 351 additions and 31 deletions

View File

@@ -6028,6 +6028,7 @@ package android.provider {
field public static final String AUTOFILL_USER_DATA_MAX_VALUE_LENGTH = "autofill_user_data_max_value_length";
field public static final String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH = "autofill_user_data_min_value_length";
field public static final String COMPLETED_CATEGORY_PREFIX = "suggested.completed_category.";
field public static final String CONTENT_CAPTURE_ENABLED = "content_capture_enabled";
field public static final String DOZE_ALWAYS_ON = "doze_always_on";
field public static final String HUSH_GESTURE_USED = "hush_gesture_used";
field public static final String INSTANT_APPS_ENABLED = "instant_apps_enabled";
@@ -9365,6 +9366,11 @@ package android.view.contentcapture {
field public static final int TYPE_VIEW_TEXT_CHANGED = 3; // 0x3
}
public final class ContentCaptureManager {
method public boolean isContentCaptureFeatureEnabled();
method public void setContentCaptureFeatureEnabled(boolean);
}
public final class UserDataRemovalRequest implements android.os.Parcelable {
method @NonNull public String getPackageName();
method @NonNull public java.util.List<android.view.contentcapture.UserDataRemovalRequest.UriRequest> getUriRequests();

View File

@@ -1698,6 +1698,7 @@ package android.provider {
field public static final String AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE = "autofill_user_data_max_user_data_size";
field public static final String AUTOFILL_USER_DATA_MAX_VALUE_LENGTH = "autofill_user_data_max_value_length";
field public static final String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH = "autofill_user_data_min_value_length";
field public static final String CONTENT_CAPTURE_ENABLED = "content_capture_enabled";
field public static final String DISABLED_PRINT_SERVICES = "disabled_print_services";
field @Deprecated public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages";
field public static final String LOCATION_ACCESS_CHECK_DELAY_MILLIS = "location_access_check_delay_millis";

View File

@@ -5687,6 +5687,14 @@ public final class Settings {
public static final String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH =
"autofill_user_data_min_value_length";
/**
* Defines whether Content Capture is enabled for the user.
* @hide
*/
@SystemApi
@TestApi
public static final String CONTENT_CAPTURE_ENABLED = "content_capture_enabled";
/**
* @deprecated Use {@link android.provider.Settings.Global#DEVICE_PROVISIONED} instead
*/

View File

@@ -15,10 +15,12 @@
*/
package android.view.contentcapture;
import static android.view.contentcapture.ContentCaptureHelper.DEBUG;
import static android.view.contentcapture.ContentCaptureHelper.VERBOSE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.UiThread;
import android.content.ComponentName;
@@ -51,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.
*/
@@ -108,9 +117,7 @@ public final class ContentCaptureManager {
if (mMainSession == null) {
mMainSession = new MainContentCaptureSession(mContext, mHandler, mService,
mDisabled);
if (VERBOSE) {
Log.v(TAG, "getDefaultContentCaptureSession(): created " + mMainSession);
}
if (VERBOSE) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession);
}
return mMainSession;
}
@@ -147,13 +154,9 @@ public final class ContentCaptureManager {
*/
@Nullable
public ComponentName getServiceComponentName() {
if (!isContentCaptureEnabled()) {
return null;
}
// Wait for system server to return the component name.
if (!isContentCaptureEnabled()) return null;
final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
try {
mService.getServiceComponentName(resultReceiver);
return resultReceiver.getParcelableResult();
@@ -164,6 +167,17 @@ public final class ContentCaptureManager {
/**
* Checks whether content capture is enabled for this activity.
*
* <p>There are many reasons it could be disabled, such as:
* <ul>
* <li>App itself disabled content capture through {@link #setContentCaptureEnabled(boolean)}.
* <li>Service disabled content capture for this specific activity.
* <li>Service disabled content capture for all activities of this package.
* <li>Service disabled content capture globally.
* <li>User disabled content capture globally (through Settings).
* <li>OEM disabled content capture globally.
* <li>Transient errors.
* </ul>
*/
public boolean isContentCaptureEnabled() {
synchronized (mLock) {
@@ -178,11 +192,81 @@ 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;
}
}
/**
* Gets whether Content Capture is enabled for the given user.
*
* <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
public boolean isContentCaptureFeatureEnabled() {
if (mService == null) return false;
final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
final int resultCode;
try {
mService.isContentCaptureFeatureEnabled(resultReceiver);
resultCode = resultReceiver.getIntResult();
} catch (RemoteException e) {
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);
}
}
/**
* Called by the app to request the Content Capture service to remove user-data associated with
* some context.

View File

@@ -99,7 +99,8 @@ public abstract class ContentCaptureSession implements AutoCloseable {
public static final int STATE_FLAG_SECURE = 0x20;
/**
* Session is disabled manually by the specific app.
* Session is disabled manually by the specific app
* (through {@link ContentCaptureManager#setContentCaptureEnabled(boolean)}).
*
* @hide
*/

View File

@@ -62,4 +62,14 @@ oneway interface IContentCaptureManager {
* Requests the removal of user data for the calling user.
*/
void removeUserData(in UserDataRemovalRequest request);
/**
* 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);
}

View File

@@ -608,6 +608,7 @@ public class SettingsBackupTest {
Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG,
Settings.Secure.COMPLETED_CATEGORY_PREFIX,
Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
Settings.Secure.CONTENT_CAPTURE_ENABLED,
Settings.Secure.DEFAULT_INPUT_METHOD,
Settings.Secure.DEVICE_PAIRED,
Settings.Secure.DIALER_DEFAULT_APPLICATION,

View File

@@ -20,10 +20,16 @@ import static android.Manifest.permission.MANAGE_CONTENT_CAPTURE;
import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
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;
import android.os.Bundle;
import android.os.IBinder;
@@ -32,8 +38,11 @@ import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.UserHandle;
import android.os.UserManager;
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;
@@ -49,6 +58,7 @@ import com.android.server.infra.FrameworkResourcesServiceNameResolver;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
* A service used to observe the contents of the screen.
@@ -60,23 +70,43 @@ import java.util.ArrayList;
public final class ContentCaptureManagerService extends
AbstractMasterSystemService<ContentCaptureManagerService, ContentCapturePerUserService> {
private static final String TAG = ContentCaptureManagerService.class.getSimpleName();
static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions";
private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
@GuardedBy("mLock")
private ActivityManagerInternal mAm;
private final LocalService mLocalService = new LocalService();
private final LocalLog mRequestsHistory = new LocalLog(20);
@GuardedBy("mLock")
private ActivityManagerInternal mAm;
/**
* Users disabled by {@link android.provider.Settings.Secure#CONTENT_CAPTURE_ENABLED}.
*/
@GuardedBy("mLock")
@Nullable
private SparseBooleanArray mDisabledUsers;
public ContentCaptureManagerService(@NonNull Context context) {
super(context, new FrameworkResourcesServiceNameResolver(context,
com.android.internal.R.string.config_defaultContentCaptureService),
UserManager.DISALLOW_CONTENT_CAPTURE);
// Sets which serviecs are disabled
final UserManager um = getContext().getSystemService(UserManager.class);
final List<UserInfo> users = um.getUsers();
for (int i = 0; i < users.size(); i++) {
final int userId = users.get(i).id;
final boolean disabled = isDisabledBySettings(userId);
if (disabled) {
Slog.i(mTag, "user " + userId + " disabled by settings");
if (mDisabledUsers == null) {
mDisabledUsers = new SparseBooleanArray(1);
}
mDisabledUsers.put(userId, true);
}
}
}
@Override // from AbstractMasterSystemService
@@ -100,7 +130,7 @@ public final class ContentCaptureManagerService extends
@Override // from AbstractMasterSystemService
protected void enforceCallingPermissionForManagement() {
getContext().enforceCallingPermission(MANAGE_CONTENT_CAPTURE, TAG);
getContext().enforceCallingPermission(MANAGE_CONTENT_CAPTURE, mTag);
}
@Override // from AbstractMasterSystemService
@@ -108,9 +138,86 @@ public final class ContentCaptureManagerService extends
return MAX_TEMP_SERVICE_DURATION_MS;
}
@Override // from AbstractMasterSystemService
protected void registerForExtraSettingsChanges(@NonNull ContentResolver resolver,
@NonNull ContentObserver observer) {
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.CONTENT_CAPTURE_ENABLED), false, observer,
UserHandle.USER_ALL);
}
@Override // from AbstractMasterSystemService
protected void onSettingsChanged(@UserIdInt int userId, @NonNull String property) {
switch (property) {
case Settings.Secure.CONTENT_CAPTURE_ENABLED:
setContentCaptureFeatureEnabledFromSettings(userId);
return;
default:
Slog.w(mTag, "Unexpected property (" + property + "); updating cache instead");
}
}
@Override // from AbstractMasterSystemService
protected boolean isDisabledLocked(@UserIdInt int userId) {
return isDisabledBySettingsLocked(userId) || super.isDisabledLocked(userId);
}
private boolean isDisabledBySettingsLocked(@UserIdInt int userId) {
return mDisabledUsers != null && mDisabledUsers.get(userId);
}
private void setContentCaptureFeatureEnabledFromSettings(@UserIdInt int userId) {
setContentCaptureFeatureEnabledForUser(userId, !isDisabledBySettings(userId));
}
private boolean isDisabledBySettings(@UserIdInt int userId) {
final String property = Settings.Secure.CONTENT_CAPTURE_ENABLED;
final String value = Settings.Secure.getStringForUser(getContext().getContentResolver(),
property, userId);
if (value == null) {
if (verbose) {
Slog.v(mTag, "isDisabledBySettings(): assuming false as '" + property
+ "' is not set");
}
return false;
}
try {
return !Boolean.valueOf(value);
} catch (Exception e) {
Slog.w(mTag, "Invalid value for property " + property + ": " + value);
}
return false;
}
private void setContentCaptureFeatureEnabledForUser(@UserIdInt int userId, boolean enabled) {
synchronized (mLock) {
if (mDisabledUsers == null) {
mDisabledUsers = new SparseBooleanArray();
}
final boolean alreadyEnabled = !mDisabledUsers.get(userId);
if (!(enabled ^ alreadyEnabled)) {
if (debug) {
Slog.d(mTag, "setContentCaptureFeatureEnabledForUser(): already " + enabled);
}
return;
}
if (enabled) {
Slog.i(mTag, "setContentCaptureFeatureEnabled(): enabling service for user "
+ userId);
mDisabledUsers.delete(userId);
} else {
Slog.i(mTag, "setContentCaptureFeatureEnabled(): disabling service for user "
+ userId);
mDisabledUsers.put(userId, true);
}
updateCachedServiceLocked(userId, !enabled);
}
}
// Called by Shell command.
void destroySessions(@UserIdInt int userId, @NonNull IResultReceiver receiver) {
Slog.i(TAG, "destroySessions() for userId " + userId);
Slog.i(mTag, "destroySessions() for userId " + userId);
enforceCallingPermissionForManagement();
synchronized (mLock) {
@@ -133,7 +240,7 @@ public final class ContentCaptureManagerService extends
// Called by Shell command.
void listSessions(int userId, IResultReceiver receiver) {
Slog.i(TAG, "listSessions() for userId " + userId);
Slog.i(mTag, "listSessions() for userId " + userId);
enforceCallingPermissionForManagement();
final Bundle resultData = new Bundle();
@@ -174,6 +281,64 @@ 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);
pw.print(prefix); pw.print("Disabled users: "); pw.println(mDisabledUsers);
}
final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
@Override
@@ -222,8 +387,7 @@ public final class ContentCaptureManagerService extends
result.send(/* resultCode= */ 0,
SyncResultReceiver.bundleFor(connectedServiceComponentName));
} catch (RemoteException e) {
// Ignore exception as we need to be resilient against app behavior.
Slog.w(TAG, "Unable to send service component name: " + e);
Slog.w(mTag, "Unable to send service component name: " + e);
}
}
@@ -237,9 +401,53 @@ public final class ContentCaptureManagerService extends
}
}
@Override
public void isContentCaptureFeatureEnabled(@NonNull IResultReceiver result) {
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 ? 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(), TAG, pw)) return;
if (!DumpUtils.checkDumpPermission(getContext(), mTag, pw)) return;
boolean showHistory = true;
if (args != null) {
@@ -252,7 +460,7 @@ public final class ContentCaptureManagerService extends
pw.println("Usage: dumpsys content_capture [--no-history]");
return;
default:
Slog.w(TAG, "Ignoring invalid dump arg: " + arg);
Slog.w(mTag, "Ignoring invalid dump arg: " + arg);
}
}
}

View File

@@ -117,7 +117,7 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem
*/
@GuardedBy("mLock")
@Nullable
private final SparseBooleanArray mDisabledUsers;
private final SparseBooleanArray mDisabledByUserRestriction;
/**
* Cache of services per user id.
@@ -148,9 +148,9 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem
}
if (disallowProperty == null) {
mDisabledUsers = null;
mDisabledByUserRestriction = null;
} else {
mDisabledUsers = new SparseBooleanArray();
mDisabledByUserRestriction = new SparseBooleanArray();
// Hookup with UserManager to disable service when necessary.
final UserManager um = context.getSystemService(UserManager.class);
final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
@@ -159,15 +159,15 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem
final int userId = users.get(i).id;
final boolean disabled = umi.getUserRestriction(userId, disallowProperty);
if (disabled) {
Slog.i(mTag, "Disabling for user " + userId);
mDisabledUsers.put(userId, disabled);
Slog.i(mTag, "Disabling by restrictions user " + userId);
mDisabledByUserRestriction.put(userId, disabled);
}
}
umi.addUserRestrictionsListener((userId, newRestrictions, prevRestrictions) -> {
final boolean disabledNow =
newRestrictions.getBoolean(disallowProperty, false);
synchronized (mLock) {
final boolean disabledBefore = mDisabledUsers.get(userId);
final boolean disabledBefore = mDisabledByUserRestriction.get(userId);
if (disabledBefore == disabledNow) {
// Nothing changed, do nothing.
if (debug) {
@@ -176,7 +176,7 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem
}
}
Slog.i(mTag, "Updating for user " + userId + ": disabled=" + disabledNow);
mDisabledUsers.put(userId, disabledNow);
mDisabledByUserRestriction.put(userId, disabledNow);
updateCachedServiceLocked(userId, disabledNow);
}
});
@@ -414,7 +414,7 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem
* given user.
*/
protected boolean isDisabledLocked(@UserIdInt int userId) {
return mDisabledUsers == null ? false : mDisabledUsers.get(userId);
return mDisabledByUserRestriction == null ? false : mDisabledByUserRestriction.get(userId);
}
/**
@@ -523,7 +523,8 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem
mServiceNameResolver.dumpShort(pw, userId); pw.println();
}
}
pw.print(prefix); pw.print("Disabled users: "); pw.println(mDisabledUsers);
pw.print(prefix); pw.print("Users disabled by restriction: ");
pw.println(mDisabledByUserRestriction);
pw.print(prefix); pw.print("Allow instant service: "); pw.println(mAllowInstantService);
final String settingsProperty = getServiceSettingsProperty();
if (settingsProperty != null) {