Add API for checking whether the default IME was set by the DO/PO

With this API, the system can determine whether a user's default
IME was set by the user or the user's DO/PO.

Bug: 32692748
Test: DPMS unit tests and CTS CtsDevicePolicyManagerTestCases

Change-Id: Ibd703ff5c9e4c072599ad8d6023c94a97d728109
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index dd44aa0..355a0e2 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.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 @@
 
     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 @@
     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 @@
 
         long mLastNetworkLogsRetrievalTime = -1;
 
+        boolean mDefaultInputMethodSet = false;
+
         // Used for initialization of users created by createAndManageUsers.
         boolean mAdminBroadcastPending = false;
         PersistableBundle mInitBundle = null;
@@ -1701,6 +1708,11 @@
                     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 @@
         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
 
         LocalServices.addService(DevicePolicyManagerInternal.class, mLocalService);
+
+        mSetupContentObserver = new SetupContentObserver(mHandler);
     }
 
     /**
@@ -2568,6 +2582,11 @@
                 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 @@
                 } 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 @@
 
         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();
 
@@ -6561,12 +6582,15 @@
             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();
@@ -6650,6 +6674,9 @@
             admin.userRestrictions = null;
             admin.defaultEnabledRestrictionsAlreadySet.clear();
         }
+        final DevicePolicyData policyData = getUserData(userId);
+        policyData.mDefaultInputMethodSet = false;
+        saveSettingsLocked(userId);
         clearUserPoliciesLocked(userId);
         mOwners.removeProfileOwner(userId);
         mOwners.writeProfileOwner(userId);
@@ -8700,6 +8727,20 @@
 
             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);
@@ -8840,12 +8881,16 @@
     }
 
     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);
@@ -8857,10 +8902,15 @@
             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)) {
@@ -8869,6 +8919,19 @@
                     // 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);
+                    }
+                }
             }
         }
     }
@@ -10747,4 +10810,15 @@
         }
         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 e6dd13f..ca9285b 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 @@
         }
 
         @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 23a1bb4..5710530 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.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;
@@ -3770,6 +3771,145 @@
                 .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 46aaf83..258b393 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 @@
             return 0;
         }
 
+        public String settingsSecureGetStringForUser(String name, int userHandle) {
+            return null;
+        }
+
         public void settingsSecurePutIntForUser(String name, int value, int userHandle) {
         }