diff --git a/app/build.gradle b/app/build.gradle
index 2257b8f2..3cbeb50f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -7,7 +7,7 @@ ext {
     // exactly 1 digit
     versionMinor = 0
     // exactly 2 digits
-    versionBuild = 01
+    versionBuild = 02
 }
 
 android {
@@ -49,7 +49,6 @@ android {
         textOutput "stdout"
     }
 
-
     testOptions {
         unitTests {
             includeAndroidResources = true
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c145106e..8734a85f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -31,10 +31,12 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.REQUEST_PASSWORD_COMPLEXITY"/>
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
     <application
             android:allowBackup="true"
             android:icon="@drawable/ic_launcher"
+            android:banner="@drawable/ic_launcher"
             android:theme="@style/AppTheme"
             android:label="@string/app_name">
 
@@ -47,6 +49,10 @@
             <action android:name="android.intent.action.MAIN"/>
             <category android:name="android.intent.category.LAUNCHER"/>
         </intent-filter>
+        <intent-filter>
+          <action android:name="android.intent.action.MAIN"/>
+          <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
+        </intent-filter>
         <intent-filter>
             <action android:name="android.app.action.CHECK_POLICY_COMPLIANCE"/>
             <category android:name="android.intent.category.DEFAULT"/>
diff --git a/app/src/main/java/com/afwsamples/testdpc/CrossProfileAppsWhitelistFragment.java b/app/src/main/java/com/afwsamples/testdpc/CrossProfileAppsWhitelistFragment.java
new file mode 100644
index 00000000..4d1ebe70
--- /dev/null
+++ b/app/src/main/java/com/afwsamples/testdpc/CrossProfileAppsWhitelistFragment.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 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.afwsamples.testdpc;
+
+import android.annotation.TargetApi;
+import android.app.Fragment;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * This fragment provides the ability to whitelist cross profile packages
+ *
+ * <p>APIs exercised:
+ * <ul>
+ * <li> {@link DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)} </li>
+ * <li> {@link DevicePolicyManager#getCrossProfilePackages(ComponentName)} </li>
+ * </ul>
+ */
+@TargetApi(VERSION_CODES.R)
+public class CrossProfileAppsWhitelistFragment extends Fragment {
+    private static final String DELIMITER = "\n";
+
+    private View mInflatedView;
+    private EditText mAppNameEditText;
+    private Button mResetButton;
+    private Button mAddButton;
+    private Button mRemoveButton;
+    private TextView mAppsList;
+
+    private DevicePolicyManager mDevicePolicyManager;
+    private ComponentName mAdminComponent;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        mDevicePolicyManager = getActivity().getSystemService(DevicePolicyManager.class);
+        mAdminComponent = DeviceAdminReceiver.getComponentName(getActivity());
+        mInflatedView = inflater.inflate(
+            R.layout.cross_profile_apps_whitelist, container, false);
+
+        mAppNameEditText = mInflatedView.findViewById(R.id.cross_profile_app_whitelist_input);
+        mResetButton = mInflatedView.findViewById(R.id.cross_profile_app_whitelist_reset_button);
+        mAddButton = mInflatedView.findViewById(R.id.cross_profile_app_whitelist_add_button);
+        mRemoveButton = mInflatedView.findViewById(R.id.cross_profile_app_whitelist_remove_button);
+        mAppsList = mInflatedView.findViewById(R.id.cross_profile_app_list);
+
+        setOnClickListeners();
+        updateCrossProfileAppsList();
+
+        return mInflatedView;
+    }
+
+    private void setOnClickListeners() {
+        mResetButton.setOnClickListener(view -> resetApps());
+        mAddButton.setOnClickListener(
+            view -> addApp(mAppNameEditText.getText().toString().toLowerCase().trim()));
+        mRemoveButton.setOnClickListener(
+            view -> removeApp(mAppNameEditText.getText().toString().toLowerCase().trim()));
+    }
+
+    private void resetApps() {
+        mDevicePolicyManager.setCrossProfilePackages(mAdminComponent, Collections.emptySet());
+        updateCrossProfileAppsList();
+    }
+
+    private void addApp(String app) {
+        Set<String> currentApps = mDevicePolicyManager.getCrossProfilePackages(mAdminComponent);
+        currentApps.add(app);
+        mDevicePolicyManager.setCrossProfilePackages(mAdminComponent, currentApps);
+        updateCrossProfileAppsList();
+    }
+
+    private void removeApp(String app) {
+        Set<String> currentApps = mDevicePolicyManager.getCrossProfilePackages(mAdminComponent);
+        currentApps.remove(app);
+        mDevicePolicyManager.setCrossProfilePackages(mAdminComponent, currentApps);
+        updateCrossProfileAppsList();
+    }
+
+    private void updateCrossProfileAppsList(){
+        Set<String> currentApps = mDevicePolicyManager.getCrossProfilePackages(mAdminComponent);
+        if (currentApps.isEmpty()) {
+            mAppsList.setText(R.string.cross_profile_apps_no_whitelisted_apps);
+        } else {
+            mAppsList.setText(String.join(DELIMITER, currentApps));
+        }
+    }
+}
diff --git a/app/src/main/java/com/afwsamples/testdpc/common/Util.java b/app/src/main/java/com/afwsamples/testdpc/common/Util.java
index e6c4bad9..8c215a1b 100644
--- a/app/src/main/java/com/afwsamples/testdpc/common/Util.java
+++ b/app/src/main/java/com/afwsamples/testdpc/common/Util.java
@@ -18,17 +18,18 @@
 
 import android.annotation.TargetApi;
 import android.app.Service;
+import android.app.UiModeManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Configuration;
 import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.os.Build.VERSION;
 import android.os.Build.VERSION_CODES;
-import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
 import androidx.preference.PreferenceFragment;
@@ -52,11 +53,6 @@ public class Util {
     private static final String TAG = "Util";
     private static final int DEFAULT_BUFFER_SIZE = 4096;
 
-    private static final String BROADCAST_ACTION_FRP_CONFIG_CHANGED =
-        "com.google.android.gms.auth.FRP_CONFIG_CHANGED";
-    private static final String GMSCORE_PACKAGE = "com.google.android.gms";
-    private static final String PERSISTENT_DEVICE_OWNER_STATE = "persistentDeviceOwnerState";
-
     // TODO: Update to S when VERSION_CODES.R becomes available.
     public static final int R_VERSION_CODE = 30;
 
@@ -205,42 +201,6 @@ public static boolean installCaCertificate(InputStream certificateInputStream,
         return false;
     }
 
-    /**
-     * Returns the persistent device owner state which has been set by the device owner as an app
-     * restriction on GmsCore or null if there is no such restriction set.
-     */
-    @TargetApi(VERSION_CODES.O)
-    public static String getPersistentDoStateFromApplicationRestriction(
-            DevicePolicyManager dpm, ComponentName admin) {
-        Bundle restrictions = dpm.getApplicationRestrictions(admin, GMSCORE_PACKAGE);
-        return restrictions.getString(PERSISTENT_DEVICE_OWNER_STATE);
-    }
-
-    /**
-     * Sets the persistent device owner state by setting a special app restriction on GmsCore and
-     * notifies GmsCore about the change by sending a broadcast.
-     *
-     * @param state The device owner state to be preserved across factory resets. If null, the
-     * persistent device owner state and the corresponding restiction are cleared.
-     */
-    @TargetApi(VERSION_CODES.O)
-    public static void setPersistentDoStateWithApplicationRestriction(
-            Context context, DevicePolicyManager dpm, ComponentName admin, String state) {
-        Bundle restrictions = dpm.getApplicationRestrictions(admin, GMSCORE_PACKAGE);
-        if (state == null) {
-            // Clear the restriction
-            restrictions.remove(PERSISTENT_DEVICE_OWNER_STATE);
-        } else {
-            // Set the restriction
-            restrictions.putString(PERSISTENT_DEVICE_OWNER_STATE, state);
-        }
-        dpm.setApplicationRestrictions(admin, GMSCORE_PACKAGE, restrictions);
-        Intent broadcastIntent = new Intent(BROADCAST_ACTION_FRP_CONFIG_CHANGED);
-        broadcastIntent.setPackage(GMSCORE_PACKAGE);
-        broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        context.sendBroadcast(broadcastIntent);
-    }
-
     /** @return Intent for the default home activity */
     public static Intent getHomeIntent() {
         final Intent intent = new Intent(Intent.ACTION_MAIN);
@@ -257,6 +217,17 @@ public static IntentFilter getHomeIntentFilter() {
         return filter;
     }
 
+    /** @return Intent for a launcher activity */
+    public static Intent getLauncherIntent(Context context) {
+        Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
+        if (Util.isRunningOnTvDevice(context)) {
+            launcherIntent.addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER);
+        } else {
+            launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        }
+        return launcherIntent;
+    }
+
     private static DevicePolicyManager getDevicePolicyManager(Context context) {
         return (DevicePolicyManager)context.getSystemService(Service.DEVICE_POLICY_SERVICE);
     }
@@ -268,4 +239,9 @@ public static boolean hasDelegation(Context context, String delegation) {
         DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
         return dpm.getDelegatedScopes(null, context.getPackageName()).contains(delegation);
     }
+
+    public static boolean isRunningOnTvDevice(Context context) {
+        UiModeManager uiModeManager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE);
+        return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
+    }
 }
diff --git a/app/src/main/java/com/afwsamples/testdpc/common/keyvaluepair/KeyValueBundleArrayFragment.java b/app/src/main/java/com/afwsamples/testdpc/common/keyvaluepair/KeyValueBundleArrayFragment.java
index 1121873e..f0a77810 100644
--- a/app/src/main/java/com/afwsamples/testdpc/common/keyvaluepair/KeyValueBundleArrayFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/common/keyvaluepair/KeyValueBundleArrayFragment.java
@@ -135,13 +135,31 @@ protected void addNewRow() {
         //no need to re-create all the keys again in new created bundle, just automatically copy all keys from the existing one. This is extremely useful when the first bundle contains lots of keys.
         Bundle bundle = new Bundle();
 
-        if(mBundleList != null && mBundleList.size() > 0) {
+        if (mBundleList != null && mBundleList.size() > 0) {
             bundle = clearBundleValues((Bundle) mBundleList.get(0).clone());
         }
+
         mAdapter.add(bundle);
         showEditDialog(bundle);
     }
 
+    private Bundle clearBundleValues(Bundle bundle) {
+        Set<String> keySet = bundle.keySet();
+        for(String key : keySet) {
+            Object valueObject = bundle.get(key);
+            if(valueObject instanceof String) {
+                bundle.putString(key, "");
+            } else if(valueObject instanceof Integer) {
+                bundle.putInt(key, 0);
+            } else if(valueObject instanceof Boolean) {
+                bundle.putBoolean(key, false);
+            } else if(valueObject instanceof Bundle) {
+                bundle.putBundle(key, clearBundleValues((Bundle) valueObject));
+            }
+        }
+        return bundle;
+    }
+
     @Override
     protected void loadDefault() {}
 
@@ -196,22 +214,4 @@ protected String getDisplayName(Bundle entry) {
             return String.valueOf("Bundle #" + mBundleList.indexOf(entry));
         }
     }
-
-    private Bundle clearBundleValues(Bundle bundle) {
-
-        Set<String> keySet = bundle.keySet();
-        for(String key : keySet) {
-            Object valueObject = bundle.get(key);
-            if(valueObject instanceof String) {
-                bundle.putString(key, "");
-            } else if(valueObject instanceof Integer) {
-                bundle.putInt(key, 0);
-            } else if(valueObject instanceof Boolean) {
-                bundle.putBoolean(key, false);
-            } else if(valueObject instanceof Bundle) {
-                bundle.putBundle(key, clearBundleValues((Bundle) valueObject));
-            }
-        }
-        return bundle;
-    }
 }
diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PersistentDeviceOwnerFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PersistentDeviceOwnerFragment.java
deleted file mode 100644
index 2d256379..00000000
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PersistentDeviceOwnerFragment.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.afwsamples.testdpc.policy;
-
-import android.annotation.TargetApi;
-import android.app.Fragment;
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.EditText;
-
-import com.afwsamples.testdpc.DeviceAdminReceiver;
-import com.afwsamples.testdpc.R;
-import com.afwsamples.testdpc.common.Util;
-
-/**
- * Allows the user to set a test persistent device owner state.
- *
- * <p>For manual testing of forced re-enrollment.
- *
- * <p>If there is a non-empty peristent device owner state, it will survive the next factory reset,
- * TestDPC will be re-installed automatically as device owner and the state will be passed to it
- * during the initial device setup.
- */
-public class PersistentDeviceOwnerFragment extends Fragment implements View.OnClickListener {
-
-    private DevicePolicyManager mDpm;
-    private ComponentName mAdminComponent;
-    private EditText mStateEdit;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        getActivity().getActionBar().setTitle(R.string.persistent_device_owner);
-        mDpm = (DevicePolicyManager) getActivity().getSystemService(
-                Context.DEVICE_POLICY_SERVICE);
-        mAdminComponent = DeviceAdminReceiver.getComponentName(getActivity());
-    }
-
-    @Override
-    public View onCreateView(
-            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        View root = inflater.inflate(R.layout.persistent_device_owner_fragment, container, false);
-        root.findViewById(R.id.clear_persistent_device_owner_button).setOnClickListener(this);
-        root.findViewById(R.id.set_persistent_device_owner_button).setOnClickListener(this);
-        mStateEdit = (EditText) root.findViewById(R.id.persistent_device_owner_state_edit);
-        return root;
-    }
-
-    @Override
-    public void onClick(View view) {
-        String message = null;
-        switch (view.getId()) {
-            case R.id.clear_persistent_device_owner_button:
-                mStateEdit.getText().clear();
-                Util.setPersistentDoStateWithApplicationRestriction(
-                        getActivity(), mDpm, mAdminComponent, null);
-                break;
-            case R.id.set_persistent_device_owner_button:
-                Util.setPersistentDoStateWithApplicationRestriction(
-                        getActivity(), mDpm, mAdminComponent, mStateEdit.getText().toString());
-                break;
-        }
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        String state = Util.getPersistentDoStateFromApplicationRestriction(mDpm, mAdminComponent);
-        mStateEdit.setText(state == null ? "" : state);
-    }
-}
diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
index 1b7c3adc..1b913179 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -65,6 +65,7 @@
 import android.telephony.TelephonyManager;
 import android.text.InputType;
 import android.text.TextUtils;
