Add lockTask policy definition

Bug: 232918480
Test: manual
Change-Id: Ifbaf0f19d018fa9aadcbab3dd6c8eefe8d79f492
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9fd990f..ef098c6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3936,6 +3936,12 @@
         return PERMISSION_GRANT_POLICY_KEY + "_" + packageName + "_" + permission;
     }
 
+    // TODO: Expose this as SystemAPI once we add the query API
+    /**
+     * @hide
+     */
+    public static final String LOCK_TASK_POLICY = "lockTask";
+
     /**
      * This object is a single place to tack on invalidation and disable calls.  All
      * binder caches in this class derive from this Config, so all can be invalidated or
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 316c736..d492fd6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -332,6 +332,7 @@
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
 import android.view.IWindowManager;
@@ -2878,60 +2879,60 @@
 
         policy.validatePasswordOwner();
         updateMaximumTimeToLockLocked(userHandle);
-        updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
+        updateLockTaskPackagesLocked(mContext, policy.mLockTaskPackages, userHandle);
         updateLockTaskFeaturesLocked(policy.mLockTaskFeatures, userHandle);
         if (policy.mStatusBarDisabled) {
             setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle);
         }
     }
 
-    private void updateLockTaskPackagesLocked(List<String> packages, int userId) {
-        String[] packagesArray = null;
+    static void updateLockTaskPackagesLocked(Context context, List<String> packages, int userId) {
+        Binder.withCleanCallingIdentity(() -> {
 
-        if (!packages.isEmpty()) {
-            // When adding packages, we need to include the exempt apps so they can still be
-            // launched (ideally we should use a different AM API as these apps don't need to use
-            // lock-task mode).
-            // They're not added when the packages is empty though, as in that case we're disabling
-            // lock-task mode.
-            List<String> exemptApps = listPolicyExemptAppsUnchecked();
-            if (!exemptApps.isEmpty()) {
-                // TODO(b/175377361): add unit test to verify it (cannot be CTS because the policy-
-                // -exempt apps are provided by OEM and the test would have no control over it) once
-                // tests are migrated to the new infra-structure
-                HashSet<String> updatedPackages = new HashSet<>(packages);
-                updatedPackages.addAll(exemptApps);
-                if (VERBOSE_LOG) {
-                    Slogf.v(LOG_TAG, "added %d policy-exempt apps to %d lock task packages. Final "
-                            + "list: %s", exemptApps.size(), packages.size(), updatedPackages);
+            String[] packagesArray = null;
+            if (!packages.isEmpty()) {
+                // When adding packages, we need to include the exempt apps so they can still be
+                // launched (ideally we should use a different AM API as these apps don't need to
+                // use lock-task mode).
+                // They're not added when the packages is empty though, as in that case we're
+                // disabling lock-task mode.
+                List<String> exemptApps = listPolicyExemptAppsUnchecked(context);
+                if (!exemptApps.isEmpty()) {
+                    // TODO(b/175377361): add unit test to verify it (cannot be CTS because the
+                    //  policy-exempt apps are provided by OEM and the test would have no control
+                    //  over it) once tests are migrated to the new infra-structure
+                    HashSet<String> updatedPackages = new HashSet<>(packages);
+                    updatedPackages.addAll(exemptApps);
+                    if (VERBOSE_LOG) {
+                        Slogf.v(LOG_TAG, "added %d policy-exempt apps to %d lock task "
+                                + "packages. Final list: %s",
+                                exemptApps.size(), packages.size(), updatedPackages);
+                    }
+                    packagesArray = updatedPackages.toArray(new String[updatedPackages.size()]);
                 }
-                packagesArray = updatedPackages.toArray(new String[updatedPackages.size()]);
             }
-        }
 
-        if (packagesArray == null) {
-            packagesArray = packages.toArray(new String[packages.size()]);
-        }
-
-        long ident = mInjector.binderClearCallingIdentity();
-        try {
-            mInjector.getIActivityManager().updateLockTaskPackages(userId, packagesArray);
-        } catch (RemoteException e) {
-            // Not gonna happen.
-        } finally {
-            mInjector.binderRestoreCallingIdentity(ident);
-        }
+            if (packagesArray == null) {
+                packagesArray = packages.toArray(new String[packages.size()]);
+            }
+            try {
+                ActivityManager.getService().updateLockTaskPackages(userId, packagesArray);
+            } catch (RemoteException e) {
+                // Shouldn't happen.
+                Slog.wtf(LOG_TAG, "Remote Exception: ", e);
+            }
+        });
     }
 
-    private void updateLockTaskFeaturesLocked(int flags, int userId) {
-        long ident = mInjector.binderClearCallingIdentity();
-        try {
-            mInjector.getIActivityTaskManager().updateLockTaskFeatures(userId, flags);
-        } catch (RemoteException e) {
-            // Not gonna happen.
-        } finally {
-            mInjector.binderRestoreCallingIdentity(ident);
-        }
+    static void updateLockTaskFeaturesLocked(int flags, int userId) {
+        Binder.withCleanCallingIdentity(() -> {
+            try {
+                ActivityTaskManager.getService().updateLockTaskFeatures(userId, flags);
+            } catch (RemoteException e) {
+                // Shouldn't happen.
+                Slog.wtf(LOG_TAG, "Remote Exception: ", e);
+            }
+        });
     }
 
     static void validateQualityConstant(int quality) {
@@ -8941,7 +8942,7 @@
         policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
         policy.mAffiliationIds.clear();
         policy.mLockTaskPackages.clear();
-        updateLockTaskPackagesLocked(policy.mLockTaskPackages, userId);
+        updateLockTaskPackagesLocked(mContext, policy.mLockTaskPackages, userId);
         policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
         saveSettingsLocked(userId);
 
@@ -11217,7 +11218,7 @@
     private String[] populateNonExemptAndExemptFromPolicyApps(String[] packageNames,
             Set<String> outputExemptApps) {
         Preconditions.checkArgument(outputExemptApps.isEmpty(), "outputExemptApps is not empty");
-        List<String> exemptAppsList = listPolicyExemptAppsUnchecked();
+        List<String> exemptAppsList = listPolicyExemptAppsUnchecked(mContext);
         if (exemptAppsList.isEmpty()) {
             return packageNames;
         }
@@ -11330,16 +11331,16 @@
                 hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)
                         || isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
-        return listPolicyExemptAppsUnchecked();
+        return listPolicyExemptAppsUnchecked(mContext);
     }
 
-    private List<String> listPolicyExemptAppsUnchecked() {
+    private static List<String> listPolicyExemptAppsUnchecked(Context context) {
         // TODO(b/181238156): decide whether it should only list the apps set by the resources,
         // or also the "critical" apps defined by PersonalAppsSuspensionHelper (like SMS app).
         // If it's the latter, refactor PersonalAppsSuspensionHelper so it (or a superclass) takes
         // the resources on constructor.
-        String[] core = mContext.getResources().getStringArray(R.array.policy_exempt_apps);
-        String[] vendor = mContext.getResources().getStringArray(R.array.vendor_policy_exempt_apps);
+        String[] core = context.getResources().getStringArray(R.array.policy_exempt_apps);
+        String[] vendor = context.getResources().getStringArray(R.array.vendor_policy_exempt_apps);
 
         int size = core.length + vendor.length;
         Set<String> apps = new ArraySet<>(size);
@@ -11516,7 +11517,7 @@
                 && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
 
-        List<String> exemptApps = listPolicyExemptAppsUnchecked();
+        List<String> exemptApps = listPolicyExemptAppsUnchecked(mContext);
         if (exemptApps.contains(packageName)) {
             Slogf.d(LOG_TAG, "setApplicationHidden(): ignoring %s as it's on policy-exempt list",
                     packageName);
@@ -12191,7 +12192,7 @@
 
         // Store the settings persistently.
         saveSettingsLocked(userHandle);
-        updateLockTaskPackagesLocked(packages, userHandle);
+        updateLockTaskPackagesLocked(mContext, packages, userHandle);
     }
 
     @Override
@@ -12210,7 +12211,7 @@
     @Override
     public boolean isLockTaskPermitted(String pkg) {
         // Check policy-exempt apps first, as it doesn't require the lock
-        if (listPolicyExemptAppsUnchecked().contains(pkg)) {
+        if (listPolicyExemptAppsUnchecked(mContext).contains(pkg)) {
             if (VERBOSE_LOG) {
                 Slogf.v(LOG_TAG, "isLockTaskPermitted(%s): returning true for policy-exempt app",
                             pkg);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index f4953b4..9261d59 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -81,6 +81,10 @@
                 componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY));
     }
 
+    static String getRoleAuthorityOf(String roleName) {
+        return ROLE_AUTHORITY_PREFIX + roleName;
+    }
+
     private EnforcingAdmin(
             String packageName, ComponentName componentName, Set<String> authorities) {
         Objects.requireNonNull(packageName);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
new file mode 100644
index 0000000..9360fd7
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.annotation.Nullable;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.Set;
+
+final class LockTaskPolicy {
+    private Set<String> mPackages;
+    private int mFlags;
+
+    LockTaskPolicy(@Nullable Set<String> packages, int flags) {
+        mPackages = packages;
+        mFlags = flags;
+    }
+
+    @Nullable
+    Set<String> getPackages() {
+        return mPackages;
+    }
+
+    int getFlags() {
+        return mFlags;
+    }
+
+    void setPackages(Set<String> packages) {
+        mPackages = packages;
+    }
+
+    void setFlags(int flags) {
+        mFlags = flags;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        LockTaskPolicy other = (LockTaskPolicy) o;
+        return Objects.equals(mPackages, other.mPackages)
+                && mFlags == other.mFlags;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPackages, mFlags);
+    }
+
+    static final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> {
+
+        private static final String ATTR_PACKAGES = ":packages";
+        private static final String ATTR_PACKAGES_SEPARATOR = ";";
+        private static final String ATTR_FLAGS = ":flags";
+
+        @Override
+        void saveToXml(
+                TypedXmlSerializer serializer, String attributeNamePrefix, LockTaskPolicy value)
+                throws IOException {
+            if (value.mPackages != null) {
+                serializer.attribute(
+                        /* namespace= */ null,
+                        attributeNamePrefix + ATTR_PACKAGES,
+                        String.join(ATTR_PACKAGES_SEPARATOR, value.mPackages));
+            }
+            serializer.attributeInt(
+                    /* namespace= */ null,
+                    attributeNamePrefix + ATTR_FLAGS,
+                    value.mFlags);
+        }
+
+        @Override
+        LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix)
+                throws XmlPullParserException {
+            String packagesStr = parser.getAttributeValue(
+                    /* namespace= */ null,
+                    attributeNamePrefix + ATTR_PACKAGES);
+            Set<String> packages = packagesStr == null
+                    ? null
+                    : Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR));
+            int flags = parser.getAttributeInt(
+                    /* namespace= */ null,
+                    attributeNamePrefix + ATTR_FLAGS);
+            return new LockTaskPolicy(packages, flags);
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index b22477b..3a18cb9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -82,11 +82,23 @@
                 new String[]{packageName, permission});
     }
 
