diff options
author | 2017-02-17 17:52:59 +0000 | |
---|---|---|
committer | 2017-02-17 17:53:02 +0000 | |
commit | 4f7e1fc9af698e08576c9beadef872f5c4ea04a8 (patch) | |
tree | f300cb38e5aa9fe13da0eea0c6609ccdb6453cbd | |
parent | a63a9d4c34ed8b38de663f655114933eb8284cbd (diff) | |
parent | aff5e9c21c28f21f7a3688d84e747727cf17f382 (diff) |
Merge "Add API for checking whether the default IME was set by the DO/PO"
7 files changed, 258 insertions, 9 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index eaea8fa0ccd7..cd1b174921bb 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -6292,6 +6292,7 @@ package android.app.admin { method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String); method public boolean isBackupServiceEnabled(android.content.ComponentName); method public deprecated boolean isCallerApplicationRestrictionsManagingPackage(); + method public boolean isDefaultInputMethodSetByOwner(android.os.UserHandle); method public boolean isDeviceManaged(); method public boolean isDeviceOwnerApp(java.lang.String); method public boolean isLockTaskPermitted(java.lang.String); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 3cb46549a652..979702ae16b4 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -7871,4 +7871,27 @@ public class DevicePolicyManager { throw re.rethrowFromSystemServer(); } } + + /** + * Called by the system to find out whether the user's IME was set by the device/profile owner + * or the user. + * + * @param user The user for whom to retrieve information. + * @return {@code true} if the user's IME was set by the device or profile owner, {@code false} + * otherwise. + * @throws SecurityException if the caller does not have permission to retrieve information + * about the given user's default IME. Device Owner and Profile Owner can retrieve + * information about the user they run on; the System can retrieve information about any + * user. + * + * @hide + */ + @TestApi + public boolean isDefaultInputMethodSetByOwner(@NonNull UserHandle user) { + try { + return mService.isDefaultInputMethodSetByOwner(user); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index c2f75c83da62..ec97c2c2084c 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -347,4 +347,6 @@ interface IDevicePolicyManager { boolean clearResetPasswordToken(in ComponentName admin); boolean isResetPasswordTokenActive(in ComponentName admin); boolean resetPasswordWithToken(in ComponentName admin, String password, in byte[] token, int flags); + + boolean isDefaultInputMethodSetByOwner(in UserHandle user); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 2b5a06dac0a7..b76cadc94fd6 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -161,6 +161,7 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; @@ -238,6 +239,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String TAG_ADMIN_BROADCAST_PENDING = "admin-broadcast-pending"; + private static final String TAG_DEFAULT_INPUT_METHOD_SET = "default-ime-set"; + private static final String ATTR_ID = "id"; private static final String ATTR_VALUE = "value"; @@ -402,6 +405,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private final AtomicBoolean mRemoteBugreportServiceIsActive = new AtomicBoolean(); private final AtomicBoolean mRemoteBugreportSharingAccepted = new AtomicBoolean(); + private SetupContentObserver mSetupContentObserver; + private final Runnable mRemoteBugreportTimeoutRunnable = new Runnable() { @Override public void run() { @@ -504,6 +509,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long mLastNetworkLogsRetrievalTime = -1; + boolean mDefaultInputMethodSet = false; + // Used for initialization of users created by createAndManageUsers. boolean mAdminBroadcastPending = false; PersistableBundle mInitBundle = null; @@ -1701,6 +1708,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { name, def, userHandle); } + String settingsSecureGetStringForUser(String name, int userHandle) { + return Settings.Secure.getStringForUser(mContext.getContentResolver(), name, + userHandle); + } + void settingsSecurePutIntForUser(String name, int value, int userHandle) { Settings.Secure.putIntForUser(mContext.getContentResolver(), name, value, userHandle); @@ -1816,6 +1828,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler); LocalServices.addService(DevicePolicyManagerInternal.class, mLocalService); + + mSetupContentObserver = new SetupContentObserver(mHandler); } /** @@ -2568,6 +2582,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.endTag(null, TAG_PASSWORD_TOKEN_HANDLE); } + if (policy.mDefaultInputMethodSet) { + out.startTag(null, TAG_DEFAULT_INPUT_METHOD_SET); + out.endTag(null, TAG_DEFAULT_INPUT_METHOD_SET); + } + out.endTag(null, "policies"); out.endDocument(); @@ -2777,6 +2796,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if (TAG_PASSWORD_TOKEN_HANDLE.equals(tag)) { policy.mPasswordTokenHandle = Long.parseLong( parser.getAttributeValue(null, ATTR_VALUE)); + } else if (TAG_DEFAULT_INPUT_METHOD_SET.equals(tag)) { + policy.mDefaultInputMethodSet = true; } else { Slog.w(LOG_TAG, "Unknown tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -2897,8 +2918,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { onStartUser(UserHandle.USER_SYSTEM); - // Register an observer for watching for user setup complete. - new SetupContentObserver(mHandler).register(); + // Register an observer for watching for user setup complete and settings changes. + mSetupContentObserver.register(); // Initialize the user setup state, to handle the upgrade case. updateUserSetupCompleteAndPaired(); @@ -6558,12 +6579,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { admin.forceEphemeralUsers = false; admin.isNetworkLoggingEnabled = false; mUserManagerInternal.setForceEphemeralUsers(admin.forceEphemeralUsers); - final DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM); - policyData.mLastSecurityLogRetrievalTime = -1; - policyData.mLastBugReportRequestTime = -1; - policyData.mLastNetworkLogsRetrievalTime = -1; - saveSettingsLocked(UserHandle.USER_SYSTEM); } + final DevicePolicyData policyData = getUserData(userId); + policyData.mDefaultInputMethodSet = false; + saveSettingsLocked(userId); + final DevicePolicyData systemPolicyData = getUserData(UserHandle.USER_SYSTEM); + systemPolicyData.mLastSecurityLogRetrievalTime = -1; + systemPolicyData.mLastBugReportRequestTime = -1; + systemPolicyData.mLastNetworkLogsRetrievalTime = -1; + saveSettingsLocked(UserHandle.USER_SYSTEM); clearUserPoliciesLocked(userId); mOwners.clearDeviceOwner(); @@ -6648,6 +6672,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { admin.userRestrictions = null; admin.defaultEnabledRestrictionsAlreadySet.clear(); } + final DevicePolicyData policyData = getUserData(userId); + policyData.mDefaultInputMethodSet = false; + saveSettingsLocked(userId); clearUserPoliciesLocked(userId); mOwners.removeProfileOwner(userId); mOwners.writeProfileOwner(userId); @@ -8698,6 +8725,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long id = mInjector.binderClearCallingIdentity(); try { + if (Settings.Secure.DEFAULT_INPUT_METHOD.equals(setting)) { + final String currentValue = mInjector.settingsSecureGetStringForUser( + Settings.Secure.DEFAULT_INPUT_METHOD, callingUserId); + if (!TextUtils.equals(currentValue, value)) { + // Tell the content observer that the next change will be due to the owner + // changing the value. There is a small race condition here that we cannot + // avoid: Change notifications are sent asynchronously, so it is possible + // that there are prior notifications queued up before the one we are about + // to trigger. This is a corner case that will have no impact in practice. + mSetupContentObserver.addPendingChangeByOwnerLocked(callingUserId); + } + getUserData(callingUserId).mDefaultInputMethodSet = true; + saveSettingsLocked(callingUserId); + } mInjector.settingsSecurePutStringForUser(setting, value, callingUserId); } finally { mInjector.binderRestoreCallingIdentity(id); @@ -8838,12 +8879,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private class SetupContentObserver extends ContentObserver { - private final Uri mUserSetupComplete = Settings.Secure.getUriFor( Settings.Secure.USER_SETUP_COMPLETE); private final Uri mDeviceProvisioned = Settings.Global.getUriFor( Settings.Global.DEVICE_PROVISIONED); private final Uri mPaired = Settings.Secure.getUriFor(Settings.Secure.DEVICE_PAIRED); + private final Uri mDefaultImeChanged = Settings.Secure.getUriFor( + Settings.Secure.DEFAULT_INPUT_METHOD); + + @GuardedBy("DevicePolicyManagerService.this") + private Set<Integer> mUserIdsWithPendingChangesByOwner = new ArraySet<>(); public SetupContentObserver(Handler handler) { super(handler); @@ -8855,10 +8900,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (mIsWatch) { mInjector.registerContentObserver(mPaired, false, this, UserHandle.USER_ALL); } + mInjector.registerContentObserver(mDefaultImeChanged, false, this, UserHandle.USER_ALL); + } + + private void addPendingChangeByOwnerLocked(int userId) { + mUserIdsWithPendingChangesByOwner.add(userId); } @Override - public void onChange(boolean selfChange, Uri uri) { + public void onChange(boolean selfChange, Uri uri, int userId) { if (mUserSetupComplete.equals(uri) || (mIsWatch && mPaired.equals(uri))) { updateUserSetupCompleteAndPaired(); } else if (mDeviceProvisioned.equals(uri)) { @@ -8867,6 +8917,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // is delayed until device is marked as provisioned. setDeviceOwnerSystemPropertyLocked(); } + } else if (mDefaultImeChanged.equals(uri)) { + synchronized (DevicePolicyManagerService.this) { + if (mUserIdsWithPendingChangesByOwner.contains(userId)) { + // This change notification was triggered by the owner changing the default + // IME. Ignore it. + mUserIdsWithPendingChangesByOwner.remove(userId); + } else { + // This change notification was triggered by the user manually changing the + // default IME. + getUserData(userId).mDefaultInputMethodSet = false; + saveSettingsLocked(userId); + } + } } } } @@ -10745,4 +10808,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } return false; } + + @Override + public boolean isDefaultInputMethodSetByOwner(@NonNull UserHandle user) { + final int userId = user.getIdentifier(); + enforceProfileOwnerOrSystemUser(null); + if (!isCallerWithSystemUid() && mInjector.userHandleGetCallingUserId() != userId) { + throw new SecurityException( + "Only the system can use this method to query information about another user"); + } + return getUserData(userId).mDefaultInputMethodSet; + } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index e6dd13fc15c2..ca9285bdd9f4 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -313,6 +313,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @Override + String settingsSecureGetStringForUser(String name, int userHandle) { + return context.settings.settingsSecureGetStringForUser(name, userHandle); + } + + @Override void settingsSecurePutIntForUser(String name, int value, int userHandle) { context.settings.settingsSecurePutIntForUser(name, value, userHandle); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index d0e5159bea5f..d65a9bfbf897 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -38,6 +38,7 @@ import android.content.pm.ParceledListSlice; import android.content.res.Resources; import android.graphics.Color; import android.net.IIpConnectivityMetrics; +import android.net.Uri; import android.content.pm.UserInfo; import android.net.wifi.WifiInfo; import android.os.Build.VERSION_CODES; @@ -3776,6 +3777,145 @@ public class DevicePolicyManagerTest extends DpmTestBase { .thenReturn(true); assertTrue(dpm.clearResetPasswordToken(admin1)); } + + public void testIsDefaultInputMethodSetByOwnerForDeviceOwner() throws Exception { + final String defaultIme = Settings.Secure.DEFAULT_INPUT_METHOD; + final Uri defaultImeUri = Settings.Secure.getUriFor(defaultIme); + final UserHandle firstUser = UserHandle.SYSTEM; + final UserHandle secondUser = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE); + + // Set up a Device Owner. + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + setupDeviceOwner(); + + // First and second user set default IMEs manually. + final long ident = mContext.binder.clearCallingIdentity(); + assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser)); + assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser)); + mContext.binder.restoreCallingIdentity(ident); + + // Device Owner changes default IME for first user. + when(mContext.settings.settingsSecureGetStringForUser(defaultIme, UserHandle.USER_SYSTEM)) + .thenReturn("ime1"); + dpm.setSecureSetting(admin1, defaultIme, "ime2"); + verify(mContext.settings).settingsSecurePutStringForUser(defaultIme, "ime2", + UserHandle.USER_SYSTEM); + reset(mContext.settings); + dpms.notifyChangeToContentObserver(defaultImeUri, UserHandle.USER_SYSTEM); + mContext.binder.clearCallingIdentity(); + assertTrue(dpm.isDefaultInputMethodSetByOwner(firstUser)); + assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser)); + mContext.binder.restoreCallingIdentity(ident); + + // Second user changes default IME manually. + dpms.notifyChangeToContentObserver(defaultImeUri, DpmMockContext.CALLER_USER_HANDLE); + mContext.binder.clearCallingIdentity(); + assertTrue(dpm.isDefaultInputMethodSetByOwner(firstUser)); + assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser)); + mContext.binder.restoreCallingIdentity(ident); + + // First user changes default IME manually. + dpms.notifyChangeToContentObserver(defaultImeUri, UserHandle.USER_SYSTEM); + mContext.binder.clearCallingIdentity(); + assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser)); + assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser)); + mContext.binder.restoreCallingIdentity(ident); + + // Device Owner changes default IME for first user again. + when(mContext.settings.settingsSecureGetStringForUser(defaultIme, UserHandle.USER_SYSTEM)) + .thenReturn("ime2"); + dpm.setSecureSetting(admin1, defaultIme, "ime3"); + verify(mContext.settings).settingsSecurePutStringForUser(defaultIme, "ime3", + UserHandle.USER_SYSTEM); + dpms.notifyChangeToContentObserver(defaultImeUri, UserHandle.USER_SYSTEM); + mContext.binder.clearCallingIdentity(); + assertTrue(dpm.isDefaultInputMethodSetByOwner(firstUser)); + assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser)); + + // Restarting the DPMS should not lose information. + initializeDpms(); + assertTrue(dpm.isDefaultInputMethodSetByOwner(firstUser)); + assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser)); + mContext.binder.restoreCallingIdentity(ident); + + // Device Owner can find out whether it set the default IME itself. + assertTrue(dpm.isDefaultInputMethodSetByOwner(firstUser)); + + // Removing the Device Owner should clear the information that it set the default IME. + clearDeviceOwner(); + mContext.binder.clearCallingIdentity(); + assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser)); + assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser)); + } + + public void testIsDefaultInputMethodSetByOwnerForProfileOwner() throws Exception { + final String defaultIme = Settings.Secure.DEFAULT_INPUT_METHOD; + final Uri defaultImeUri = Settings.Secure.getUriFor(defaultIme); + final UserHandle firstUser = UserHandle.SYSTEM; + final UserHandle secondUser = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE); + + // Set up a profile owner. + setupProfileOwner(); + + // First and second user set default IMEs manually. + final long ident = mContext.binder.clearCallingIdentity(); + assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser)); + assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser)); + mContext.binder.restoreCallingIdentity(ident); + + // Profile Owner changes default IME for second user. + when(mContext.settings.settingsSecureGetStringForUser(defaultIme, + DpmMockContext.CALLER_USER_HANDLE)).thenReturn("ime1"); + dpm.setSecureSetting(admin1, defaultIme, "ime2"); + verify(mContext.settings).settingsSecurePutStringForUser(defaultIme, "ime2", + DpmMockContext.CALLER_USER_HANDLE); + reset(mContext.settings); + dpms.notifyChangeToContentObserver(defaultImeUri, DpmMockContext.CALLER_USER_HANDLE); + mContext.binder.clearCallingIdentity(); + assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser)); + assertTrue(dpm.isDefaultInputMethodSetByOwner(secondUser)); + mContext.binder.restoreCallingIdentity(ident); + + // First user changes default IME manually. + dpms.notifyChangeToContentObserver(defaultImeUri, UserHandle.USER_SYSTEM); + mContext.binder.clearCallingIdentity(); + assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser)); + assertTrue(dpm.isDefaultInputMethodSetByOwner(secondUser)); + mContext.binder.restoreCallingIdentity(ident); + + // Second user changes default IME manually. + dpms.notifyChangeToContentObserver(defaultImeUri, DpmMockContext.CALLER_USER_HANDLE); + mContext.binder.clearCallingIdentity(); + assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser)); + assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser)); + mContext.binder.restoreCallingIdentity(ident); + + // Profile Owner changes default IME for second user again. + when(mContext.settings.settingsSecureGetStringForUser(defaultIme, + DpmMockContext.CALLER_USER_HANDLE)).thenReturn("ime2"); + dpm.setSecureSetting(admin1, defaultIme, "ime3"); + verify(mContext.settings).settingsSecurePutStringForUser(defaultIme, "ime3", + DpmMockContext.CALLER_USER_HANDLE); + dpms.notifyChangeToContentObserver(defaultImeUri, DpmMockContext.CALLER_USER_HANDLE); + mContext.binder.clearCallingIdentity(); + assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser)); + assertTrue(dpm.isDefaultInputMethodSetByOwner(secondUser)); + + // Restarting the DPMS should not lose information. + initializeDpms(); + assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser)); + assertTrue(dpm.isDefaultInputMethodSetByOwner(secondUser)); + mContext.binder.restoreCallingIdentity(ident); + + // Profile Owner can find out whether it set the default IME itself. + assertTrue(dpm.isDefaultInputMethodSetByOwner(secondUser)); + + // Removing the Profile Owner should clear the information that it set the default IME. + dpm.clearProfileOwner(admin1); + mContext.binder.clearCallingIdentity(); + assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser)); + assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser)); + } private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) { when(mContext.settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index 46aaf832ec99..258b3933a294 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -201,6 +201,10 @@ public class DpmMockContext extends MockContext { return 0; } + public String settingsSecureGetStringForUser(String name, int userHandle) { + return null; + } + public void settingsSecurePutIntForUser(String name, int value, int userHandle) { } |