+import android.util.Base64;
 import android.util.Log;
 import android.util.SparseIntArray;
 import android.view.LayoutInflater;
@@ -94,6 +95,7 @@
 import com.afwsamples.testdpc.AddAccountActivity;
 import com.afwsamples.testdpc.BuildConfig;
 import com.afwsamples.testdpc.CrossProfileAppsFragment;
+import com.afwsamples.testdpc.CrossProfileAppsWhitelistFragment;
 import com.afwsamples.testdpc.DeviceAdminReceiver;
 import com.afwsamples.testdpc.R;
 import com.afwsamples.testdpc.SetupManagementActivity;
@@ -117,6 +119,7 @@
 import com.afwsamples.testdpc.policy.keyguard.LockScreenPolicyFragment;
 import com.afwsamples.testdpc.policy.keyguard.PasswordConstraintsFragment;
 import com.afwsamples.testdpc.policy.keymanagement.GenerateKeyAndCertificateTask;
+import com.afwsamples.testdpc.policy.keymanagement.KeyGenerationParameters;
 import com.afwsamples.testdpc.policy.keymanagement.SignAndVerifyTask;
 import com.afwsamples.testdpc.policy.locktask.KioskModeActivity;
 import com.afwsamples.testdpc.policy.locktask.LockTaskAppInfoArrayAdapter;
@@ -369,7 +372,6 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private static final String CLEAR_APP_DATA_KEY = "clear_app_data";
     private static final String KEEP_UNINSTALLED_PACKAGES = "keep_uninstalled_packages";
     private static final String WIPE_DATA_KEY = "wipe_data";
-    private static final String PERSISTENT_DEVICE_OWNER_KEY = "persistent_device_owner";
     private static final String CREATE_WIFI_CONFIGURATION_KEY = "create_wifi_configuration";
     private static final String CREATE_EAP_TLS_WIFI_CONFIGURATION_KEY
             = "create_eap_tls_wifi_configuration";
@@ -384,6 +386,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private static final String SET_PROFILE_PARENT_NEW_PASSWORD = "set_profile_parent_new_password";
     private static final String BIND_DEVICE_ADMIN_POLICIES = "bind_device_admin_policies";
     private static final String CROSS_PROFILE_APPS = "cross_profile_apps";
+    private static final String CROSS_PROFILE_APPS_WHITELIST = "cross_profile_apps_whitelist";
     private static final String SET_SCREEN_BRIGHTNESS_KEY = "set_screen_brightness";
     private static final String AUTO_BRIGHTNESS_KEY = "auto_brightness";
     private static final String CROSS_PROFILE_CALENDAR_KEY = "cross_profile_calendar";
@@ -605,7 +608,6 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
                 STAY_ON_WHILE_PLUGGED_IN);
         mStayOnWhilePluggedInSwitchPreference.setOnPreferenceChangeListener(this);
         findPreference(WIPE_DATA_KEY).setOnPreferenceClickListener(this);
-        findPreference(PERSISTENT_DEVICE_OWNER_KEY).setOnPreferenceClickListener(this);
         findPreference(REMOVE_DEVICE_OWNER_KEY).setOnPreferenceClickListener(this);
         mEnableBackupServicePreference = (DpcSwitchPreference) findPreference(
             ENABLE_BACKUP_SERVICE);
@@ -709,6 +711,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         findPreference(SET_NEW_PASSWORD).setOnPreferenceClickListener(this);
         findPreference(SET_PROFILE_PARENT_NEW_PASSWORD).setOnPreferenceClickListener(this);
         findPreference(CROSS_PROFILE_APPS).setOnPreferenceClickListener(this);
+        findPreference(CROSS_PROFILE_APPS_WHITELIST).setOnPreferenceClickListener(this);
 
         findPreference(SET_SCREEN_BRIGHTNESS_KEY).setOnPreferenceClickListener(this);
         mAutoBrightnessPreference = (DpcSwitchPreference) findPreference(AUTO_BRIGHTNESS_KEY);
@@ -933,9 +936,6 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
             case WIPE_DATA_KEY:
                 showWipeDataPrompt();
                 return true;
-            case PERSISTENT_DEVICE_OWNER_KEY:
-                showFragment(new PersistentDeviceOwnerFragment());
-                return true;
             case REMOVE_DEVICE_OWNER_KEY:
                 showRemoveDeviceOwnerPrompt();
                 return true;
@@ -1216,6 +1216,9 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
             case CROSS_PROFILE_APPS:
                 showFragment(new CrossProfileAppsFragment());
                 return true;
+            case CROSS_PROFILE_APPS_WHITELIST:
+                showFragment(new CrossProfileAppsWhitelistFragment());
+                return true;
             case SET_SCREEN_BRIGHTNESS_KEY:
                 showSetScreenBrightnessDialog();
                 return true;
@@ -1227,11 +1230,21 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
                 return true;
             case SET_TIME_KEY:
                 // Disable auto time before we could set time manually.
-                setAutoTimeEnabled(false);
+                if (Util.SDK_INT >= VERSION_CODES.R) {
+                    setAutoTimeEnabled(false);
+                } else {
+                    mDevicePolicyManager.setGlobalSetting(mAdminComponentName,
+                            Settings.Global.AUTO_TIME, "0");
+                }
                 showSetTimeDialog();
                 return true;
             case SET_TIME_ZONE_KEY:
-                setAutoTimeZoneEnabled(false);
+                if (Util.SDK_INT >= VERSION_CODES.R) {
+                    setAutoTimeZoneEnabled(false);
+                } else {
+                    mDevicePolicyManager.setGlobalSetting(mAdminComponentName,
+                            Settings.Global.AUTO_TIME_ZONE, "0");
+                }
                 showSetTimeZoneDialog();
                 return true;
             case MANAGE_OVERRIDE_APN_KEY:
@@ -1617,14 +1630,8 @@ private boolean installKeyPair(final PrivateKey key, final Certificate cert, fin
         }
     }
 