+    static PolicyDefinition<LockTaskPolicy> LOCK_TASK = new PolicyDefinition<>(
+            DevicePolicyManager.LOCK_TASK_POLICY,
+            new TopPriority<>(List.of(
+                    // TODO(b/258166155): add correct device lock role name
+                    EnforcingAdmin.getRoleAuthorityOf("DeviceLock"),
+                    EnforcingAdmin.DPC_AUTHORITY)),
+            POLICY_FLAG_LOCAL_ONLY_POLICY,
+            (LockTaskPolicy value, Context context, Integer userId, String[] args) ->
+                    PolicyEnforcerCallbacks.setLockTask(value, context, userId),
+            new LockTaskPolicy.LockTaskPolicySerializer());
+
     private static Map<String, PolicyDefinition<?>> sPolicyDefinitions = Map.of(
             DevicePolicyManager.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE,
             DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY, PERMISSION_GRANT_NO_ARGS
     );
 
+
     private final String mPolicyKey;
     private final String mPolicyDefinitionKey;
     private final ResolutionMechanism<V> mResolutionMechanism;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 2b63218..b645b97 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -29,6 +29,7 @@
 
 import com.android.server.utils.Slogf;
 
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -103,6 +104,14 @@
         }
     }
 
+    static boolean setLockTask(
+            @Nullable LockTaskPolicy policy, @NonNull Context context, int userId) {
+        DevicePolicyManagerService.updateLockTaskPackagesLocked(
+                context, List.copyOf(policy.getPackages()), userId);
+        DevicePolicyManagerService.updateLockTaskFeaturesLocked(policy.getFlags(), userId);
+        return true;
+    }
+
     private static class BlockingCallback {
         private final CountDownLatch mLatch = new CountDownLatch(1);
         private final AtomicReference<Boolean> mValue = new AtomicReference<>();