-    private void generateKeyPair(final String alias, boolean isUserSelectable,
-                                 byte[] attestationChallenge,
-                                 int idAttestationFlags,
-                                 boolean useStrongBox,
-                                 boolean generateEcKey) {
-        new GenerateKeyAndCertificateTask(
-                alias, isUserSelectable, attestationChallenge, idAttestationFlags,
-                useStrongBox, generateEcKey, getActivity(), mAdminComponentName).execute();
+    private void generateKeyPair(final KeyGenerationParameters params) {
+        new GenerateKeyAndCertificateTask(params, getActivity(), mAdminComponentName).execute();
     }
 
     /**
@@ -1665,8 +1672,7 @@ private void showManageLockTaskListPrompt(int dialogTitle,
         if (getActivity() == null || getActivity().isFinishing()) {
             return;
         }
-        Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
-        launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        Intent launcherIntent = Util.getLauncherIntent(getActivity());
         final List<ResolveInfo> primaryUserAppList = mPackageManager
                 .queryIntentActivities(launcherIntent, 0);
         Intent homeIntent = new Intent(Intent.ACTION_MAIN);
@@ -2536,8 +2542,8 @@ private void reloadSetAutoTimeUi() {
         if (Util.SDK_INT < VERSION_CODES.R) {
             return;
         }
-        boolean isOrgOwned = 
-                mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
+        boolean isOrgOwned =
+            mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
 
         if (mDevicePolicyManager.isDeviceOwnerApp(mPackageName)
                 || (mDevicePolicyManager.isProfileOwnerApp(mPackageName)
@@ -2553,7 +2559,7 @@ private void reloadSetAutoTimeZoneUi() {
             return;
         }
         boolean isOrgOwned =
-                mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
+            mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
 
         if (mDevicePolicyManager.isDeviceOwnerApp(mPackageName)
                 || (mDevicePolicyManager.isProfileOwnerApp(mPackageName)
@@ -2779,6 +2785,13 @@ private void showPromptForGeneratedKeyAlias(String alias) {
                 R.id.include_device_meid_in_attestation);
         final CheckBox useStrongBoxCheckbox = aliasNamingView.findViewById(
                 R.id.use_strongbox);
+        final CheckBox useIndividualAttestationCheckbox = aliasNamingView.findViewById(
+                R.id.use_individual_attestation);
+        useIndividualAttestationCheckbox.setEnabled(Util.SDK_INT >= VERSION_CODES.R);
+
+        // Custom Challenge input
+        final EditText customChallengeInput = aliasNamingView.findViewById(
+                R.id.custom_challenge_input);
 
         new AlertDialog.Builder(getActivity())
                 .setTitle(getString(R.string.certificate_alias_prompt_title))
@@ -2786,12 +2799,17 @@ private void showPromptForGeneratedKeyAlias(String alias) {
                 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
-                        String alias = input.getText().toString();
-                        boolean isUserSelectable = userSelectableCheckbox.isChecked();
+                        KeyGenerationParameters.Builder paramsBuilder =
+                                new KeyGenerationParameters.Builder();
+                        paramsBuilder.setAlias(input.getText().toString());
+                        paramsBuilder.setIsUserSelectable(userSelectableCheckbox.isChecked());
 
-                        byte[] attestationChallenge = null;
                         if (includeAttestationChallengeCheckbox.isChecked()) {
-                            attestationChallenge = new byte[] {0x61, 0x62, 0x63};
+                            String customChallenge = customChallengeInput.getText().toString()
+                                .trim();
+                            byte[] decodedChallenge = Base64.decode(customChallenge,
+                                Base64.DEFAULT);
+                            paramsBuilder.setAttestationChallenge(decodedChallenge);
                         }
 
                         int idAttestationFlags = 0;
@@ -2807,10 +2825,15 @@ public void onClick(DialogInterface dialog, int which) {
                         if (deviceMeidAttestationCheckbox.isChecked()) {
                             idAttestationFlags |= DevicePolicyManager.ID_TYPE_MEID;
                         }
+                        if (useIndividualAttestationCheckbox.isChecked()) {
+                            idAttestationFlags |=
+                                    DevicePolicyManager.ID_TYPE_INDIVIDUAL_ATTESTATION;
+                        }
+                        paramsBuilder.setIdAttestationFlags(idAttestationFlags);
+                        paramsBuilder.setUseStrongBox(useStrongBoxCheckbox.isChecked());
+                        paramsBuilder.setGenerateEcKey(ecKeyCheckbox.isChecked());
 
-                        generateKeyPair(alias, isUserSelectable, attestationChallenge,
-                                idAttestationFlags, useStrongBoxCheckbox.isChecked(),
-                                ecKeyCheckbox.isChecked());
+                        generateKeyPair(paramsBuilder.build());
                     }
                 })
                 .setNegativeButton(android.R.string.cancel, null)
@@ -3378,8 +3401,7 @@ private boolean isPackageSuspended(String packageName) {
     }
 
     private List<ResolveInfo> getAllLauncherIntentResolversSorted() {
-        final Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
-        launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        final Intent launcherIntent = Util.getLauncherIntent(getActivity());
         final List<ResolveInfo> launcherIntentResolvers = mPackageManager
                 .queryIntentActivities(launcherIntent, 0);
         Collections.sort(launcherIntentResolvers,
diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/keymanagement/GenerateKeyAndCertificateTask.java b/app/src/main/java/com/afwsamples/testdpc/policy/keymanagement/GenerateKeyAndCertificateTask.java
index a758a013..1321c4a7 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/keymanagement/GenerateKeyAndCertificateTask.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/keymanagement/GenerateKeyAndCertificateTask.java
@@ -27,6 +27,7 @@
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.StrongBoxUnavailableException;
+import android.util.Base64;
 import android.util.Log;
 import android.view.View;
 import android.widget.TextView;
@@ -60,20 +61,15 @@ public class GenerateKeyAndCertificateTask extends AsyncTask<Void, Integer, Atte
     private final Activity mActivity;
 
     public GenerateKeyAndCertificateTask(
-            String alias,
-            boolean isUserSelectable,
-            byte[] attestationChallenge,
-            int idAttestationFlags,
-            boolean useStrongBox,
-            boolean generateEcKey,
+            KeyGenerationParameters params,
             Activity activity,
             ComponentName admin) {
-        mAlias = alias;
-        mIsUserSelectable = isUserSelectable;
-        mAttestationChallenge = attestationChallenge;
-        mIdAttestationFlags = idAttestationFlags;
-        mUseStrongBox = useStrongBox;
-        mGenerateEcKey = generateEcKey;
+        mAlias = params.alias;
+        mIsUserSelectable = params.isUserSelectable;
+        mAttestationChallenge = params.attestationChallenge;
+        mIdAttestationFlags = params.idAttestationFlags;
+        mUseStrongBox = params.useStrongBox;
+        mGenerateEcKey = params.generateEcKey;
         mActivity = activity;
         mAdminComponentName = admin;
         mDevicePolicyManager =
@@ -121,6 +117,15 @@ protected AttestedKeyPair doInBackground(Void... voids) {
                 return null;
             }
 
+            List<Certificate> attestationRecord = keyPair.getAttestationRecord();
+            if (attestationRecord != null) {
+                Log.i(TAG, "Attestation record:");
+                for (Certificate cert : attestationRecord) {
+                    Log.i(TAG, Base64.encodeToString(cert.getEncoded(), Base64.NO_WRAP));
+                }
+                Log.i(TAG, "End of attestation record.");
+            }
+
             X500Principal subject = new X500Principal("CN=TestDPC, O=Android, C=US");
             // Self-signed certificate: Same subject and issuer.
             X509Certificate selfSigned =
@@ -199,6 +204,9 @@ private void showKeyGenerationResult(AttestedKeyPair keyPair) {
                     attestationDetails.append(
                             mActivity.getText(R.string.device_meid_description) + "\n");
                     attestationDetails.append(teeList.getMeid() + "\n");
+                    attestationDetails.append(
+                            "Individual Attestation:" + "\n");
+                    attestationDetails.append(teeList.isIndividualAttestation() + "\n");
                 }
 
                 Certificate root = attestationChain.get(attestationChain.size() - 1);
diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/keymanagement/KeyGenerationParameters.java b/app/src/main/java/com/afwsamples/testdpc/policy/keymanagement/KeyGenerationParameters.java
new file mode 100644
index 00000000..83ed0efc
--- /dev/null
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/keymanagement/KeyGenerationParameters.java
@@ -0,0 +1,66 @@
+package com.afwsamples.testdpc.policy.keymanagement;
+
+public class KeyGenerationParameters {
+    public final String alias;
+    public final boolean isUserSelectable;
+    public final byte[] attestationChallenge;
+    public final int idAttestationFlags;
+    public final boolean useStrongBox;
+    public final boolean generateEcKey;
+
+    public KeyGenerationParameters(
+            String alias, boolean isUserSelectable, byte[] attestationChallenge,
+            int idAttestationFlags, boolean useStrongBox, boolean generateEcKey) {
+        this.alias = alias;
+        this.isUserSelectable = isUserSelectable;
+        this.attestationChallenge = attestationChallenge;
+        this.idAttestationFlags = idAttestationFlags;
+        this.useStrongBox = useStrongBox;
+        this.generateEcKey = generateEcKey;
+    }
+
+    public static class Builder {
+        private String mAlias;
+        private boolean mIsUserSelectable;
+        private byte[] mAttestationChallenge;
+        private int mIdAttestationFlags;
+        private boolean mUseStrongBox;
+        private boolean mGenerateEcKey;
+
+        public Builder setAlias(String alias) {
+            mAlias = alias;
+            return this;
+        }
+
+        public Builder setIsUserSelectable(boolean isUserSelectable) {
+            mIsUserSelectable = isUserSelectable;
+            return this;
+        }
+
+        public Builder setAttestationChallenge(byte[] attestationChallenge) {
+            mAttestationChallenge = attestationChallenge;
+            return this;
+        }
+
+        public Builder setIdAttestationFlags(int idAttestationFlags) {
+            mIdAttestationFlags = idAttestationFlags;
+            return this;
+        }
+
+        public Builder setUseStrongBox(boolean useStrongBox) {
+            mUseStrongBox = useStrongBox;
+            return this;
+        }
+
+        public Builder setGenerateEcKey(boolean generateEcKey) {
+            mGenerateEcKey = generateEcKey;
+            return this;
+        }
+
+        public KeyGenerationParameters build() {
+            return new KeyGenerationParameters(mAlias, mIsUserSelectable, mAttestationChallenge,
+                    mIdAttestationFlags, mUseStrongBox, mGenerateEcKey);
+        }
+
+    }
+}
diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/locktask/KioskModeActivity.java b/app/src/main/java/com/afwsamples/testdpc/policy/locktask/KioskModeActivity.java
index 0990a38c..c20d0162 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/locktask/KioskModeActivity.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/locktask/KioskModeActivity.java
@@ -269,7 +269,15 @@ public void onItemClick(AdapterView<?> parent, View view, int position, long id)
                 return;
             }
             PackageManager pm = getPackageManager();
-            startActivity(pm.getLaunchIntentForPackage(getItem(position)));
+            Intent launchAppIntent;
+            String appPackage = getItem(position);
+
+            if (Util.isRunningOnTvDevice(getContext())) {
+                launchAppIntent = pm.getLeanbackLaunchIntentForPackage(appPackage);
+            } else {
+                launchAppIntent = pm.getLaunchIntentForPackage(appPackage);
+            }
+            startActivity(launchAppIntent);
         }
     }
 }
diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/utils/AuthorizationList.java b/app/src/main/java/com/afwsamples/testdpc/policy/utils/AuthorizationList.java
index 47dfce21..b002a7d1 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/utils/AuthorizationList.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/utils/AuthorizationList.java
@@ -131,6 +131,7 @@ public class AuthorizationList {
     private static final int KM_TAG_ATTESTATION_ID_MODEL = KM_BYTES | 717;
     private static final int KM_TAG_VENDOR_PATCHLEVEL = KM_UINT | 718;
     private static final int KM_TAG_BOOT_PATCHLEVEL = KM_UINT | 719;
+    private static final int KM_TAG_DEVICE_UNIQUE_ATTESTATION = KM_BOOL | 720;
 
     // Map for converting padding values to strings
     private static final ImmutableMap<Integer, String> paddingMap = ImmutableMap
@@ -198,6 +199,7 @@ public class AuthorizationList {
     private String model;
     private boolean userPresenceRequired;
     private boolean confirmationRequired;
+    private boolean individualAttestation;
 
     @RequiresApi(api = VERSION_CODES.N)
     public AuthorizationList(ASN1Encodable sequence) throws CertificateParsingException {
@@ -323,6 +325,9 @@ public AuthorizationList(ASN1Encodable sequence) throws CertificateParsingExcept
                 case KM_TAG_TRUSTED_CONFIRMATION_REQUIRED & KEYMASTER_TAG_TYPE_MASK:
                     confirmationRequired = true;
                     break;
+                case KM_TAG_DEVICE_UNIQUE_ATTESTATION & KEYMASTER_TAG_TYPE_MASK:
+                    individualAttestation = true;
+                    break;
             }
         }
 
@@ -600,6 +605,10 @@ public boolean isConfirmationRequired() {
         return confirmationRequired;
     }
 
+    public boolean isIndividualAttestation() {
+        return individualAttestation;
+    }
+
     private String getStringFromAsn1Value(ASN1Primitive value) throws CertificateParsingException {
         try {
             return Asn1Utils.getStringFromAsn1OctetStreamAssumingUTF8(value);
@@ -709,6 +718,10 @@ public String toString() {
             s.append("\nConfirmation required");
         }
 
+        if (individualAttestation) {
+            s.append("\nIndividual attestation");
+        }
+
         if (brand != null) {
             s.append("\nBrand: ").append(brand);
         }
diff --git a/app/src/main/java/com/afwsamples/testdpc/provision/PostProvisioningTask.java b/app/src/main/java/com/afwsamples/testdpc/provision/PostProvisioningTask.java
index f9b6345b..5087ba51 100644
--- a/app/src/main/java/com/afwsamples/testdpc/provision/PostProvisioningTask.java
+++ b/app/src/main/java/com/afwsamples/testdpc/provision/PostProvisioningTask.java
@@ -35,7 +35,6 @@
 import android.os.PersistableBundle;
 import android.util.Log;
 import com.afwsamples.testdpc.AddAccountActivity;
-import com.afwsamples.testdpc.DeviceAdminReceiver;
 import com.afwsamples.testdpc.FinalizeActivity;
 import com.afwsamples.testdpc.common.LaunchIntentUtil;
 import com.afwsamples.testdpc.common.Util;
@@ -63,8 +62,6 @@ public class PostProvisioningTask {
             "com.afwsamples.testdpc.SetupManagementLaunchActivity";
     private static final String POST_PROV_PREFS = "post_prov_prefs";
     private static final String KEY_POST_PROV_DONE = "key_post_prov_done";
-    private static final String KEY_DEVICE_OWNER_STATE =
-          "android.app.extra.PERSISTENT_DEVICE_OWNER_STATE";
 
     private final Context mContext;
     private final DevicePolicyManager mDevicePolicyManager;
@@ -97,18 +94,6 @@ public boolean performPostProvisioningOperations(Intent intent) {
             maybeSetAffiliationIds(extras);
         }
 
-        // If TestDPC asked GmsCore to store its state in the FRP area before factory reset, the
-        // state will be handed over to it during the next device setup.
-        if (Util.SDK_INT >= VERSION_CODES.O_MR1
-            && extras != null
-            && extras.containsKey(KEY_DEVICE_OWNER_STATE)) {
-            Util.setPersistentDoStateWithApplicationRestriction(
-                mContext,
-                mDevicePolicyManager,
-                DeviceAdminReceiver.getComponentName(mContext),
-                extras.getString(KEY_DEVICE_OWNER_STATE));
-        }
-
         // Hide the setup launcher when this app is the admin
         mContext.getPackageManager().setComponentEnabledSetting(
                 new ComponentName(mContext, SETUP_MANAGEMENT_LAUNCH_ACTIVITY),
diff --git a/app/src/main/res/layout/cross_profile_apps_whitelist.xml b/app/src/main/res/layout/cross_profile_apps_whitelist.xml
new file mode 100644
index 00000000..b72c0ef2
--- /dev/null
+++ b/app/src/main/res/layout/cross_profile_apps_whitelist.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2020 Google Inc.
+
+    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.
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fadeScrollbars="false">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical"
+            android:paddingRight="@dimen/activity_horizontal_margin"
+            android:paddingLeft="@dimen/activity_horizontal_margin">
+
+          <TextView
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:text="@string/cross_profile_apps_enabled_title"
+              android:textSize="20sp"
+              android:paddingBottom="@dimen/content_padding_between_text"
+              android:paddingTop="@dimen/content_padding_between_text"/>
+
+          <TextView
+              android:id="@+id/cross_profile_app_list"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:textSize="16sp"
+              android:paddingBottom="@dimen/content_padding_bottom"/>
+
+            <EditText
+                android:id="@+id/cross_profile_app_whitelist_input"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/cross_profile_apps_example_text"
+                android:singleLine="false" />
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+                android:orientation="horizontal"
+                android:paddingBottom="@dimen/content_padding_between_text">
+
+              <Button
+                  android:id="@+id/cross_profile_app_whitelist_add_button"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:layout_marginTop="10dp"
+                  android:text="@string/cross_profile_apps_add_button_text" />
+
+              <Button
+                  android:id="@+id/cross_profile_app_whitelist_remove_button"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:layout_marginTop="10dp"
+                  android:text="@string/cross_profile_apps_remove_button_text" />
+            </LinearLayout>
+
+          <Button
+              android:id="@+id/cross_profile_app_whitelist_reset_button"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="@string/cross_profile_apps_reset_button_text" />
+        </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/key_generation_prompt.xml b/app/src/main/res/layout/key_generation_prompt.xml
index 8fdb8b1f..f01b4574 100644
--- a/app/src/main/res/layout/key_generation_prompt.xml
+++ b/app/src/main/res/layout/key_generation_prompt.xml
@@ -64,6 +64,18 @@
         android:layout_height="wrap_content"
         android:text="@string/key_attestation_checkbox"/>
 
+    <EditText
+        android:id="@+id/custom_challenge_input"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:lines="1"
+        android:maxLines="1"
+        android:scrollHorizontally="true"
+        android:ellipsize="end"
+        android:inputType="text"
+        android:text="YWJj"
+        android:hint="Custom Challenge (Base64)"/>
+
     <TextView
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
@@ -93,10 +105,21 @@
         android:layout_height="wrap_content"
         android:text="@string/meid_attestation_checkbox"/>
 
+    <TextView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/strongbox_features_description"/>
+
     <CheckBox
         android:id="@+id/use_strongbox"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:text="@string/use_strongbox_checkbox"/>
 
+    <CheckBox
+        android:id="@+id/use_individual_attestation"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/use_individual_attestation_checkbox"/>
+
 </LinearLayout>
diff --git a/app/src/main/res/layout/persistent_device_owner_fragment.xml b/app/src/main/res/layout/persistent_device_owner_fragment.xml
deleted file mode 100644
index 92543834..00000000
--- a/app/src/main/res/layout/persistent_device_owner_fragment.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2017 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.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:padding="10dp"
-    android:orientation="vertical">
-  <EditText android:id="@+id/persistent_device_owner_state_edit"
-      android:layout_height="wrap_content"
-      android:layout_width="match_parent"
-      android:lines="5"
-      android:layout_gravity="center"
-      android:hint="@string/persistent_device_owner_state_hint"/>
-  <LinearLayout
-      android:layout_height="wrap_content"
-      android:layout_width="wrap_content"
-      android:layout_gravity="center"
-      android:orientation="horizontal">
-    <Button android:id="@+id/clear_persistent_device_owner_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/clear_persistent_device_owner_label"
-        android:layout_margin="8dp"
-        android:visibility="visible"/>
-    <Button android:id="@+id/set_persistent_device_owner_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/set_persistent_device_owner_label"
-        android:layout_margin="8dp"
-        android:visibility="visible"/>
-  </LinearLayout>
-</LinearLayout>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 51e06173..ee5a1518 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -133,7 +133,6 @@
     <string name="wipe_data_title">Wipe data?</string>
     <string name="wipe_data_confirmation">Are you sure you want to wipe the data?</string>
     <string name="wipe_data_include">Also wipe:</string>
-    <string name="persistent_device_owner">Persistent device owner state</string>
     <string name="external_storage">External storage</string>
     <string name="reset_protection_data">Factory reset protection</string>
     <string name="remove_device_owner">Remove this device owner</string>
@@ -565,7 +564,10 @@
     <string name="serial_num_attestation_checkbox">Include device serial number</string>
     <string name="imei_attestation_checkbox">Include device IMEI</string>
     <string name="meid_attestation_checkbox">Include device MEID</string>
+    <string name="strongbox_features_description">StrongBox-related options</string>
     <string name="use_strongbox_checkbox">Use StrongBox</string>
+    <string name="use_individual_attestation_checkbox">Use Individual Attestation Certificate</string>
+
 
     <!-- Strings for app restrictions -->
     <string name="managed_configurations">Managed configurations</string>
@@ -1103,6 +1105,14 @@
     <string name="cross_profile_apps_available">TestDPC is installed in another profile, tap the icon to launch it in that profile.</string>
     <string name="cross_profile_apps_not_available">TestDPC is not installed in another profile, try installing it and come back to here.</string>
 
+    <string name="cross_profile_apps_whitelist">Whitelisted cross profile apps</string>
+    <string name="cross_profile_apps_enabled_title">Enabled cross profile apps</string>
+    <string name="cross_profile_apps_no_whitelisted_apps">No apps whitelisted</string>
+    <string name="cross_profile_apps_example_text">example.package.name</string>
+    <string name="cross_profile_apps_add_button_text">Add app</string>
+    <string name="cross_profile_apps_remove_button_text">Remove app</string>
+    <string name="cross_profile_apps_reset_button_text">Reset cross profile apps</string>
+
     <string name="install_update">Install update from file</string>
     <string name="install_update_prompt">This will attempt to install the update file ota.zip in the TestDPC files directory, if the file exists (i.e. if you pushed it there with adb). Do you want to continue?</string>
     <string name="install_update_prompt_yes">Yes</string>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 963bc04c..2313c902 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -743,11 +743,6 @@
             android:title="@string/reboot"
             testdpc:admin="deviceOwner"
             testdpc:minSdkVersion="N" />
-        <com.afwsamples.testdpc.common.preference.DpcPreference
-            android:key="persistent_device_owner"
-            android:title="@string/persistent_device_owner"
-            testdpc:admin="deviceOwner"
-            testdpc:minSdkVersion="O_MR1" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="set_factory_reset_protection_policy"
             android:title="@string/set_factory_reset_protection_policy"
@@ -786,5 +781,11 @@
             android:title="@string/cross_profile_apps_api"
             testdpc:admin="any"
             testdpc:minSdkVersion="P" />
+        <com.afwsamples.testdpc.common.preference.DpcPreference
+            android:key="cross_profile_apps_whitelist"
+            android:title="@string/cross_profile_apps_whitelist"
+            testdpc:admin="any"
+            testdpc:user="managedProfile"
+            testdpc:minSdkVersion="R" />
     </PreferenceCategory>
 </PreferenceScreen>
diff --git a/app/src/test/java/com/afwsamples/testdpc/common/PermissionsHelperTest.java b/app/src/test/java/com/afwsamples/testdpc/common/PermissionsHelperTest.java
deleted file mode 100644
index 37f6a400..00000000
--- a/app/src/test/java/com/afwsamples/testdpc/common/PermissionsHelperTest.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2016 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.afwsamples.testdpc.common;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.robolectric.Shadows.shadowOf;
-
-import android.Manifest.permission;
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PermissionInfo;
-import android.os.Build.VERSION_CODES;
-import androidx.test.core.app.ApplicationProvider;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowLog;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(minSdk = VERSION_CODES.M)
-public class PermissionsHelperTest {
-
-  private static final ComponentName TESTDPC_ADMIN =
-      new ComponentName("com.afwsamples.testdpc", "TestCls");
-  private static final ComponentName NON_TESTDPC_ADMIN =
-      new ComponentName("TestPkg", "TestCls");
-
-  private final Context mContext = RuntimeEnvironment.application;
-  private final DevicePolicyManager mDevicePolicyManager =
-      (DevicePolicyManager) ApplicationProvider.getApplicationContext()
-          .getSystemService(Context.DEVICE_POLICY_SERVICE);
-
-  // Permission protection levels should be defined by the framework/shadows and should not be set
-  // by the tests, however this is not the case now
-  private static final String MISSING_PERMISSION = "permission";
-  private static final String DANGEROUS_PERMISSION = permission.ACCESS_FINE_LOCATION;
-  private static final String NORMAL_PERMISSION = permission.ACCESS_WIFI_STATE;
-  private static final String MISSING_INFO_PERMISSION = permission.CHANGE_WIFI_STATE;
-
-  @Test
-  public void ensureRequiredPermissions_ifPermissionMissingFromManifest_shouldReturnFalseAndLogError() {
-    boolean requiredPermissionsGranted = PermissionsHelper
-        .ensureRequiredPermissions(new String[]{MISSING_PERMISSION}, NON_TESTDPC_ADMIN, mContext);
-
-    assertFalse(requiredPermissionsGranted);
-    assertTrue(ShadowLog.getLogsForTag(PermissionsHelper.TAG).get(0).msg
-        .contains("Missing required permission from manifest: " + MISSING_PERMISSION));
-  }
-
-  @Test
-  public void ensureRequiredPermissions_ifPermissionIsDangerousAndDpcIsProfileOwner_shouldReturnTrueAndSetPermissionGrantState() {
-    addPermissionInfo(DANGEROUS_PERMISSION, PermissionInfo.PROTECTION_DANGEROUS);
-    shadowOf(mDevicePolicyManager).setProfileOwner(TESTDPC_ADMIN);
-
-    boolean requiredPermissionsGranted = PermissionsHelper
-        .ensureRequiredPermissions(new String[]{DANGEROUS_PERMISSION}, TESTDPC_ADMIN, mContext);
-
-    assertTrue(requiredPermissionsGranted);
-    assertThat(mDevicePolicyManager
-        .getPermissionGrantState(TESTDPC_ADMIN, mContext.getPackageName(), DANGEROUS_PERMISSION))
-        .isEqualTo(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-  }
-
-  @Test
-  public void ensureRequiredPermissions_ifPermissionIsDangerousAndDpcIsDeviceOwner_shouldReturnTrueAndSetPermissionGrantState() {
-    addPermissionInfo(DANGEROUS_PERMISSION, PermissionInfo.PROTECTION_DANGEROUS);
-    shadowOf(mDevicePolicyManager).setDeviceOwner(TESTDPC_ADMIN);
-
-    boolean requiredPermissionsGranted = PermissionsHelper
-        .ensureRequiredPermissions(new String[]{DANGEROUS_PERMISSION}, TESTDPC_ADMIN, mContext);
-
-    assertTrue(requiredPermissionsGranted);
-    assertThat(mDevicePolicyManager
-        .getPermissionGrantState(TESTDPC_ADMIN, mContext.getPackageName(), DANGEROUS_PERMISSION))
-        .isEqualTo(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-  }
-
-  @Test
-  public void ensureRequiredPermissions_ifPermissionIsDangerousAndPermissionGrantStateIsAlreadySet_shouldReturnTrue() {
-    addPermissionInfo(DANGEROUS_PERMISSION, PermissionInfo.PROTECTION_DANGEROUS);
-    shadowOf(mDevicePolicyManager).setProfileOwner(TESTDPC_ADMIN);
-    mDevicePolicyManager.setPermissionGrantState(
-        TESTDPC_ADMIN,
-        mContext.getPackageName(),
-        DANGEROUS_PERMISSION,
-        DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-
-    boolean requiredPermissionsGranted = PermissionsHelper
-        .ensureRequiredPermissions(new String[]{DANGEROUS_PERMISSION}, TESTDPC_ADMIN, mContext);
-
-    assertTrue(requiredPermissionsGranted);
-    assertThat(mDevicePolicyManager
-        .getPermissionGrantState(TESTDPC_ADMIN, mContext.getPackageName(), DANGEROUS_PERMISSION))
-        .isEqualTo(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-  }
-
-  @Test
-  public void ensureRequiredPermissions_ifPermissionIsDangerousAndDpcIsNotOwner_shouldReturnFalse() {
-    addPermissionInfo(DANGEROUS_PERMISSION, PermissionInfo.PROTECTION_DANGEROUS);
-    shadowOf(mDevicePolicyManager).setProfileOwner(NON_TESTDPC_ADMIN);
-
-    boolean requiredPermissionsGranted = PermissionsHelper
-        .ensureRequiredPermissions(new String[]{DANGEROUS_PERMISSION}, NON_TESTDPC_ADMIN, mContext);
-
-    assertFalse(requiredPermissionsGranted);
-  }
-
-  @Test
-  public void ensureRequiredPermissions_ifPermissionInfoNotFound_shouldReturnTrueAndLogError() {
-    shadowOf(mDevicePolicyManager).setProfileOwner(TESTDPC_ADMIN);
-
-    boolean requiredPermissionsGranted = PermissionsHelper
-        .ensureRequiredPermissions(new String[]{MISSING_INFO_PERMISSION}, TESTDPC_ADMIN, mContext);
-
-    assertTrue(requiredPermissionsGranted);
-    assertTrue(ShadowLog.getLogsForTag(PermissionsHelper.TAG).get(0).msg
-        .contains("Failed to look up permission."));
-  }
-
-  @Test
-  public void ensureRequiredPermissions_ifPermissionIsNormal_shouldReturnTrueAndNotSetPermissionGrantState() {
-    addPermissionInfo(NORMAL_PERMISSION, PermissionInfo.PROTECTION_NORMAL);
-    shadowOf(mDevicePolicyManager).setProfileOwner(TESTDPC_ADMIN);
-
-    boolean requiredPermissionsGranted = PermissionsHelper
-        .ensureRequiredPermissions(new String[]{NORMAL_PERMISSION}, TESTDPC_ADMIN, mContext);
-
-    assertTrue(requiredPermissionsGranted);
-    assertThat(mDevicePolicyManager
-        .getPermissionGrantState(TESTDPC_ADMIN, mContext.getPackageName(), NORMAL_PERMISSION))
-        .isEqualTo(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-  }
-
-  @Test
-  public void ensureRequiredPermissions_ifAllPermissionsAreGranted_shouldReturnTrue() {
-    addPermissionInfo(NORMAL_PERMISSION, PermissionInfo.PROTECTION_NORMAL);
-    addPermissionInfo(DANGEROUS_PERMISSION, PermissionInfo.PROTECTION_DANGEROUS);
-    shadowOf(mDevicePolicyManager).setProfileOwner(TESTDPC_ADMIN);
-
-    boolean requiredPermissionsGranted = PermissionsHelper
-        .ensureRequiredPermissions(new String[]{NORMAL_PERMISSION, DANGEROUS_PERMISSION},
-            TESTDPC_ADMIN, mContext);
-
-    assertTrue(requiredPermissionsGranted);
-    assertThat(mDevicePolicyManager
-        .getPermissionGrantState(TESTDPC_ADMIN, mContext.getPackageName(), NORMAL_PERMISSION))
-        .isEqualTo(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-    assertThat(mDevicePolicyManager
-        .getPermissionGrantState(TESTDPC_ADMIN, mContext.getPackageName(), DANGEROUS_PERMISSION))
-        .isEqualTo(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-  }
-
-  @Test
-  public void ensureRequiredPermissions_ifAtLeastOnePermissionNotGranted_shouldReturnFalse() {
-    addPermissionInfo(NORMAL_PERMISSION, PermissionInfo.PROTECTION_NORMAL);
-    addPermissionInfo(DANGEROUS_PERMISSION, PermissionInfo.PROTECTION_DANGEROUS);
-    shadowOf(mDevicePolicyManager).setProfileOwner(NON_TESTDPC_ADMIN);
-
-    boolean requiredPermissionsGranted = PermissionsHelper
-        .ensureRequiredPermissions(new String[]{NORMAL_PERMISSION, DANGEROUS_PERMISSION},
-            NON_TESTDPC_ADMIN, mContext);
-
-    assertFalse(requiredPermissionsGranted);
-  }
-
-  private void addPermissionInfo(String permission, int protectionLevel) {
-    PermissionInfo permissionInfo = new PermissionInfo();
-    permissionInfo.name = permission;
-    permissionInfo.protectionLevel = protectionLevel;
-    shadowOf(mContext.getPackageManager()).addPermissionInfo(permissionInfo);
-  }
-
-}
\ No newline at end of file
diff --git a/app/src/test/java/com/afwsamples/testdpc/policy/PolicyManagementFragmentTest.java b/app/src/test/java/com/afwsamples/testdpc/policy/PolicyManagementFragmentTest.java
deleted file mode 100644
index 62be9679..00000000
--- a/app/src/test/java/com/afwsamples/testdpc/policy/PolicyManagementFragmentTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2016 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.afwsamples.testdpc.policy;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.robolectric.Shadows.shadowOf;
-
-import android.app.ActivityManager;
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import com.afwsamples.testdpc.DeviceAdminReceiver;
-import java.util.HashMap;
-import java.lang.reflect.Field;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-import org.robolectric.util.FragmentTestUtil;
-
-
-@RunWith(RobolectricTestRunner.class)
-@Config(minSdk = Build.VERSION_CODES.P)
-public class PolicyManagementFragmentTest{
-  private final Context mContext = RuntimeEnvironment.application;
-  private final DevicePolicyManager mDevicePolicyManager =
-      mContext.getSystemService(DevicePolicyManager.class);
-  private PolicyManagementFragment mPolicyFragment = new PolicyManagementFragment();
-
-  private static final String RELAUNCH_IN_LOCK_TASK = "relaunch_in_lock_task";
-
-  @Before
-  public void setup() throws Exception {
-    shadowOf(mDevicePolicyManager).setDeviceOwner(
-        new ComponentName(mContext.getApplicationContext(), DeviceAdminReceiver.class)
-    );
-  }
-
-  @After
-  public void tearDown() throws Exception {
-    resetFileProviderMap();
-  }
-
-  @Test
-  public void relaunchInLockTaskMode_lockTaskModeNone_launchWithCorrectIntent() {
-    relaunchWithTaskMode(ActivityManager.LOCK_TASK_MODE_NONE);
-
-    mPolicyFragment.findPreference(RELAUNCH_IN_LOCK_TASK).performClick();
-
-    Intent intent = shadowOf(RuntimeEnvironment.application).getNextStartedActivity();
-    assertThat(containsFlag(intent,Intent.FLAG_ACTIVITY_NEW_TASK)).isTrue();
-    assertThat(containsFlag(intent,Intent.FLAG_ACTIVITY_MULTIPLE_TASK)).isTrue();
-  }
-
-  @Test
-  public void relaunchInLockTaskMode_lockTaskModeLocked_launchWithCorrectIntent() {
-    relaunchWithTaskMode(ActivityManager.LOCK_TASK_MODE_LOCKED);
-
-    mPolicyFragment.findPreference(RELAUNCH_IN_LOCK_TASK).performClick();
-
-    Intent intent = shadowOf(RuntimeEnvironment.application).getNextStartedActivity();
-    assertThat(containsFlag(intent,Intent.FLAG_ACTIVITY_NEW_TASK)).isTrue();
-    assertThat(containsFlag(intent,Intent.FLAG_ACTIVITY_MULTIPLE_TASK)).isFalse();
-  }
-
-  private void relaunchWithTaskMode(int lockTaskModeState) {
-    shadowOf(mContext.getSystemService(ActivityManager.class))
-        .setLockTaskModeState(lockTaskModeState);
-    FragmentTestUtil.startFragment(mPolicyFragment);
-  }
-
-  private static boolean containsFlag(Intent intent, int flag) {
-    return (intent.getFlags() & flag) != 0;
-  }
-
-  /**
-   * This is a workaround for fixing {@link android.support.v4.content.FileProvider} throws
-   * "java.lang.IllegalArgumentException: Failed to find configured root that contains ..." when
-   * there are more than one test cases using FileProvider. For more details, see b/122474286.
-   */
-  private static void resetFileProviderMap() throws Exception {
-    Class<?> clazz = Class.forName("androidx.core.content.FileProvider");
-    Field field = clazz.getDeclaredField("sCache");
-    field.setAccessible(true);
-    field.set(null, new HashMap<>());
-  }
-}
-
diff --git a/settings.gradle b/settings.gradle
index 5e7c0a3e..29c40ab0 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,2 @@
 include ':TestDPC'
-project(':TestDPC').projectDir = new File(rootDir, "app")
+project(':TestDPC').projectDir = new File(rootDir, "app")
\ No newline at end of file