From d67698f604f58db46460ef11d2d4962f16078915 Mon Sep 17 00:00:00 2001
From: Jonathan Scott <scottjonathan@google.com>
Date: Tue, 4 Jun 2019 11:00:04 +0100
Subject: [PATCH 01/73] Bump version for release.

Test: N/A
Bug: 130713895
Change-Id: I0608e2fc7f18056ad1d131c76cd3d8e23509141b
---
 app/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/build.gradle b/app/build.gradle
index 65180e3f..766112b3 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,7 +9,7 @@ ext {
     // exactly 1 digit
     versionMinor = 1
     // exactly 2 digits
-    versionBuild = 00
+    versionBuild = 02
 }
 
 android {

From 1dc24e6bb80f0091cccbfd946dc7e741844cb81c Mon Sep 17 00:00:00 2001
From: Jonathan Scott <scottjonathan@google.com>
Date: Wed, 29 May 2019 11:49:21 +0100
Subject: [PATCH 02/73] Add tests for AppStatesService.

Test: ./gradlew test
Change-Id: I8729174b071446e26ce653e3f728acbc1ba5c7c4
---
 app/build.gradle                              |  10 +-
 .../testdpc/feedback/AppStatesService.java    |  43 ++-
 .../feedback/AppStatesServiceTest.java        | 274 ++++++++++++++++++
 3 files changed, 318 insertions(+), 9 deletions(-)
 create mode 100644 app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java

diff --git a/app/build.gradle b/app/build.gradle
index 766112b3..5e94cc7a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -51,6 +51,12 @@ android {
         textOutput "stdout"
     }
 
+    testOptions {
+        unitTests {
+            includeAndroidResources = true
+        }
+    }
+
     signingConfigs {
         debug {
             storeFile file("$projectDir/debug.keystore")
@@ -137,6 +143,8 @@ dependencies {
     implementation 'org.bouncycastle:bcpkix-jdk15on:1.56'
     implementation 'org.bouncycastle:bcprov-jdk15on:1.56'
     implementation 'com.google.guava:guava:23.6-android'
-
+    testImplementation 'org.robolectric:robolectric:4.2'
+    testImplementation "com.google.truth:truth:0.44"
+    testImplementation 'junit:junit:4.5'
     annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
 }
diff --git a/app/src/main/java/com/afwsamples/testdpc/feedback/AppStatesService.java b/app/src/main/java/com/afwsamples/testdpc/feedback/AppStatesService.java
index 9c7aa6fe..577d6579 100644
--- a/app/src/main/java/com/afwsamples/testdpc/feedback/AppStatesService.java
+++ b/app/src/main/java/com/afwsamples/testdpc/feedback/AppStatesService.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2019 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.feedback;
 
 import android.app.NotificationChannel;
@@ -7,6 +22,7 @@
 import androidx.core.app.NotificationManagerCompat;
 import androidx.preference.PreferenceManager;
 import android.util.Log;
+import androidx.annotation.VisibleForTesting;
 import androidx.enterprise.feedback.KeyedAppState;
 import androidx.enterprise.feedback.KeyedAppStatesService;
 import androidx.enterprise.feedback.ReceivedKeyedAppState;
@@ -26,6 +42,9 @@ public class AppStatesService extends KeyedAppStatesService {
   private static final String CHANNEL_ID = "KeyedAppStates";
   private static final String CHANNEL_NAME = "Keyed App States";
 
+  @VisibleForTesting
+  static final String TAG = "KeyedAppStates";
+
   private int nextNotificationId = 0;
   private Map<String, Integer> idMapping = new HashMap<>();
 
@@ -47,17 +66,25 @@ public void onReceive(Collection<ReceivedKeyedAppState> states, boolean requestS
   }
 
   private void showNotification(ReceivedKeyedAppState state, boolean requestSync) {
-    Log.i("KeyedAppStates",
-        state.timestamp() + " " +
-            state.packageName() + ":" +
-            state.key() + "=" +
-            state.data() + " (" +
-            state.message() + ")" + (requestSync ? " - SYNC REQUESTED" : ""));
+    final String logMessage = state.timestamp() + " " +
+        state.packageName() + ":" +
+        state.key() + "=" +
+        state.data() + " (" +
+        state.message() + ")" + (requestSync ? " - SYNC REQUESTED" : "");
+
+    if (state.severity() == KeyedAppState.SEVERITY_ERROR) {
+      Log.e(TAG, logMessage);
+    } else {
+      Log.i(TAG, logMessage);
+    }
+
+    final String severity = (state.severity() == KeyedAppState.SEVERITY_ERROR) ? "ERROR" :
+        (state.severity() == KeyedAppState.SEVERITY_INFO) ? "INFO" : "UNKNOWN";
 
     NotificationCompat.Builder notificationBuilder =
         new NotificationCompat.Builder(this, CHANNEL_ID)
             .setSmallIcon(R.drawable.arrow_down)
-            .setContentTitle(state.packageName() + ":" + state.key())
+            .setContentTitle(state.packageName() + ":" + state.key() + " " + severity)
             .setContentText(state.timestamp() + " " +
                 state.data() +
                 " (" + state.message() +")" +
@@ -83,4 +110,4 @@ private int getIdForState(ReceivedKeyedAppState state) {
     }
     return idMapping.get(key);
   }
-}
+}
\ No newline at end of file
diff --git a/app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java b/app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java
new file mode 100644
index 00000000..82ce40c9
--- /dev/null
+++ b/app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2019 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.feedback;
+
+import static android.util.Log.ERROR;
+import static android.util.Log.INFO;
+import static com.google.common.truth.Truth.assertThat;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Build.VERSION_CODES;
+import android.support.v7.preference.PreferenceManager;
+import androidx.enterprise.feedback.KeyedAppState;
+import androidx.enterprise.feedback.ReceivedKeyedAppState;
+import com.afwsamples.testdpc.R;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLog;
+import org.robolectric.shadows.ShadowNotification;
+import org.robolectric.shadows.ShadowNotificationManager;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(minSdk=VERSION_CODES.N) // Feedback channel is supported from N onwards
+public class AppStatesServiceTest {
+
+  private static final String REQUEST_SYNC_LOG_TEXT = "SYNC REQUESTED";
+
+  private static final ReceivedKeyedAppState STATE1 =
+      ReceivedKeyedAppState.builder()
+          .setPackageName("test.package")
+          .setTimestamp(123L)
+          .setSeverity(KeyedAppState.SEVERITY_INFO)
+          .setKey("key1")
+          .setMessage("message1")
+          .setData("data1")
+          .build();
+
+  private static final ReceivedKeyedAppState STATE1_DIFFERENT_MESSAGE =
+      ReceivedKeyedAppState.builder()
+          .setPackageName("test.package")
+          .setTimestamp(123L)
+          .setSeverity(KeyedAppState.SEVERITY_INFO)
+          .setKey("key1")
+          .setMessage("different message1")
+          .setData("data1")
+          .build();
+
+  private static final ReceivedKeyedAppState STATE2 =
+      ReceivedKeyedAppState.builder()
+          .setPackageName("test.package")
+          .setTimestamp(123L)
+          .setSeverity(KeyedAppState.SEVERITY_ERROR)
+          .setKey("key2")
+          .setMessage("message2")
+          .setData("data2")
+          .build();
+
+
+  private static final ReceivedKeyedAppState INFO_STATE = STATE1;
+  private static final ReceivedKeyedAppState ERROR_STATE = STATE2;
+
+  private final Context context = RuntimeEnvironment.application;
+  private final SharedPreferences preferences =
+      PreferenceManager.getDefaultSharedPreferences(context);
+  private final NotificationManager notificationManager =
+    context.getSystemService(NotificationManager.class);
+  private final AppStatesService service =
+    Robolectric.buildService(AppStatesService.class).get();
+
+  @Test
+  public void onReceive_shouldNotNotify_noNotification() {
+    setNotificationPreference(false);
+
+    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+
+    assertThat(shadowOf(notificationManager).getActiveNotifications()).isEmpty();
+  }
+
+  @Test
+  public void onReceive_shouldNotNotify_noLogs() {
+    setNotificationPreference(false);
+
+    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+
+    assertThat(ShadowLog.getLogsForTag(AppStatesService.TAG)).isEmpty();
+  }
+
+  @Test
+  public void onReceive_shouldNotify_logContainsRequiredInformation() {
+    setNotificationPreference(true);
+
+    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+
+    assertThatLogContainsRequiredInformation(
+        ShadowLog.getLogsForTag(AppStatesService.TAG).get(0), STATE1);
+  }
+
+  private void assertThatLogContainsRequiredInformation(
+      ShadowLog.LogItem logItem, ReceivedKeyedAppState state) {
+    assertThat(logItem.msg).contains(Long.toString(state.timestamp()));
+    assertThat(logItem.msg).contains(state.packageName());
+    assertThat(logItem.msg).contains(state.key());
+    assertThat(logItem.msg).contains(state.data());
+    assertThat(logItem.msg).contains(state.message());
+  }
+
+  @Test
+  public void onReceive_infoLog_shouldNotify_logIsInfoLevel() {
+    setNotificationPreference(true);
+
+    service.onReceive(Arrays.asList(INFO_STATE), /* requestSync= */ false);
+
+    assertThat(ShadowLog.getLogsForTag(AppStatesService.TAG).get(0).type).isEqualTo(INFO);
+  }
+
+  @Test
+  public void onReceive_errorLog_shouldNotify_logIsErrorLevel() {
+    setNotificationPreference(true);
+
+    service.onReceive(Arrays.asList(ERROR_STATE), /* requestSync= */ false);
+
+    assertThat(ShadowLog.getLogsForTag(AppStatesService.TAG).get(0).type).isEqualTo(ERROR);
+  }
+
+  @Test
+  public void onReceive_shouldNotify_noRequestSync_logDoesNotContainRequestSync() {
+    setNotificationPreference(true);
+
+    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+
+    ShadowLog.LogItem logItem = ShadowLog.getLogsForTag(AppStatesService.TAG).get(0);
+    assertThat(logItem.msg).doesNotContain(REQUEST_SYNC_LOG_TEXT);
+  }
+
+  @Test
+  public void onReceive_shouldNotify_requestSync_logContainsRequestSync() {
+    setNotificationPreference(true);
+
+    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ true);
+
+    ShadowLog.LogItem logItem = ShadowLog.getLogsForTag(AppStatesService.TAG).get(0);
+    assertThat(logItem.msg).contains(REQUEST_SYNC_LOG_TEXT);
+  }
+
+  @Test
+  public void onReceive_shouldNotify_oneLogPerState() {
+    setNotificationPreference(true);
+
+    service.onReceive(Arrays.asList(STATE1, STATE2), /* requestSync= */ false);
+
+    assertThat(ShadowLog.getLogsForTag(AppStatesService.TAG)).hasSize(2);
+  }
+
+  @Test
+  public void onReceive_shouldNotify_notificationContainsRequiredInformation() {
+    setNotificationPreference(true);
+
+    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+
+    assertThatNotificationContainsRequiredInformation(
+        shadowOf(notificationManager).getAllNotifications().get(0),
+        STATE1
+    );
+  }
+
+  private void assertThatNotificationContainsRequiredInformation(
+      Notification notification, ReceivedKeyedAppState state) {
+    assertThat(shadowOf(notification).getContentTitle().toString()).contains(state.packageName());
+    assertThat(shadowOf(notification).getContentTitle().toString()).contains(state.key());
+    assertThat(shadowOf(notification).getContentText().toString())
+      .contains(Long.toString(state.timestamp()));
+    assertThat(shadowOf(notification).getContentText().toString()).contains(state.data());
+    assertThat(shadowOf(notification).getContentText().toString()).contains(state.message());
+  }
+
+  @Test
+  public void onReceive_infoLog_shouldNotify_notificationTitleIncludesInfo() {
+    setNotificationPreference(true);
+
+    service.onReceive(Arrays.asList(INFO_STATE), /* requestSync= */ false);
+
+    final Notification notification = shadowOf(notificationManager).getAllNotifications().get(0);
+    assertThat(
+        shadowOf(notification).getContentTitle().toString()).contains("INFO");
+  }
+
+  @Test
+  public void onReceive_errorLog_shouldNotify_notificationTitleIncludesError() {
+    setNotificationPreference(true);
+
+    service.onReceive(Arrays.asList(ERROR_STATE), /* requestSync= */ false);
+
+    final Notification notification = shadowOf(notificationManager).getAllNotifications().get(0);
+    assertThat(
+        shadowOf(notification).getContentTitle().toString()).contains("ERROR");
+  }
+
+  @Test
+  public void onReceive_shouldNotify_noRequestSync_notificationDoesNotContainRequestSync() {
+    setNotificationPreference(true);
+
+    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+
+    final Notification notification = shadowOf(notificationManager).getAllNotifications().get(0);
+    assertThat(
+        shadowOf(notification).getContentText().toString()).doesNotContain(REQUEST_SYNC_LOG_TEXT);
+  }
+
+  @Test
+  public void onReceive_shouldNotify_requestSync_notificationContainsRequestSync() {
+    setNotificationPreference(true);
+
+    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ true);
+
+    final Notification notification = shadowOf(notificationManager).getAllNotifications().get(0);
+    assertThat(shadowOf(notification).getContentText().toString()).contains(REQUEST_SYNC_LOG_TEXT);
+  }
+
+  @Test
+  public void onReceive_shouldNotify_oneNotificationPerKey() {
+    setNotificationPreference(true);
+
+    service.onReceive(Arrays.asList(STATE1, STATE2), /* requestSync= */ false);
+
+    assertThat(shadowOf(notificationManager).getAllNotifications()).hasSize(2);
+  }
+
+  @Test
+  public void onReceive_multiple_shouldNotify_oneNotificationPerKey() {
+    setNotificationPreference(true);
+    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+
+    service.onReceive(Arrays.asList(STATE2), /* requestSync= */ false);
+
+    assertThat(shadowOf(notificationManager).getAllNotifications()).hasSize(2);
+  }
+
+  @Test
+  public void onReceive_shouldNotify_sameKeyUpdatesNotification() {
+    setNotificationPreference(true);
+    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+
+    service.onReceive(Arrays.asList(STATE1_DIFFERENT_MESSAGE), /* requestSync= */ false);
+
+    assertThat(shadowOf(notificationManager).getAllNotifications()).hasSize(1);
+  }
+
+  private void setNotificationPreference(boolean shouldNotify) {
+    preferences.edit()
+        .putBoolean(context.getString(R.string.app_feedback_notifications), shouldNotify)
+        .commit();
+  }
+}
\ No newline at end of file

From e742ebc5e05bc5c1f3851ccbc975ec69082b2f24 Mon Sep 17 00:00:00 2001
From: kholoud mohamed <kholoudm@google.com>
Date: Tue, 4 Jun 2019 18:00:17 +0100
Subject: [PATCH 03/73] remove login screen from admin-integrated flow

Bug: 133139326
Test: N/A
Change-Id: I6fa4644642e29842951fa03cd8c91698c37f6e58
---
 .../testdpc/provision/DpcLoginActivity.java   | 41 +------------------
 1 file changed, 2 insertions(+), 39 deletions(-)

diff --git a/app/src/main/java/com/afwsamples/testdpc/provision/DpcLoginActivity.java b/app/src/main/java/com/afwsamples/testdpc/provision/DpcLoginActivity.java
index 5ace02e0..60f02f73 100644
--- a/app/src/main/java/com/afwsamples/testdpc/provision/DpcLoginActivity.java
+++ b/app/src/main/java/com/afwsamples/testdpc/provision/DpcLoginActivity.java
@@ -16,23 +16,16 @@
 
 package com.afwsamples.testdpc.provision;
 
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_MODE;
 import static android.app.admin.DevicePolicyManager.PROVISIONING_MODE_FULLY_MANAGED_DEVICE;
 import static android.app.admin.DevicePolicyManager.PROVISIONING_MODE_MANAGED_PROFILE;
 
-import android.accounts.Account;
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
-import android.os.PersistableBundle;
-import android.util.Log;
 import android.view.View;
 import android.widget.RadioGroup;
-import com.afwsamples.testdpc.AddAccountActivity;
 import com.afwsamples.testdpc.R;
-import com.afwsamples.testdpc.common.LaunchIntentUtil;
 import com.android.setupwizardlib.GlifLayout;
 
 /**
@@ -41,9 +34,6 @@
  */
 public class DpcLoginActivity extends Activity {
 
-    private static final String LOG_TAG = "DpcLoginActivity";
-    private static final int ADD_ACCOUNT_REQUEST_CODE = 1;
-
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -58,32 +48,6 @@ public void onBackPressed() {
         super.onBackPressed();
     }
 
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        switch (requestCode) {
-            case ADD_ACCOUNT_REQUEST_CODE:
-                finishWithIntent(createResultIntentFromData(data));
-                break;
-            default:
-                Log.d(LOG_TAG, "Unknown result code: " + resultCode);
-                break;
-        }
-    }
-
-    private Intent createResultIntentFromData(Intent data) {
-        final Intent resultIntent = new Intent();
-        resultIntent.putExtra(EXTRA_PROVISIONING_MODE, PROVISIONING_MODE_MANAGED_PROFILE);
-        if (data != null && data.hasExtra(EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE)) {
-            final Account accountToMigrate = data.getParcelableExtra(
-                EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE);
-            resultIntent.putExtra(EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE, accountToMigrate);
-            final PersistableBundle bundle = new PersistableBundle();
-            bundle.putString(LaunchIntentUtil.EXTRA_ACCOUNT_NAME, accountToMigrate.name);
-            resultIntent.putExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE, bundle);
-        }
-        return resultIntent;
-    }
-
     private void onNavigateNext(View nextButton) {
         final Intent intent = new Intent();
         RadioGroup dpcLoginOptions = findViewById(R.id.dpc_login_options);
@@ -93,9 +57,8 @@ private void onNavigateNext(View nextButton) {
                 finishWithIntent(intent);
                 return;
             case R.id.dpc_login_po:
-                startActivityForResult(
-                    new Intent(getApplicationContext(), AddAccountActivity.class),
-                    ADD_ACCOUNT_REQUEST_CODE);
+                intent.putExtra(EXTRA_PROVISIONING_MODE, PROVISIONING_MODE_MANAGED_PROFILE);
+                finishWithIntent(intent);
                 return;
             default:
                 finish();

From f751b1f5e6070aaaf3d14b679f528a4fb373c501 Mon Sep 17 00:00:00 2001
From: kholoud mohamed <kholoudm@google.com>
Date: Wed, 5 Jun 2019 13:23:26 +0100
Subject: [PATCH 04/73] fix error in AppStatesServiceTest

Test: ./gradlew test
Change-Id: Icfa9e7aa5ce62bd2d355b0a2cb5b66b2da1df9b4
---
 .../com/afwsamples/testdpc/feedback/AppStatesServiceTest.java | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java b/app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java
index 82ce40c9..ced7820f 100644
--- a/app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java
+++ b/app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java
@@ -25,9 +25,9 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.Build.VERSION_CODES;
-import android.support.v7.preference.PreferenceManager;
 import androidx.enterprise.feedback.KeyedAppState;
 import androidx.enterprise.feedback.ReceivedKeyedAppState;
+import androidx.preference.PreferenceManager;
 import com.afwsamples.testdpc.R;
 import java.util.Arrays;
 import org.junit.Test;
@@ -37,8 +37,6 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowLog;
-import org.robolectric.shadows.ShadowNotification;
-import org.robolectric.shadows.ShadowNotificationManager;
 
 @RunWith(RobolectricTestRunner.class)
 @Config(minSdk=VERSION_CODES.N) // Feedback channel is supported from N onwards

From d8250e62c0f3348b1dd0ff73b77916e22fe3a3da Mon Sep 17 00:00:00 2001
From: kholoud mohamed <kholoudm@google.com>
Date: Wed, 5 Jun 2019 15:57:16 +0100
Subject: [PATCH 05/73] add tests for DpcLoginActivity

Fixes:133139326
Test: ./gradlew test
Change-Id: I77c5a3de168daa5e16e81238cc889470c6f70ed6
---
 app/build.gradle                              |  1 +
 .../provision/DpcLoginActivityTest.java       | 78 +++++++++++++++++++
 build.gradle                                  |  2 +-
 3 files changed, 80 insertions(+), 1 deletion(-)
 create mode 100644 app/src/test/java/com/afwsamples/testdpc/provision/DpcLoginActivityTest.java

diff --git a/app/build.gradle b/app/build.gradle
index 5e94cc7a..61ff4efc 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -146,5 +146,6 @@ dependencies {
     testImplementation 'org.robolectric:robolectric:4.2'
     testImplementation "com.google.truth:truth:0.44"
     testImplementation 'junit:junit:4.5'
+    testImplementation 'androidx.test:core:1.0.0'
     annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
 }
diff --git a/app/src/test/java/com/afwsamples/testdpc/provision/DpcLoginActivityTest.java b/app/src/test/java/com/afwsamples/testdpc/provision/DpcLoginActivityTest.java
new file mode 100644
index 00000000..c11e24df
--- /dev/null
+++ b/app/src/test/java/com/afwsamples/testdpc/provision/DpcLoginActivityTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2019 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.provision;
+
+import static android.app.Activity.RESULT_CANCELED;
+import static android.app.Activity.RESULT_OK;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_MODE;
+import static android.app.admin.DevicePolicyManager.PROVISIONING_MODE_FULLY_MANAGED_DEVICE;
+import static android.app.admin.DevicePolicyManager.PROVISIONING_MODE_MANAGED_PROFILE;
+import static com.google.common.truth.Truth.assertThat;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.os.Build.VERSION_CODES;
+import android.widget.RadioButton;
+import androidx.test.core.app.ActivityScenario;
+import com.afwsamples.testdpc.R;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+// TODO: update to Q when Q is testable
+@Config(minSdk = VERSION_CODES.P)
+public class DpcLoginActivityTest {
+
+  @Test
+  public void onNavigateNext_poRadioBoxSelected_shouldFinishWithCorrectIntent() {
+    ActivityScenario.launch(DpcLoginActivity.class).onActivity(activity -> {
+      RadioButton dpcLoginPo = activity.findViewById(R.id.dpc_login_po);
+      dpcLoginPo.setChecked(true);
+
+      activity.findViewById(R.id.suw_navbar_next).performClick();
+
+      assertThat(shadowOf(activity).getResultCode()).isEqualTo(RESULT_OK);
+      assertThat(shadowOf(activity).getResultIntent().getIntExtra(EXTRA_PROVISIONING_MODE, -1))
+          .isEqualTo(PROVISIONING_MODE_MANAGED_PROFILE);
+    });
+  }
+
+  @Test
+  public void onNavigateNext_doRadioBoxSelected_shouldFinishWithCorrectIntent() {
+    ActivityScenario.launch(DpcLoginActivity.class).onActivity(activity -> {
+      RadioButton dpcLoginDo = activity.findViewById(R.id.dpc_login_do);
+      dpcLoginDo.setChecked(true);
+
+      activity.findViewById(R.id.suw_navbar_next).performClick();
+
+      assertThat(shadowOf(activity).getResultCode()).isEqualTo(RESULT_OK);
+      assertThat(shadowOf(activity).getResultIntent().getIntExtra(EXTRA_PROVISIONING_MODE, -1))
+          .isEqualTo(PROVISIONING_MODE_FULLY_MANAGED_DEVICE);
+    });
+  }
+
+  @Test
+  public void onBackPressed_shouldSetResultToCancelled() {
+    ActivityScenario.launch(DpcLoginActivity.class).onActivity(activity -> {
+
+      activity.onBackPressed();
+
+      assertThat(shadowOf(activity).getResultCode()).isEqualTo(RESULT_CANCELED);
+    });
+  }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index c77c4e30..72d6d248 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ buildscript {
      maven {url "$rootDir/../../../../prebuilts/tools/common/m2/repository"}
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.0.1'
+        classpath 'com.android.tools.build:gradle:3.4.1'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files

From eba130286d4e53f5d85bd4da9d8256b5ae30ea03 Mon Sep 17 00:00:00 2001
From: Jonathan Scott <scottjonathan@google.com>
Date: Thu, 6 Jun 2019 11:38:39 +0100
Subject: [PATCH 06/73] Bump version of enterprise-feedback.

Test: ./gradlew test

Change-Id: Ie3016698e59f984e94d65e5a8db2328e112dd56d
---
 app/build.gradle                              |  2 +-
 .../testdpc/feedback/AppStatesService.java    | 26 +++++++++----------
 .../feedback/AppStatesServiceTest.java        | 20 +++++++-------
 3 files changed, 24 insertions(+), 24 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index 61ff4efc..793e8532 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -133,7 +133,7 @@ private void stripTestOnlyForBuild(flavor, buildType) {
 dependencies {
     def lifecycle_version = "2.0.0"
     implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
-    implementation 'androidx.enterprise:enterprise-feedback:1.0.0-alpha01'
+    implementation 'androidx.enterprise:enterprise-feedback:1.0.0-alpha02'
     implementation 'androidx.multidex:multidex:2.0.0'
     implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
     implementation 'androidx.recyclerview:recyclerview:1.0.0'
diff --git a/app/src/main/java/com/afwsamples/testdpc/feedback/AppStatesService.java b/app/src/main/java/com/afwsamples/testdpc/feedback/AppStatesService.java
index 577d6579..7b678f6c 100644
--- a/app/src/main/java/com/afwsamples/testdpc/feedback/AppStatesService.java
+++ b/app/src/main/java/com/afwsamples/testdpc/feedback/AppStatesService.java
@@ -66,28 +66,28 @@ public void onReceive(Collection<ReceivedKeyedAppState> states, boolean requestS
   }
 
   private void showNotification(ReceivedKeyedAppState state, boolean requestSync) {
-    final String logMessage = state.timestamp() + " " +
-        state.packageName() + ":" +
-        state.key() + "=" +
-        state.data() + " (" +
-        state.message() + ")" + (requestSync ? " - SYNC REQUESTED" : "");
+    final String logMessage = state.getTimestamp() + " " +
+        state.getPackageName() + ":" +
+        state.getKey() + "=" +
+        state.getData() + " (" +
+        state.getMessage() + ")" + (requestSync ? " - SYNC REQUESTED" : "");
 
-    if (state.severity() == KeyedAppState.SEVERITY_ERROR) {
+    if (state.getSeverity() == KeyedAppState.SEVERITY_ERROR) {
       Log.e(TAG, logMessage);
     } else {
       Log.i(TAG, logMessage);
     }
 
-    final String severity = (state.severity() == KeyedAppState.SEVERITY_ERROR) ? "ERROR" :
-        (state.severity() == KeyedAppState.SEVERITY_INFO) ? "INFO" : "UNKNOWN";
+    final String severity = (state.getSeverity() == KeyedAppState.SEVERITY_ERROR) ? "ERROR" :
+        (state.getSeverity() == KeyedAppState.SEVERITY_INFO) ? "INFO" : "UNKNOWN";
 
     NotificationCompat.Builder notificationBuilder =
         new NotificationCompat.Builder(this, CHANNEL_ID)
             .setSmallIcon(R.drawable.arrow_down)
-            .setContentTitle(state.packageName() + ":" + state.key() + " " + severity)
-            .setContentText(state.timestamp() + " " +
-                state.data() +
-                " (" + state.message() +")" +
+            .setContentTitle(state.getPackageName() + ":" + state.getKey() + " " + severity)
+            .setContentText(state.getTimestamp() + " " +
+                state.getData() +
+                " (" + state.getMessage() +")" +
                 (requestSync ? "\nSYNC REQUESTED" : ""));
     NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
     notificationManager.notify(getIdForState(state), notificationBuilder.build());
@@ -103,7 +103,7 @@ private void createNotificationChannel() {
   }
 
   private int getIdForState(ReceivedKeyedAppState state) {
-    String key = state.packageName() + ":" + state.key();
+    String key = state.getPackageName() + ":" + state.getKey();
 
     if (!idMapping.containsKey(key)) {
       idMapping.put(key, nextNotificationId++);
diff --git a/app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java b/app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java
index ced7820f..da9cdd28 100644
--- a/app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java
+++ b/app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java
@@ -116,11 +116,11 @@ public void onReceive_shouldNotify_logContainsRequiredInformation() {
 
   private void assertThatLogContainsRequiredInformation(
       ShadowLog.LogItem logItem, ReceivedKeyedAppState state) {
-    assertThat(logItem.msg).contains(Long.toString(state.timestamp()));
-    assertThat(logItem.msg).contains(state.packageName());
-    assertThat(logItem.msg).contains(state.key());
-    assertThat(logItem.msg).contains(state.data());
-    assertThat(logItem.msg).contains(state.message());
+    assertThat(logItem.msg).contains(Long.toString(state.getTimestamp()));
+    assertThat(logItem.msg).contains(state.getPackageName());
+    assertThat(logItem.msg).contains(state.getKey());
+    assertThat(logItem.msg).contains(state.getData());
+    assertThat(logItem.msg).contains(state.getMessage());
   }
 
   @Test
@@ -184,12 +184,12 @@ public void onReceive_shouldNotify_notificationContainsRequiredInformation() {
 
   private void assertThatNotificationContainsRequiredInformation(
       Notification notification, ReceivedKeyedAppState state) {
-    assertThat(shadowOf(notification).getContentTitle().toString()).contains(state.packageName());
-    assertThat(shadowOf(notification).getContentTitle().toString()).contains(state.key());
+    assertThat(shadowOf(notification).getContentTitle().toString()).contains(state.getPackageName());
+    assertThat(shadowOf(notification).getContentTitle().toString()).contains(state.getKey());
     assertThat(shadowOf(notification).getContentText().toString())
-      .contains(Long.toString(state.timestamp()));
-    assertThat(shadowOf(notification).getContentText().toString()).contains(state.data());
-    assertThat(shadowOf(notification).getContentText().toString()).contains(state.message());
+      .contains(Long.toString(state.getTimestamp()));
+    assertThat(shadowOf(notification).getContentText().toString()).contains(state.getData());
+    assertThat(shadowOf(notification).getContentText().toString()).contains(state.getMessage());
   }
 
   @Test

From 7e529a799ec0286a70b1a3aedeec8cf57aed30fc Mon Sep 17 00:00:00 2001
From: Jonathan Scott <scottjonathan@google.com>
Date: Thu, 13 Jun 2019 17:31:50 +0100
Subject: [PATCH 07/73] Fix crash on L.

Test: Manual
Change-Id: I1608c85dc23cca7af42216a94fe981fb65e0f43a
---
 .../testdpc/policy/PolicyManagementFragment.java           | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

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 0c26436e..9036ff53 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -2102,8 +2102,13 @@ public void onClick(DialogInterface dialogInterface, int i) {
                 .show();
     }
 
-    @TargetApi(VERSION_CODES.M)
+    @TargetApi(VERSION_CODES.N)
     private void loadAppFeedbackNotifications() {
+        if (Util.SDK_INT < VERSION_CODES.N) {
+            // This toggle is only available >= N due to device_policy_header.xml
+            // so this code not executing will not be noticed
+            return;
+        }
         mEnableAppFeedbackNotificationsPreference.setChecked(
             PreferenceManager.getDefaultSharedPreferences(getContext())
                 .getBoolean(getString(R.string.app_feedback_notifications), false));

From 84df07edac68dd07b4fe73d8c207b10839abed42 Mon Sep 17 00:00:00 2001
From: kholoud mohamed <kholoudm@google.com>
Date: Fri, 7 Jun 2019 17:17:00 +0100
Subject: [PATCH 08/73] grant required permissions for wifi configuration

Fixes: 120591423
Test: ./gradlew test
Change-Id: Ic74ed576c0719bf85c9e3a4ab1aaf82890afb196
---
 app/src/main/AndroidManifest.xml              |   1 +
 .../testdpc/common/PermissionsHelper.java     | 118 +++++++++++
 .../WifiConfigCreationDialog.java             |   4 +-
 .../policy/wifimanagement/WifiConfigUtil.java |  57 +++---
 .../WifiModificationFragment.java             |  23 ++-
 .../testdpc/common/PermissionsHelperTest.java | 190 ++++++++++++++++++
 6 files changed, 352 insertions(+), 41 deletions(-)
 create mode 100644 app/src/main/java/com/afwsamples/testdpc/common/PermissionsHelper.java
 create mode 100644 app/src/test/java/com/afwsamples/testdpc/common/PermissionsHelperTest.java

diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 498dfb0a..0f99cfd5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -30,6 +30,7 @@
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
     <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"/>
 
     <application
             android:allowBackup="true"
diff --git a/app/src/main/java/com/afwsamples/testdpc/common/PermissionsHelper.java b/app/src/main/java/com/afwsamples/testdpc/common/PermissionsHelper.java
new file mode 100644
index 00000000..1f3a5454
--- /dev/null
+++ b/app/src/main/java/com/afwsamples/testdpc/common/PermissionsHelper.java
@@ -0,0 +1,118 @@
+/*
+ * 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 android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PermissionInfo;
+import android.os.Build.VERSION_CODES;
+import android.util.Log;
+import androidx.annotation.RequiresApi;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Checks that the requested permissions are present, and grants any dangerous
+ * permissions required.
+ */
+public class PermissionsHelper {
+
+  public static String TAG = "PermissionsHelper";
+
+  /**
+   * Ensures that the passed in permissions are defined in manifest and attempts to grant a
+   * permission automatically if it is considered dangerous.
+   */
+  @RequiresApi(VERSION_CODES.M)
+  public static boolean ensureRequiredPermissions(String[] requiredPermissions, ComponentName admin,
+      Context context) {
+    PackageInfo packageInfo;
+    try {
+      packageInfo =
+          context
+              .getPackageManager()
+              .getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
+    } catch (NameNotFoundException e) {
+      Log.e(TAG, "Could not find own package.", e);
+      return false;
+    }
+    List<String> manifestPermissions = Arrays.asList(packageInfo.requestedPermissions);
+    for (String expectedPermission : requiredPermissions) {
+      if (!manifestPermissions.contains(expectedPermission)) {
+        Log.e(TAG, "Missing required permission from manifest: " + expectedPermission);
+        return false;
+      }
+      if (!maybeGrantDangerousPermission(expectedPermission, admin, context)){
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Attempts to grant a permission automatically if it is considered dangerous - this only happens
+   * for PO/DO devices.
+   */
+  @RequiresApi(VERSION_CODES.M)
+  private static boolean maybeGrantDangerousPermission(String permission, ComponentName admin,
+      Context context) {
+    if (!isPermissionDangerous(permission, context)) {
+      return true;
+    }
+    if (!ProvisioningStateUtil.isManagedByTestDPC(context)) {
+      return false;
+    }
+    if (hasPermissionGranted(admin, context, permission)) {
+      return true;
+    }
+    DevicePolicyManager devicePolicyManager = (DevicePolicyManager) context
+        .getSystemService(Context.DEVICE_POLICY_SERVICE);
+    return devicePolicyManager.setPermissionGrantState(
+        admin,
+        context.getPackageName(),
+        permission,
+        DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+  }
+
+
+  // Min API version required for DevicePolicyManager.getPermissionGrantState
+  @RequiresApi(VERSION_CODES.M)
+  private static boolean hasPermissionGranted(
+      ComponentName componentName, Context context, String permission) {
+    DevicePolicyManager devicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+    return devicePolicyManager
+        .getPermissionGrantState(componentName, context.getPackageName(), permission)
+        == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
+  }
+
+  private static boolean isPermissionDangerous(String permission, Context context) {
+    PermissionInfo permissionInfo;
+    try {
+      permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
+    } catch (NameNotFoundException e) {
+      Log.e(TAG, "Failed to look up permission.", e);
+      return false;
+    }
+    return permissionInfo != null
+        && (permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
+        == PermissionInfo.PROTECTION_DANGEROUS;
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigCreationDialog.java b/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigCreationDialog.java
index d70c9426..a7629d7e 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigCreationDialog.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigCreationDialog.java
@@ -19,10 +19,8 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
-import android.content.Context;
 import android.content.DialogInterface;
 import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
 import android.os.Bundle;
 import android.text.Editable;
 import android.text.TextWatcher;
@@ -191,7 +189,7 @@ private void updateConfigurationSecurity(WifiConfiguration config) {
                     String password = mPasswordText.getText().toString();
                     // WEP-40, WEP-104, and 256-bit WEP (WEP-232?)
                     if ((length == 10 || length == 26 || length == 58)
-                            && password.matches("[0-9A-Fa-f]*")) {
+                        && password.matches("[0-9A-Fa-f]*")) {
                         config.wepKeys[0] = password;
                     } else {
                         config.wepKeys[0] = getQuotedString(password);
diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtil.java b/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtil.java
index d17374d9..f9010158 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtil.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtil.java
@@ -22,39 +22,30 @@
 
 public class WifiConfigUtil {
 
-    /**
-     * Save or replace the wifi configuration.
-     *
-     * @param context
-     * @param wifiConfiguration
-     * @return success to add/replace the wifi configuration
-     */
-    public static boolean saveWifiConfiguration(Context context, WifiConfiguration
-            wifiConfiguration) {
-        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-        final int networkId;
-
-        if (wifiConfiguration.networkId == -1) {
-            // new wifi configuration, add it and then save it.
-            networkId = wifiManager.addNetwork(wifiConfiguration);
-        } else {
-            // existing wifi configuration, update it and then save it.
-            networkId = wifiManager.updateNetwork(wifiConfiguration);
-        }
-
-        if (networkId == -1) {
-            return false;
-        }
-
-        // Added successfully, try to save it now.
-        wifiManager.enableNetwork(networkId, /* disableOthers */ false);
-        if (wifiManager.saveConfiguration()) {
-            return true;
-        } else {
-            // Remove the added network that fail to save.
-            wifiManager.removeNetwork(networkId);
-            return false;
-        }
+  /**
+   * Save or replace the wifi configuration.
+   *
+   * @return success to add/replace the wifi configuration
+   */
+  public static boolean saveWifiConfiguration(Context context, WifiConfiguration
+      wifiConfiguration) {
+    WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+    final int networkId;
+
+    // WifiManager deprecated APIs including #addNetwork, #updateNetwork, #enableNetwork are restricted to system apps and DPCs
+    // https://developer.android.com/preview/privacy/camera-connectivity#wifi-network-config-restrictions
+    if (wifiConfiguration.networkId == -1) {
+      // new wifi configuration, add it and then save it.
+      networkId = wifiManager.addNetwork(wifiConfiguration);
+    } else {
+      // existing wifi configuration, update it and then save it.
+      networkId = wifiManager.updateNetwork(wifiConfiguration);
     }
 
+    if (networkId == -1) {
+      return false;
+    }
+    wifiManager.enableNetwork(networkId, /* disableOthers= */ false);
+    return true;
+  }
 }
diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiModificationFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiModificationFragment.java
index b8d9c498..c3f94c1e 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiModificationFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiModificationFragment.java
@@ -16,12 +16,16 @@
 
 package com.afwsamples.testdpc.policy.wifimanagement;
 
+import static android.net.wifi.WifiEnterpriseConfig.Eap;
+
+import android.Manifest.permission;
 import android.app.AlertDialog;
 import android.app.DialogFragment;
 import android.app.Fragment;
 import android.content.Context;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
+import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -31,14 +35,14 @@
 import android.widget.CheckedTextView;
 import android.widget.ListView;
 import android.widget.TextView;
-
+import androidx.annotation.RequiresApi;
+import com.afwsamples.testdpc.DeviceAdminReceiver;
 import com.afwsamples.testdpc.R;
-
+import com.afwsamples.testdpc.common.PermissionsHelper;
+import com.afwsamples.testdpc.common.Util;
 import java.util.ArrayList;
 import java.util.List;
 
-import static android.net.wifi.WifiEnterpriseConfig.Eap;
-
 /**
  * Fragment for WiFi configuration editing.
  */
@@ -46,7 +50,6 @@ public class WifiModificationFragment extends Fragment
         implements WifiConfigCreationDialog.Listener {
 
     private static final String TAG_WIFI_CONFIG_MODIFICATION = "wifi_config_modification";
-    private TextView mText;
     private ListView mConfigsList;
     private ConfigsAdapter mConfigsAdapter;
     private List<WifiConfiguration> mConfiguredNetworks = new ArrayList<>();
@@ -54,6 +57,8 @@ public class WifiModificationFragment extends Fragment
 
     private void updateConfigsList() {
         mConfiguredNetworks.clear();
+        // WifiManager deprecated APIs including #getConfiguredNetworks are restricted to system apps and DPCs
+        // https://developer.android.com/preview/privacy/camera-connectivity#wifi-network-config-restrictions
         List<WifiConfiguration> configuredNetworks = mWifiManager.getConfiguredNetworks();
         if (configuredNetworks != null) {
             mConfiguredNetworks.addAll(configuredNetworks);
@@ -110,6 +115,7 @@ public void onResume() {
         updateConfigsList();
     }
 
+    @RequiresApi(api = VERSION_CODES.M)
     @Override
     public View onCreateView(LayoutInflater inflater, final ViewGroup container,
             Bundle savedInstanceState) {
@@ -125,6 +131,13 @@ public View onCreateView(LayoutInflater inflater, final ViewGroup container,
         mConfigsAdapter = new ConfigsAdapter();
         mConfigsList.setAdapter(mConfigsAdapter);
         mConfigsList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+
+        if (Util.SDK_INT >= VERSION_CODES.M) {
+            PermissionsHelper
+                .ensureRequiredPermissions(new String[]{permission.ACCESS_FINE_LOCATION},
+                    DeviceAdminReceiver.getComponentName(this.getActivity()), this.getContext());
+        }
+
         updateConfigsList();
 
         Button updateConfigButton = (Button) view.findViewById(R.id.updateSelectedConfig);
diff --git a/app/src/test/java/com/afwsamples/testdpc/common/PermissionsHelperTest.java b/app/src/test/java/com/afwsamples/testdpc/common/PermissionsHelperTest.java
new file mode 100644
index 00000000..039823b5
--- /dev/null
+++ b/app/src/test/java/com/afwsamples/testdpc/common/PermissionsHelperTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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 final Context context = RuntimeEnvironment.application;
+  private final DevicePolicyManager devicePolicyManager =
+      (DevicePolicyManager) ApplicationProvider.getApplicationContext()
+          .getSystemService(Context.DEVICE_POLICY_SERVICE);
+  private final ComponentName testDpcAdmin =
+      new ComponentName("com.afwsamples.testdpc", "TestCls");
+  private final ComponentName nonTestDpcAdmin = new ComponentName("TestPkg", "TestCls");
+
+  // 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}, nonTestDpcAdmin, context);
+
+    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(devicePolicyManager).setProfileOwner(testDpcAdmin);
+
+    boolean requiredPermissionsGranted = PermissionsHelper
+        .ensureRequiredPermissions(new String[]{DANGEROUS_PERMISSION}, testDpcAdmin, context);
+
+    assertTrue(requiredPermissionsGranted);
+    assertThat(devicePolicyManager
+        .getPermissionGrantState(testDpcAdmin, context.getPackageName(), DANGEROUS_PERMISSION))
+        .isEqualTo(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+  }
+
+  @Test
+  public void ensureRequiredPermissions_ifPermissionIsDangerousAndDpcIsDeviceOwner_shouldReturnTrueAndSetPermissionGrantState() {
+    addPermissionInfo(DANGEROUS_PERMISSION, PermissionInfo.PROTECTION_DANGEROUS);
+    shadowOf(devicePolicyManager).setDeviceOwner(testDpcAdmin);
+
+    boolean requiredPermissionsGranted = PermissionsHelper
+        .ensureRequiredPermissions(new String[]{DANGEROUS_PERMISSION}, testDpcAdmin, context);
+
+    assertTrue(requiredPermissionsGranted);
+    assertThat(devicePolicyManager
+        .getPermissionGrantState(testDpcAdmin, context.getPackageName(), DANGEROUS_PERMISSION))
+        .isEqualTo(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+  }
+
+  @Test
+  public void ensureRequiredPermissions_ifPermissionIsDangerousAndPermissionGrantStateIsAlreadySet_shouldReturnTrue() {
+    addPermissionInfo(DANGEROUS_PERMISSION, PermissionInfo.PROTECTION_DANGEROUS);
+    shadowOf(devicePolicyManager).setProfileOwner(testDpcAdmin);
+    devicePolicyManager.setPermissionGrantState(
+        testDpcAdmin,
+        context.getPackageName(),
+        DANGEROUS_PERMISSION,
+        DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+
+    boolean requiredPermissionsGranted = PermissionsHelper
+        .ensureRequiredPermissions(new String[]{DANGEROUS_PERMISSION}, testDpcAdmin, context);
+
+    assertTrue(requiredPermissionsGranted);
+    assertThat(devicePolicyManager
+        .getPermissionGrantState(testDpcAdmin, context.getPackageName(), DANGEROUS_PERMISSION))
+        .isEqualTo(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+  }
+
+  @Test
+  public void ensureRequiredPermissions_ifPermissionIsDangerousAndDpcIsNotOwner_shouldReturnFalse() {
+    addPermissionInfo(DANGEROUS_PERMISSION, PermissionInfo.PROTECTION_DANGEROUS);
+    shadowOf(devicePolicyManager).setProfileOwner(nonTestDpcAdmin);
+
+    boolean requiredPermissionsGranted = PermissionsHelper
+        .ensureRequiredPermissions(new String[]{DANGEROUS_PERMISSION}, nonTestDpcAdmin, context);
+
+    assertFalse(requiredPermissionsGranted);
+  }
+
+  @Test
+  public void ensureRequiredPermissions_ifPermissionInfoNotFound_shouldReturnTrueAndLogError() {
+    shadowOf(devicePolicyManager).setProfileOwner(testDpcAdmin);
+
+    boolean requiredPermissionsGranted = PermissionsHelper
+        .ensureRequiredPermissions(new String[]{MISSING_INFO_PERMISSION}, testDpcAdmin, context);
+
+    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(devicePolicyManager).setProfileOwner(testDpcAdmin);
+
+    boolean requiredPermissionsGranted = PermissionsHelper
+        .ensureRequiredPermissions(new String[]{NORMAL_PERMISSION}, testDpcAdmin, context);
+
+    assertTrue(requiredPermissionsGranted);
+    assertThat(devicePolicyManager
+        .getPermissionGrantState(testDpcAdmin, context.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(devicePolicyManager).setProfileOwner(testDpcAdmin);
+
+    boolean requiredPermissionsGranted = PermissionsHelper
+        .ensureRequiredPermissions(new String[]{NORMAL_PERMISSION, DANGEROUS_PERMISSION},
+            testDpcAdmin, context);
+
+    assertTrue(requiredPermissionsGranted);
+    assertThat(devicePolicyManager
+        .getPermissionGrantState(testDpcAdmin, context.getPackageName(), NORMAL_PERMISSION))
+        .isEqualTo(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+    assertThat(devicePolicyManager
+        .getPermissionGrantState(testDpcAdmin, context.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(devicePolicyManager).setProfileOwner(nonTestDpcAdmin);
+
+    boolean requiredPermissionsGranted = PermissionsHelper
+        .ensureRequiredPermissions(new String[]{NORMAL_PERMISSION, DANGEROUS_PERMISSION},
+            nonTestDpcAdmin, context);
+
+    assertFalse(requiredPermissionsGranted);
+  }
+
+  private void addPermissionInfo(String permission, int protectionLevel) {
+    PermissionInfo permissionInfo = new PermissionInfo();
+    permissionInfo.name = permission;
+    permissionInfo.protectionLevel = protectionLevel;
+    shadowOf(context.getPackageManager()).addPermissionInfo(permissionInfo);
+  }
+
+}
\ No newline at end of file

From 449e4658e9841e80106b0b539669e345009aef37 Mon Sep 17 00:00:00 2001
From: kholoud mohamed <kholoudm@google.com>
Date: Tue, 18 Jun 2019 12:08:37 +0100
Subject: [PATCH 09/73] change disambig screen ui

Fixes: 123523949
Test: ./gradlew test
Change-Id: If826a8244f201073f9e2d0a579015fa6d538e8c9
---
 app/src/main/AndroidManifest.xml              |   3 +-
 .../testdpc/provision/DpcLoginActivity.java   |  65 +++----
 .../res/drawable/managed_device_setup.xml     |  47 +++++
 .../res/drawable/managed_profile_setup.xml    |  42 ++++
 .../main/res/layout/activity_dpc_login.xml    | 179 +++++++++++++++---
 app/src/main/res/values/colors.xml            |   1 +
 app/src/main/res/values/dimens.xml            |  14 ++
 app/src/main/res/values/strings.xml           |  43 +++++
 app/src/main/res/values/styles.xml            |  23 +++
 .../provision/DpcLoginActivityTest.java       |  15 +-
 10 files changed, 358 insertions(+), 74 deletions(-)
 create mode 100644 app/src/main/res/drawable/managed_device_setup.xml
 create mode 100644 app/src/main/res/drawable/managed_profile_setup.xml

diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0f99cfd5..0aed44e1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -100,7 +100,8 @@
 
         <activity android:name=".provision.DpcLoginActivity"
                 android:exported="true"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+                android:permission="android.permission.BIND_DEVICE_ADMIN"
+                android:theme="@style/DpcLoginTheme">
             <intent-filter>
                 <action android:name="android.app.action.GET_PROVISIONING_MODE" />
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/app/src/main/java/com/afwsamples/testdpc/provision/DpcLoginActivity.java b/app/src/main/java/com/afwsamples/testdpc/provision/DpcLoginActivity.java
index 60f02f73..ed408bb9 100644
--- a/app/src/main/java/com/afwsamples/testdpc/provision/DpcLoginActivity.java
+++ b/app/src/main/java/com/afwsamples/testdpc/provision/DpcLoginActivity.java
@@ -24,9 +24,8 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
-import android.widget.RadioGroup;
+import android.widget.LinearLayout;
 import com.afwsamples.testdpc.R;
-import com.android.setupwizardlib.GlifLayout;
 
 /**
  * Activity that gets launched by the
@@ -34,39 +33,37 @@
  */
 public class DpcLoginActivity extends Activity {
 
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        setContentView(R.layout.activity_dpc_login);
-        GlifLayout layout = findViewById(R.id.dpc_login);
-        layout.findViewById(R.id.suw_navbar_next).setOnClickListener(this::onNavigateNext);
-    }
+  @Override
+  public void onCreate(Bundle icicle) {
+    super.onCreate(icicle);
+    setContentView(R.layout.activity_dpc_login);
+    final LinearLayout layout = findViewById(R.id.dpc_login);
+    layout.findViewById(R.id.do_selection_button).setOnClickListener(this::onDoButtonClick);
+    layout.findViewById(R.id.po_selection_button).setOnClickListener(this::onPoButtonClick);
+  }
 
-    @Override
-    public void onBackPressed() {
-        setResult(RESULT_CANCELED);
-        super.onBackPressed();
-    }
+  @Override
+  public void onBackPressed() {
+    setResult(RESULT_CANCELED);
+    super.onBackPressed();
+  }
 
-    private void onNavigateNext(View nextButton) {
-        final Intent intent = new Intent();
-        RadioGroup dpcLoginOptions = findViewById(R.id.dpc_login_options);
-        switch (dpcLoginOptions.getCheckedRadioButtonId()) {
-            case R.id.dpc_login_do:
-                intent.putExtra(EXTRA_PROVISIONING_MODE, PROVISIONING_MODE_FULLY_MANAGED_DEVICE);
-                finishWithIntent(intent);
-                return;
-            case R.id.dpc_login_po:
-                intent.putExtra(EXTRA_PROVISIONING_MODE, PROVISIONING_MODE_MANAGED_PROFILE);
-                finishWithIntent(intent);
-                return;
-            default:
-                finish();
-        }
-    }
+  private void onDoButtonClick(View button) {
+    final Intent intent = new Intent();
+    intent.putExtra(EXTRA_PROVISIONING_MODE, PROVISIONING_MODE_FULLY_MANAGED_DEVICE);
+    finishWithIntent(intent);
+    return;
+  }
 
-    private void finishWithIntent(Intent intent) {
-        setResult(RESULT_OK, intent);
-        finish();
-    }
+  private void onPoButtonClick(View button) {
+    final Intent intent = new Intent();
+    intent.putExtra(EXTRA_PROVISIONING_MODE, PROVISIONING_MODE_MANAGED_PROFILE);
+    finishWithIntent(intent);
+    return;
+  }
+
+  private void finishWithIntent(Intent intent) {
+    setResult(RESULT_OK, intent);
+    finish();
+  }
 }
diff --git a/app/src/main/res/drawable/managed_device_setup.xml b/app/src/main/res/drawable/managed_device_setup.xml
new file mode 100644
index 00000000..1e8e1a08
--- /dev/null
+++ b/app/src/main/res/drawable/managed_device_setup.xml
@@ -0,0 +1,47 @@
+<vector xmlns:tools="http://schemas.android.com/tools"
+    android:height="153.1dp" android:viewportHeight="153.1"
+    android:viewportWidth="142.09" android:width="142.09dp" xmlns:android="http://schemas.android.com/apk/res/android"
+    tools:ignore="NewApi">
+  <path android:fillColor="#f1f3f4"
+      android:pathData="M7.68,1L61.08,1A6.68,6.68 0,0 1,67.76 7.68L67.76,133.17A6.68,6.68 0,0 1,61.08 139.85L7.68,139.85A6.68,6.68 0,0 1,1 133.17L1,7.68A6.68,6.68 0,0 1,7.68 1z"
+      android:strokeColor="@color/managed_provisioning_color" android:strokeWidth="2"/>
+  <path android:fillColor="#fff" android:pathData="M8.37,4.46L60.38,4.46A4.01,4.01 0,0 1,64.39 8.47L64.39,132.39A4.01,4.01 0,0 1,60.38 136.4L8.37,136.4A4.01,4.01 0,0 1,4.36 132.39L4.36,8.47A4.01,4.01 0,0 1,8.37 4.46z"/>
+  <path android:fillColor="@color/managed_provisioning_color" android:pathData="M68.42,34.44h0a1.29,1.29 0,0 1,1.29 1.29v6.11a1.29,1.29 0,0 1,-1.29 1.29h0a0,0 0,0 1,0 0V34.44A0,0 0,0 1,68.42 34.44Z"/>
+  <path android:fillColor="@color/managed_provisioning_color" android:pathData="M68.44,53.67h0v8.68h1.34V55A1.32,1.32 0,0 0,68.44 53.67Z"/>
+  <path android:fillColor="@color/managed_provisioning_color" android:pathData="M68.44,71h0V62.35h1.34v7.37A1.32,1.32 0,0 1,68.44 71Z"/>
+  <path android:fillColor="#fff" android:pathData="M8.37,4.46L60.38,4.46A4.01,4.01 0,0 1,64.39 8.47L64.39,132.39A4.01,4.01 0,0 1,60.38 136.4L8.37,136.4A4.01,4.01 0,0 1,4.36 132.39L4.36,8.47A4.01,4.01 0,0 1,8.37 4.46z"/>
+  <path android:fillColor="@color/managed_provisioning_color" android:pathData="M42.38,64.93h-16a2,2 0,0 0,-2 2v11a2,2 0,0 0,2 2h16a2,2 0,0 0,2 -2v-11A2,2 0,0 0,42.38 64.93Z"/>
+  <path android:fillColor="@color/managed_provisioning_color" android:pathData="M36.38,60.92h-4a2,2 0,0 0,-2 2v2h2v-2h4v2h2v-2A2,2 0,0 0,36.38 60.92Z"/>
+  <path android:fillColor="#fff" android:pathData="M34.38,71.93m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
+  <path android:fillColor="#00000000"
+      android:pathData="M110.45,121.02l21.31,0l0,27.8"
+      android:strokeColor="@color/managed_provisioning_color" android:strokeWidth="2"/>
+  <path android:fillColor="#00000000"
+      android:pathData="M124.77,127.66L124.77,145.5"
+      android:strokeColor="@color/managed_provisioning_color" android:strokeWidth="2"/>
+  <path android:fillColor="#00000000"
+      android:pathData="M118.78,127.66L118.78,145.5"
+      android:strokeColor="@color/managed_provisioning_color" android:strokeWidth="2"/>
+  <path android:fillColor="#00000000"
+      android:pathData="M112.79,127.66L112.79,145.5"
+      android:strokeColor="@color/managed_provisioning_color" android:strokeWidth="2"/>
+  <path android:fillColor="@color/managed_provisioning_color" android:pathData="M105.94,149.3l-29.18,-0l-0,-40.93l29.18,-0z"/>
+  <path android:fillColor="#fff" android:pathData="M95.14,149.92l-7.58,-0l-0,-7.23l7.58,-0z"/>
+  <path android:fillColor="#fff" android:pathData="M101.7,149.92l-4.19,-0l-0,-7.23l4.19,-0z"/>
+  <path android:fillColor="#fff" android:pathData="M84.9,149.92l-4.19,-0l-0,-7.23l4.19,-0z"/>
+  <path android:fillColor="#fff" android:pathData="M84.9,124.23l-4.19,-0l-0,-2.23l4.19,-0z"/>
+  <path android:fillColor="#fff" android:pathData="M101.7,124.23l-4.19,-0l-0,-2.23l4.19,-0z"/>
+  <path android:fillColor="#fff" android:pathData="M95.14,124.23l-7.58,-0l-0,-2.23l7.58,-0z"/>
+  <path android:fillColor="#fff" android:pathData="M84.9,131.05l-4.19,-0l-0,-2.23l4.19,-0z"/>
+  <path android:fillColor="#fff" android:pathData="M101.7,131.05l-4.19,-0l-0,-2.23l4.19,-0z"/>
+  <path android:fillColor="#fff" android:pathData="M95.14,131.05l-7.58,-0l-0,-2.23l7.58,-0z"/>
+  <path android:fillColor="#fff" android:pathData="M84.9,137.86l-4.19,-0l-0,-2.23l4.19,-0z"/>
+  <path android:fillColor="#fff" android:pathData="M101.7,137.86l-4.19,-0l-0,-2.23l4.19,-0z"/>
+  <path android:fillColor="#fff" android:pathData="M95.14,137.86l-7.58,-0l-0,-2.23l7.58,-0z"/>
+  <path android:fillColor="#fff" android:pathData="M84.9,117.41l-4.19,-0l-0,-2.23l4.19,-0z"/>
+  <path android:fillColor="#fff" android:pathData="M101.7,117.41l-4.19,-0l-0,-2.23l4.19,-0z"/>
+  <path android:fillColor="#fff" android:pathData="M95.14,117.41l-7.58,-0l-0,-2.23l7.58,-0z"/>
+  <path android:fillColor="#00000000"
+      android:pathData="M142.09,152.1L1,152.1"
+      android:strokeColor="#dadce0" android:strokeWidth="2"/>
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/managed_profile_setup.xml b/app/src/main/res/drawable/managed_profile_setup.xml
new file mode 100644
index 00000000..96189073
--- /dev/null
+++ b/app/src/main/res/drawable/managed_profile_setup.xml
@@ -0,0 +1,42 @@
+<vector xmlns:tools="http://schemas.android.com/tools"
+    android:height="153.1dp" android:viewportHeight="153.1"
+    android:viewportWidth="142.09" android:width="142.09dp" xmlns:android="http://schemas.android.com/apk/res/android"
+    tools:ignore="NewApi">
+  <path android:fillColor="#00000000"
+      android:pathData="M142.09,152.1L1,152.1"
+      android:strokeColor="#dadce0" android:strokeWidth="2"/>
+  <path android:fillColor="#34a853" android:pathData="M120.42,122.38l2,-0.46a5.85,5.85 0,0 1,5.2 1.3l3.33,2.91 -3.69,6.6 -0.5,-0.9 1.39,-4.85 -2.79,-2.15 2.69,8.9 -1.79,17.49h-1.79l0.47,-16.87 -1.08,0 -1,8.94 -2.72,7.89h-1.39l1.41,-8.49 0.1,-8.4 -2.65,-6.55A4,4 0,0 1,120.42 122.38Z"/>
+  <path android:fillColor="#34a853" android:pathData="M118.46,123.67v-4.24a1,1 0,0 1,1 -1h0a1,1 0,0 1,0.88 0.59l1.42,3.41Z"/>
+  <path android:fillColor="#34a853" android:pathData="M116.15,125.1l-3.66,-3.68v1.27l3.5,5.94 5.65,-1.85c0.22,-0.58 -2.76,-3.21 -3.28,-3.56Z"/>
+  <path android:fillColor="#34a853" android:pathData="M113.24,122.92L111.93,122.92A0.23,0.23 0,0 1,111.7 122.69L111.7,119.46A0.23,0.23 0,0 1,111.93 119.23L113.24,119.23A0.23,0.23 0,0 1,113.47 119.46L113.47,122.69A0.23,0.23 0,0 1,113.24 122.92z"/>
+  <path android:fillColor="#34a853" android:pathData="M113.45,121.78l-1,-1.52 -0.64,0.18a1.11,1.11 0,0 0,-0.23 2,2.15 2.15,0 0,0 1.14,0.38A0.82,0.82 0,0 0,113.45 121.78Z"/>
+  <path android:fillColor="#00000000"
+      android:pathData="M99.21,139.52 L85,125.26a25.81,25.81 0,0 1,0 -36.4L99.21,74.6l14.26,14.26"
+      android:strokeColor="#dadce0" android:strokeWidth="2"/>
+  <path android:fillColor="#f1f3f4"
+      android:pathData="M99.7,96.85L99.7,151.22"
+      android:strokeColor="#dadce0" android:strokeWidth="2"/>
+  <path android:fillColor="#00000000"
+      android:pathData="M99.7,120.26L88.16,108.71"
+      android:strokeColor="#dadce0" android:strokeWidth="2"/>
+  <path android:fillColor="#00000000"
+      android:pathData="M99.7,108.71L108.52,99.89"
+      android:strokeColor="#dadce0" android:strokeWidth="2"/>
+  <path android:fillColor="#00000000"
+      android:pathData="M99.7,139.26L108.52,130.44"
+      android:strokeColor="#dadce0" android:strokeWidth="2"/>
+  <path android:fillColor="#f1f3f4"
+      android:pathData="M7.68,1L61.08,1A6.68,6.68 0,0 1,67.76 7.68L67.76,133.17A6.68,6.68 0,0 1,61.08 139.85L7.68,139.85A6.68,6.68 0,0 1,1 133.17L1,7.68A6.68,6.68 0,0 1,7.68 1z"
+      android:strokeColor="#dadce0" android:strokeWidth="2"/>
+  <path android:fillColor="#fff" android:pathData="M8.37,4.46L60.38,4.46A4.01,4.01 0,0 1,64.39 8.47L64.39,132.39A4.01,4.01 0,0 1,60.38 136.4L8.37,136.4A4.01,4.01 0,0 1,4.36 132.39L4.36,8.47A4.01,4.01 0,0 1,8.37 4.46z"/>
+  <path android:fillColor="#dadce0" android:pathData="M68.42,34.44h0a1.29,1.29 0,0 1,1.29 1.29v6.11a1.29,1.29 0,0 1,-1.29 1.29h0a0,0 0,0 1,0 0V34.44A0,0 0,0 1,68.42 34.44Z"/>
+  <path android:fillColor="#dadce0" android:pathData="M68.44,53.67h0v8.68h1.34V55A1.32,1.32 0,0 0,68.44 53.67Z"/>
+  <path android:fillColor="#dadce0" android:pathData="M68.44,71h0V62.35h1.34v7.37A1.32,1.32 0,0 1,68.44 71Z"/>
+  <path android:fillColor="@color/managed_provisioning_color" android:pathData="M60.39,6.46a2,2 0,0 1,2 2V66.22a2,2 0,0 1,-2 2h-52a2,2 0,0 1,-2 -2V8.46a2,2 0,0 1,2 -2h52m0,-2h-52a4,4 0,0 0,-4 4V66.22a4,4 0,0 0,4 4h52a4,4 0,0 0,4 -4V8.46a4,4 0,0 0,-4 -4Z"/>
+  <path android:fillColor="@color/managed_provisioning_color" android:pathData="M42.38,31.84h-16a2,2 0,0 0,-2 2v11a2,2 0,0 0,2 2h16a2,2 0,0 0,2 -2v-11A2,2 0,0 0,42.38 31.84Z"/>
+  <path android:fillColor="@color/managed_provisioning_color" android:pathData="M36.38,27.84h-4a2,2 0,0 0,-2 2v2h2v-2h4v2h2v-2A2,2 0,0 0,36.38 27.84Z"/>
+  <path android:fillColor="#fff" android:pathData="M34.38,38.84m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
+  <path android:fillColor="#34a853" android:pathData="M34.38,93.02L34.38,93.02A10,10 0,0 1,44.38 103.02L44.38,103.02A10,10 0,0 1,34.38 113.02L34.38,113.02A10,10 0,0 1,24.38 103.02L24.38,103.02A10,10 0,0 1,34.38 93.02z"/>
+  <path android:fillColor="#fff" android:pathData="M34.38,100.63m-2.72,0a2.72,2.72 0,1 1,5.44 0a2.72,2.72 0,1 1,-5.44 0"/>
+  <path android:fillColor="#fff" android:pathData="M39.83,106.59c-1,-1.54 -3.09,-2.18 -5.45,-2.18 -2.82,0 -5.2,0.92 -5.93,3.17a7.42,7.42 0,0 0,5.93 3h0a7.42,7.42 0,0 0,5.93 -3A3.92,3.92 0,0 0,39.83 106.59Z"/>
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_dpc_login.xml b/app/src/main/res/layout/activity_dpc_login.xml
index 0e378cca..eb386dba 100644
--- a/app/src/main/res/layout/activity_dpc_login.xml
+++ b/app/src/main/res/layout/activity_dpc_login.xml
@@ -14,48 +14,171 @@
  See the License for the specific language governing permissions and
  limitations under the License.
 -->
-<com.android.setupwizardlib.GlifLayout
+
+<ScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/dpc_login"
-    android:layout_width="match_parent"
+    xmlns:custom="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_height="match_parent"
-    android:icon="@drawable/ic_enterprise_blue"
-    app:suwFooter="@layout/next_footer"
-    app:suwHeaderText="@string/dpc_login_title">
-
+    android:layout_width="match_parent">
   <LinearLayout
-      style="@style/SuwItemContainer"
+      android:id="@+id/dpc_login"
       android:layout_width="match_parent"
-      android:layout_height="match_parent"
+      android:layout_height="wrap_content"
       android:orientation="vertical">
 
     <TextView
-        style="@style/SuwItemTitle"
+        android:id="@+id/selection_title"
+        style="@style/SelectionTitle"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:text="@string/dpc_login_explanation" />
+        android:layout_marginTop="@dimen/selection_title_top_margin"
+        android:gravity="center_horizontal"
+        android:text="@string/selector_title"
+        android:textAlignment="center"
+        android:textAppearance="@style/TextAppearance.AppCompat.Large"
+        android:textSize="@dimen/selection_title_text_size"
+        custom:layout_constraintEnd_toEndOf="parent"
+        custom:layout_constraintStart_toStartOf="parent"/>
 
-    <RadioGroup
-        android:id="@+id/dpc_login_options"
-        android:layout_width="match_parent"
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:paddingTop="@dimen/content_padding_between_text">
+        android:layout_marginTop="@dimen/selection_content_padding"
+        android:layout_marginBottom="@dimen/selection_content_padding"
+        android:layout_marginLeft="@dimen/constraint_layout_margin"
+        android:layout_marginRight="@dimen/constraint_layout_margin">
 
-      <RadioButton
-          android:id="@+id/dpc_login_do"
+      <androidx.constraintlayout.widget.Guideline
+          android:id="@+id/guideline_vertical_left"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
-          android:checked="true"
-          android:text="@string/dpc_login_do" />
+          android:paddingStart="@dimen/selection_guideline_padding"
+          android:paddingEnd="@dimen/selection_guideline_padding"
+          android:orientation="vertical"
+          custom:layout_constraintGuide_percent="0.368"/>
 
-      <RadioButton
-          android:id="@+id/dpc_login_po"
-          android:layout_width="wrap_content"
+      <ImageView
+          android:id="@+id/do_selection_image"
+          android:layout_width="0dp"
+          android:layout_height="0dp"
+          android:scaleType="fitCenter"
+          android:src="@drawable/managed_device_setup"
+          custom:layout_constraintBottom_toTopOf="@+id/do_selection_button"
+          custom:layout_constraintEnd_toStartOf="@+id/guideline_vertical_left"
+          custom:layout_constraintStart_toStartOf="parent"
+          custom:layout_constraintTop_toTopOf="@+id/do_selection_detailed_title"
+          tools:ignore="ContentDescription"/>
+
+      <TextView
+          android:id="@+id/do_selection_detailed_title"
+          style="@style/SelectionTitle"
+          android:layout_width="0dp"
           android:layout_height="wrap_content"
-          android:text="@string/dpc_login_po" />
-    </RadioGroup>
-  </LinearLayout>
+          android:text="@string/do_selection_subtitle"
+          custom:layout_constraintBottom_toTopOf="@+id/do_selection_detailed_text"
+          custom:layout_constraintEnd_toEndOf="parent"
+          custom:layout_constraintHorizontal_bias="0.0"
+          custom:layout_constraintStart_toEndOf="@+id/guideline_vertical_left"/>
+
+      <TextView
+          android:id="@+id/do_selection_detailed_text"
+          style="@style/SelectionDesc"
+          android:layout_width="0dp"
+          android:layout_height="wrap_content"
+          android:text="@string/do_selection_detailed_text"
+          custom:layout_constraintBottom_toTopOf="@+id/do_selection_button"
+          custom:layout_constraintEnd_toEndOf="parent"
+          custom:layout_constraintStart_toStartOf="@+id/guideline_vertical_left"/>
+
+      <Button
+          android:id="@+id/do_selection_button"
+          style="@style/SelectionButton"
+          android:layout_width="0dp"
+          android:layout_height="wrap_content"
+          android:text="@string/do_selection_button_text"
+          custom:layout_constraintBottom_toTopOf="@+id/divider"
+          custom:layout_constraintEnd_toEndOf="parent"
+          custom:layout_constraintStart_toStartOf="@+id/guideline_vertical_left"/>
+
+      <RelativeLayout
+          android:id="@+id/divider"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:paddingTop="@dimen/selection_middle_padding"
+          android:paddingBottom="@dimen/selection_middle_padding"
+          custom:layout_constraintBottom_toTopOf="parent"
+          custom:layout_constraintEnd_toEndOf="parent"
+          custom:layout_constraintStart_toStartOf="parent"
+          custom:layout_constraintTop_toBottomOf="parent">
 
-</com.android.setupwizardlib.GlifLayout>
+        <TextView
+            android:id="@+id/breakText"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/selection_divider_margin_horizontal"
+            android:layout_marginRight="@dimen/selection_divider_margin_horizontal"
+            android:layout_centerInParent="true"
+            android:text="@string/selection_divider_text"/>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/text_divider_height"
+            android:layout_centerVertical="true"
+            android:layout_toStartOf="@id/breakText"
+            android:background="?android:attr/listDivider"/>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/text_divider_height"
+            android:layout_centerVertical="true"
+            android:layout_toEndOf="@id/breakText"
+            android:background="?android:attr/listDivider"/>
+
+      </RelativeLayout>
+
+      <ImageView
+          android:id="@+id/po_selection_image"
+          android:layout_width="0dp"
+          android:layout_height="0dp"
+          android:scaleType="fitCenter"
+          android:src="@drawable/managed_profile_setup"
+          custom:layout_constraintBottom_toTopOf="@+id/po_selection_button"
+          custom:layout_constraintEnd_toStartOf="@+id/guideline_vertical_left"
+          custom:layout_constraintStart_toStartOf="parent"
+          custom:layout_constraintTop_toTopOf="@+id/po_selection_detailed_title"
+          tools:ignore="ContentDescription"/>
+
+      <TextView
+          android:id="@+id/po_selection_detailed_title"
+          style="@style/SelectionTitle"
+          android:layout_width="0dp"
+          android:layout_height="wrap_content"
+          android:text="@string/po_selection_subtitle"
+          custom:layout_constraintEnd_toEndOf="parent"
+          custom:layout_constraintStart_toStartOf="@+id/guideline_vertical_left"
+          custom:layout_constraintTop_toBottomOf="@+id/divider"/>
+
+
+      <TextView
+          android:id="@+id/po_selection_detailed_text"
+          style="@style/SelectionDesc"
+          android:layout_width="0dp"
+          android:layout_height="wrap_content"
+          android:text="@string/po_selection_detailed_text"
+          custom:layout_constraintEnd_toEndOf="parent"
+          custom:layout_constraintStart_toStartOf="@+id/guideline_vertical_left"
+          custom:layout_constraintTop_toBottomOf="@+id/po_selection_detailed_title"/>
+
+      <Button
+          android:id="@+id/po_selection_button"
+          style="@style/SelectionButton"
+          android:layout_width="0dp"
+          android:layout_height="wrap_content"
+          android:text="@string/po_selection_button_text"
+          custom:layout_constraintEnd_toEndOf="parent"
+          custom:layout_constraintStart_toStartOf="@+id/guideline_vertical_left"
+          custom:layout_constraintTop_toBottomOf="@+id/po_selection_detailed_text"/>
+    </androidx.constraintlayout.widget.ConstraintLayout>
+  </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 610bca7a..da84e1dd 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -28,4 +28,5 @@
     <color name="primary_dark">#ff254FAE</color>
     <color name="primary">#ff3367d6</color>
     <color name="accent">#ff3367d6</color>
+    <color name="managed_provisioning_color">#4285f4</color>
 </resources>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index c0d85eb9..53cf7a24 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -55,4 +55,18 @@
 
     <!-- Dimens used for key value pair dialog -->
     <dimen name="key_value_pair_dialog_padding">8dp</dimen>
+
+    <!-- Dimens used for dpc login screen -->
+    <dimen name="selection_divider_margin_horizontal">10dp</dimen>
+    <dimen name="selection_detailed_text_padding_top">8dp</dimen>
+    <dimen name="selection_detailed_text_padding_bottom">32dp</dimen>
+    <dimen name="selection_guideline_padding">8dp</dimen>
+    <dimen name="selection_middle_padding">16dp</dimen>
+    <dimen name="selection_content_padding">32dp</dimen>
+    <dimen name="selection_detailed_title_size">16sp</dimen>
+    <dimen name="selection_detailed_text_size">14sp</dimen>
+    <dimen name="selection_title_top_margin">50dp</dimen>
+    <dimen name="selection_title_text_size">24sp</dimen>
+    <dimen name="constraint_layout_margin">10dp</dimen>
+    <dimen name="text_divider_height">1dp</dimen>
 </resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f587926c..33412e24 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1096,4 +1096,47 @@
     <string name="vpn_exempted_packages">Exempted packages</string>
     <string name="vpn_lockdown">Lockdown</string>
 
+    <!-- This string is the title of a screen asking user how to set device up.
+
+      This screen will be shown if the user sets device up with a managed account, and we believe that the user may want to set up a managed profile rather than a managed device.[CHAR LIMIT=100] -->
+    <string name="selector_title">How will you use this phone?</string>
+    <!-- This string is the subtitle of a screen for setting up a managed device.
+
+          This screen will be shown if the user sets device up with a managed account, and we believe that the user may want to set up a managed profile rather than a managed device.[CHAR LIMIT=100] -->
+    <string name="do_selection_subtitle">Work</string>
+    <!-- This string is the detailed text of a screen for setting up a managed device, giving user more info on how their device will behave in this mode.
+
+        Text bullets that are part of a list. They describe one of the options a user can choose when setting up their new device, which would be to have their organization manage their entire device. That means that the data on the device won't be private (it will be visible to their IT admin), and it can be erased at any time.[CHAR LIMIT=200] -->
+    <string name="do_selection_detailed_text">
+        <li>This phone will be managed by your IT admin</li>\n\n
+        <li>Your data won\'t be private and can be erased at any time</li>
+    </string>
+    <!-- This string is for the button for setting up a managed device.
+
+        This screen will be shown if the user sets device up with a managed account, and we believe that the user may want to set up a managed profile rather than a managed device.[CHAR LIMIT=50] -->
+    <string name="do_selection_button_text">Use for work only</string>
+
+
+    <!-- This string is the subtitle of a screen for setting up a managed profile.
+
+          This screen will be shown if the user sets device up with a managed account, and we believe that the user may want to set up a managed profile rather than a managed device.[CHAR LIMIT=100] -->
+    <string name="po_selection_subtitle">Work and Personal</string>
+    <!-- This string is the detailed text of a screen for setting up a managed profile, giving user more info on how their device will behave in this mode.
+
+        This screen will be shown if the user sets device up with a managed account, and we believe that the user may want to set up a managed profile rather than a managed device.[CHAR LIMIT=200] -->
+    <string name="po_selection_detailed_text">
+        <li>Your work data will be kept in a separate profile</li>\n\n
+        <li>Your personal data stays private</li>
+    </string>
+    <!-- This string is for the button for setting up a managed profile.
+
+        This screen will be shown if the user sets device up with a managed account, and we believe that the user may want to set up a managed profile rather than a managed device.[CHAR LIMIT=50] -->
+    <string name="po_selection_button_text">Use for work &amp; personal</string>
+
+
+    <!-- This string is the text shown to divide the two options shown on the mode selection screen.
+
+        This screen will be shown if the user sets device up with a managed account, and we believe that the user may want to set up a managed profile rather than a managed device.[CHAR LIMIT=100] -->
+    <string name="selection_divider_text">or</string>
+
 </resources>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index f449125c..b5d40b3d 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -25,6 +25,11 @@
         <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
     </style>
 
+    <style name="DpcLoginTheme" parent="AppTheme">
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
     <style name="contentContainer">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">match_parent</item>
@@ -87,4 +92,22 @@
         <item name="android:layout_marginTop">8dp</item>
         <item name="android:textSize">16sp</item>
     </style>
+
+    <style name="SelectionTitle">
+        <item name="android:textSize">@dimen/selection_detailed_title_size</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="SelectionDesc">
+        <item name="android:textSize">@dimen/selection_detailed_text_size</item>
+        <item name="android:textColor">@color/light_gray</item>
+        <item name="android:paddingTop">@dimen/selection_detailed_text_padding_top</item>
+        <item name="android:paddingBottom">@dimen/selection_detailed_text_padding_bottom</item>
+    </style>
+
+    <style name="SelectionButton" parent="SuwGlifButton.Primary">
+        <item name="android:textSize">@dimen/selection_detailed_title_size</item>
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textColor">@android:color/white</item>
+    </style>
 </resources>
diff --git a/app/src/test/java/com/afwsamples/testdpc/provision/DpcLoginActivityTest.java b/app/src/test/java/com/afwsamples/testdpc/provision/DpcLoginActivityTest.java
index c11e24df..653b9d9a 100644
--- a/app/src/test/java/com/afwsamples/testdpc/provision/DpcLoginActivityTest.java
+++ b/app/src/test/java/com/afwsamples/testdpc/provision/DpcLoginActivityTest.java
@@ -25,7 +25,6 @@
 import static org.robolectric.Shadows.shadowOf;
 
 import android.os.Build.VERSION_CODES;
-import android.widget.RadioButton;
 import androidx.test.core.app.ActivityScenario;
 import com.afwsamples.testdpc.R;
 import org.junit.Test;
@@ -39,12 +38,9 @@
 public class DpcLoginActivityTest {
 
   @Test
-  public void onNavigateNext_poRadioBoxSelected_shouldFinishWithCorrectIntent() {
+  public void onPoButtonClick_shouldFinishWithCorrectIntent() {
     ActivityScenario.launch(DpcLoginActivity.class).onActivity(activity -> {
-      RadioButton dpcLoginPo = activity.findViewById(R.id.dpc_login_po);
-      dpcLoginPo.setChecked(true);
-
-      activity.findViewById(R.id.suw_navbar_next).performClick();
+      activity.findViewById(R.id.po_selection_button).performClick();
 
       assertThat(shadowOf(activity).getResultCode()).isEqualTo(RESULT_OK);
       assertThat(shadowOf(activity).getResultIntent().getIntExtra(EXTRA_PROVISIONING_MODE, -1))
@@ -53,12 +49,9 @@ public void onNavigateNext_poRadioBoxSelected_shouldFinishWithCorrectIntent() {
   }
 
   @Test
-  public void onNavigateNext_doRadioBoxSelected_shouldFinishWithCorrectIntent() {
+  public void onDoButtonClick_shouldFinishWithCorrectIntent() {
     ActivityScenario.launch(DpcLoginActivity.class).onActivity(activity -> {
-      RadioButton dpcLoginDo = activity.findViewById(R.id.dpc_login_do);
-      dpcLoginDo.setChecked(true);
-
-      activity.findViewById(R.id.suw_navbar_next).performClick();
+      activity.findViewById(R.id.do_selection_button).performClick();
 
       assertThat(shadowOf(activity).getResultCode()).isEqualTo(RESULT_OK);
       assertThat(shadowOf(activity).getResultIntent().getIntExtra(EXTRA_PROVISIONING_MODE, -1))

From 7ce521799a89a8675a613d4ae9d50869e8fcde1e Mon Sep 17 00:00:00 2001
From: Jonathan Scott <scottjonathan@google.com>
Date: Mon, 8 Jul 2019 15:16:23 +0100
Subject: [PATCH 10/73] Increase size of touch target.

Test: Manual
Bug: 127914462
Fixes: 130131855
Change-Id: If8539d0ac3bdde2aab4f4db0137f991e89e5b53f
---
 app/src/main/res/layout/activity_add_account.xml | 6 +++---
 app/src/main/res/values/dimens.xml               | 3 +++
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/app/src/main/res/layout/activity_add_account.xml b/app/src/main/res/layout/activity_add_account.xml
index 379a8d14..217e9148 100644
--- a/app/src/main/res/layout/activity_add_account.xml
+++ b/app/src/main/res/layout/activity_add_account.xml
@@ -45,20 +45,20 @@
             <RadioButton
                 android:id="@+id/add_account"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
+                android:layout_height="@dimen/add_account_option_height"
                 android:checked="true"
                 android:text="@string/add_account" />
 
             <RadioButton
                 android:id="@+id/add_account_with_name"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
+                android:layout_height="@dimen/add_account_option_height"
                 android:text="@string/add_account_with_name" />
 
             <RadioButton
                 android:id="@+id/add_account_skip"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content"
+                android:layout_height="@dimen/add_account_option_height"
                 android:text="@string/add_account_skip" />
         </RadioGroup>
     </LinearLayout>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 53cf7a24..061a38c7 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -69,4 +69,7 @@
     <dimen name="selection_title_text_size">24sp</dimen>
     <dimen name="constraint_layout_margin">10dp</dimen>
     <dimen name="text_divider_height">1dp</dimen>
+
+    <!-- Dimens used for add account activity -->
+    <dimen name="add_account_option_height">48dp</dimen>
 </resources>

From 3e4289a6810b48a853dd5f41f4da0f9d57d15a8f Mon Sep 17 00:00:00 2001
From: Frank Alcantara <falcantara@google.com>
Date: Fri, 28 Jun 2019 18:51:33 -0400
Subject: [PATCH 11/73] Renamed manage_apps_restrictions string to
 managed_configurations and renamed ManageAppRestrictionsFragment

Fixes: 121030046
Change-Id: Ie959ab169e1816f20b966586b989fd5431dde64f
---
 .../testdpc/policy/PolicyManagementFragment.java       | 10 +++++-----
 ...ragment.java => ManagedConfigurationsFragment.java} |  8 ++++----
 app/src/main/res/values/strings.xml                    |  2 +-
 app/src/main/res/xml/device_policy_header.xml          |  4 ++--
 4 files changed, 12 insertions(+), 12 deletions(-)
 rename app/src/main/java/com/afwsamples/testdpc/profilepolicy/apprestrictions/{ManageAppRestrictionsFragment.java => ManagedConfigurationsFragment.java} (98%)

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 9036ff53..a0095406 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -125,7 +125,7 @@
 import com.afwsamples.testdpc.profilepolicy.ProfilePolicyManagementFragment;
 import com.afwsamples.testdpc.profilepolicy.addsystemapps.EnableSystemAppsByIntentFragment;
 import com.afwsamples.testdpc.profilepolicy.apprestrictions.AppRestrictionsManagingPackageFragment;
-import com.afwsamples.testdpc.profilepolicy.apprestrictions.ManageAppRestrictionsFragment;
+import com.afwsamples.testdpc.profilepolicy.apprestrictions.ManagedConfigurationsFragment;
 import com.afwsamples.testdpc.profilepolicy.delegation.DelegationFragment;
 import com.afwsamples.testdpc.profilepolicy.permission.ManageAppPermissionsFragment;
 import com.afwsamples.testdpc.transferownership.PickTransferComponentFragment;
@@ -293,7 +293,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
             = "install_nonmarket_apps";
     private static final String LOCK_SCREEN_POLICY_KEY = "lock_screen_policy";
     private static final String MANAGE_APP_PERMISSIONS_KEY = "manage_app_permissions";
-    private static final String MANAGE_APP_RESTRICTIONS_KEY = "manage_app_restrictions";
+    private static final String MANAGED_CONFIGURATIONS_KEY = "managed_configurations";
     private static final String MANAGED_PROFILE_SPECIFIC_POLICIES_KEY = "managed_profile_policies";
     private static final String MANAGE_LOCK_TASK_LIST_KEY = "manage_lock_task";
     private static final String MUTE_AUDIO_KEY = "mute_audio";
@@ -621,7 +621,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         findPreference(UNSUSPEND_APPS_KEY).setOnPreferenceClickListener(this);
         findPreference(CLEAR_APP_DATA_KEY).setOnPreferenceClickListener(this);
         findPreference(KEEP_UNINSTALLED_PACKAGES).setOnPreferenceClickListener(this);
-        findPreference(MANAGE_APP_RESTRICTIONS_KEY).setOnPreferenceClickListener(this);
+        findPreference(MANAGED_CONFIGURATIONS_KEY).setOnPreferenceClickListener(this);
         findPreference(DISABLE_METERED_DATA_KEY).setOnPreferenceClickListener(this);
         findPreference(GENERIC_DELEGATION_KEY).setOnPreferenceClickListener(this);
         findPreference(APP_RESTRICTIONS_MANAGING_PACKAGE_KEY).setOnPreferenceClickListener(this);
@@ -959,8 +959,8 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
             case KEEP_UNINSTALLED_PACKAGES:
                 showFragment(new ManageKeepUninstalledPackagesFragment());
                 return true;
-            case MANAGE_APP_RESTRICTIONS_KEY:
-                showFragment(new ManageAppRestrictionsFragment());
+            case MANAGED_CONFIGURATIONS_KEY:
+                showFragment(new ManagedConfigurationsFragment());
                 return true;
             case DISABLE_METERED_DATA_KEY:
                 showSetMeteredDataPrompt();
diff --git a/app/src/main/java/com/afwsamples/testdpc/profilepolicy/apprestrictions/ManageAppRestrictionsFragment.java b/app/src/main/java/com/afwsamples/testdpc/profilepolicy/apprestrictions/ManagedConfigurationsFragment.java
similarity index 98%
rename from app/src/main/java/com/afwsamples/testdpc/profilepolicy/apprestrictions/ManageAppRestrictionsFragment.java
rename to app/src/main/java/com/afwsamples/testdpc/profilepolicy/apprestrictions/ManagedConfigurationsFragment.java
index 00c66870..b350784f 100644
--- a/app/src/main/java/com/afwsamples/testdpc/profilepolicy/apprestrictions/ManageAppRestrictionsFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/profilepolicy/apprestrictions/ManagedConfigurationsFragment.java
@@ -52,7 +52,7 @@
  * This fragment shows all installed apps and allows viewing and editing application restrictions
  * for those apps. It also allows loading the default app restrictions for each of those apps.
  */
-public class ManageAppRestrictionsFragment extends ManageAppFragment
+public class ManagedConfigurationsFragment extends ManageAppFragment
         implements EditDeleteArrayAdapter.OnEditButtonClickListener<RestrictionEntry> {
     private List<RestrictionEntry> mRestrictionEntries = new ArrayList<>();
     private List<RestrictionEntry> mLastRestrictionEntries;
@@ -95,9 +95,9 @@ public void onCreate(Bundle savedInstanceState) {
     }
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-        getActivity().getActionBar().setTitle(R.string.manage_app_restrictions);
+    public void onResume() {
+        super.onResume();
+        getActivity().getActionBar().setTitle(R.string.managed_configurations);
     }
 
     protected void loadAppRestrictionsList(RestrictionEntry[] restrictionEntries) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 33412e24..18af6a80 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -540,7 +540,7 @@
     <string name="use_strongbox_checkbox">Use StrongBox</string>
 
     <!-- Strings for app restrictions -->
-    <string name="manage_app_restrictions">Manage app restrictions</string>
+    <string name="managed_configurations">Managed configurations</string>
     <string name="define_bundle">Define bundle</string>
     <string name="define_bundle_array">Define bundle array</string>
     <string name="load_manifest_restrictions">Load manifest restrictions</string>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 427c9be2..c8776061 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -169,8 +169,8 @@
             testdpc:delegation="delegation-keep-uninstalled-packages"
             testdpc:minSdkVersion="P" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
-            android:key="manage_app_restrictions"
-            android:title="@string/manage_app_restrictions"
+            android:key="managed_configurations"
+            android:title="@string/managed_configurations"
             testdpc:minSdkVersion="L"
             testdpc:delegation="delegation-app-restrictions" />
         <com.afwsamples.testdpc.common.preference.DpcPreference

From 3ebfbc60996b7d073432908d7a62c731d4325f8a Mon Sep 17 00:00:00 2001
From: Rubin Xu <rubinxu@google.com>
Date: Tue, 30 Jul 2019 16:05:32 +0100
Subject: [PATCH 12/73] Move 'Install update from file' perference under System
 update

Change-Id: Ic4de1590885acbe0ac43b13a72856a90248d6a2d
Fix: 138643581
Test: manual
---
 app/src/main/res/xml/device_policy_header.xml | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index c8776061..5dbadd1e 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -522,6 +522,11 @@
             android:title="@string/system_update_pending"
             testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="O" />
+        <com.afwsamples.testdpc.common.preference.DpcPreference
+            android:key="managed_system_updates"
+            android:title="@string/install_update"
+            testdpc:admin="deviceOwner"
+            testdpc:minSdkVersion="Q" />
     </PreferenceCategory>
 
     <PreferenceCategory android:title="@string/user_management">
@@ -721,10 +726,4 @@
             testdpc:admin="any"
             testdpc:minSdkVersion="P" />
     </PreferenceCategory>
-
-    <com.afwsamples.testdpc.common.preference.DpcPreference
-        android:key="managed_system_updates"
-        android:title="@string/install_update"
-        testdpc:admin="deviceOwner"
-        testdpc:minSdkVersion="Q" />
 </PreferenceScreen>

From 5dba59712fe644132261311c71c774264e4290bf Mon Sep 17 00:00:00 2001
From: Jonathan Scott <scottjonathan@google.com>
Date: Thu, 1 Aug 2019 17:20:23 +0100
Subject: [PATCH 13/73] Fix variable names in test for android style.

Test: ./gradlew test
Change-Id: I4c8cf0ff1de6d097820840243e76c1b43f934d16
---
 .../testdpc/common/PermissionsHelperTest.java | 78 ++++++++++---------
 .../feedback/AppStatesServiceTest.java        | 70 ++++++++---------
 2 files changed, 75 insertions(+), 73 deletions(-)

diff --git a/app/src/test/java/com/afwsamples/testdpc/common/PermissionsHelperTest.java b/app/src/test/java/com/afwsamples/testdpc/common/PermissionsHelperTest.java
index 039823b5..37f6a400 100644
--- a/app/src/test/java/com/afwsamples/testdpc/common/PermissionsHelperTest.java
+++ b/app/src/test/java/com/afwsamples/testdpc/common/PermissionsHelperTest.java
@@ -39,13 +39,15 @@
 @Config(minSdk = VERSION_CODES.M)
 public class PermissionsHelperTest {
 
-  private final Context context = RuntimeEnvironment.application;
-  private final DevicePolicyManager devicePolicyManager =
+  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);
-  private final ComponentName testDpcAdmin =
-      new ComponentName("com.afwsamples.testdpc", "TestCls");
-  private final ComponentName nonTestDpcAdmin = new ComponentName("TestPkg", "TestCls");
 
   // 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
@@ -57,7 +59,7 @@ public class PermissionsHelperTest {
   @Test
   public void ensureRequiredPermissions_ifPermissionMissingFromManifest_shouldReturnFalseAndLogError() {
     boolean requiredPermissionsGranted = PermissionsHelper
-        .ensureRequiredPermissions(new String[]{MISSING_PERMISSION}, nonTestDpcAdmin, context);
+        .ensureRequiredPermissions(new String[]{MISSING_PERMISSION}, NON_TESTDPC_ADMIN, mContext);
 
     assertFalse(requiredPermissionsGranted);
     assertTrue(ShadowLog.getLogsForTag(PermissionsHelper.TAG).get(0).msg
@@ -67,67 +69,67 @@ public void ensureRequiredPermissions_ifPermissionMissingFromManifest_shouldRetu
   @Test
   public void ensureRequiredPermissions_ifPermissionIsDangerousAndDpcIsProfileOwner_shouldReturnTrueAndSetPermissionGrantState() {
     addPermissionInfo(DANGEROUS_PERMISSION, PermissionInfo.PROTECTION_DANGEROUS);
-    shadowOf(devicePolicyManager).setProfileOwner(testDpcAdmin);
+    shadowOf(mDevicePolicyManager).setProfileOwner(TESTDPC_ADMIN);
 
     boolean requiredPermissionsGranted = PermissionsHelper
-        .ensureRequiredPermissions(new String[]{DANGEROUS_PERMISSION}, testDpcAdmin, context);
+        .ensureRequiredPermissions(new String[]{DANGEROUS_PERMISSION}, TESTDPC_ADMIN, mContext);
 
     assertTrue(requiredPermissionsGranted);
-    assertThat(devicePolicyManager
-        .getPermissionGrantState(testDpcAdmin, context.getPackageName(), DANGEROUS_PERMISSION))
+    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(devicePolicyManager).setDeviceOwner(testDpcAdmin);
+    shadowOf(mDevicePolicyManager).setDeviceOwner(TESTDPC_ADMIN);
 
     boolean requiredPermissionsGranted = PermissionsHelper
-        .ensureRequiredPermissions(new String[]{DANGEROUS_PERMISSION}, testDpcAdmin, context);
+        .ensureRequiredPermissions(new String[]{DANGEROUS_PERMISSION}, TESTDPC_ADMIN, mContext);
 
     assertTrue(requiredPermissionsGranted);
-    assertThat(devicePolicyManager
-        .getPermissionGrantState(testDpcAdmin, context.getPackageName(), DANGEROUS_PERMISSION))
+    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(devicePolicyManager).setProfileOwner(testDpcAdmin);
-    devicePolicyManager.setPermissionGrantState(
-        testDpcAdmin,
-        context.getPackageName(),
+    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}, testDpcAdmin, context);
+        .ensureRequiredPermissions(new String[]{DANGEROUS_PERMISSION}, TESTDPC_ADMIN, mContext);
 
     assertTrue(requiredPermissionsGranted);
-    assertThat(devicePolicyManager
-        .getPermissionGrantState(testDpcAdmin, context.getPackageName(), DANGEROUS_PERMISSION))
+    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(devicePolicyManager).setProfileOwner(nonTestDpcAdmin);
+    shadowOf(mDevicePolicyManager).setProfileOwner(NON_TESTDPC_ADMIN);
 
     boolean requiredPermissionsGranted = PermissionsHelper
-        .ensureRequiredPermissions(new String[]{DANGEROUS_PERMISSION}, nonTestDpcAdmin, context);
+        .ensureRequiredPermissions(new String[]{DANGEROUS_PERMISSION}, NON_TESTDPC_ADMIN, mContext);
 
     assertFalse(requiredPermissionsGranted);
   }
 
   @Test
   public void ensureRequiredPermissions_ifPermissionInfoNotFound_shouldReturnTrueAndLogError() {
-    shadowOf(devicePolicyManager).setProfileOwner(testDpcAdmin);
+    shadowOf(mDevicePolicyManager).setProfileOwner(TESTDPC_ADMIN);
 
     boolean requiredPermissionsGranted = PermissionsHelper
-        .ensureRequiredPermissions(new String[]{MISSING_INFO_PERMISSION}, testDpcAdmin, context);
+        .ensureRequiredPermissions(new String[]{MISSING_INFO_PERMISSION}, TESTDPC_ADMIN, mContext);
 
     assertTrue(requiredPermissionsGranted);
     assertTrue(ShadowLog.getLogsForTag(PermissionsHelper.TAG).get(0).msg
@@ -137,14 +139,14 @@ public void ensureRequiredPermissions_ifPermissionInfoNotFound_shouldReturnTrueA
   @Test
   public void ensureRequiredPermissions_ifPermissionIsNormal_shouldReturnTrueAndNotSetPermissionGrantState() {
     addPermissionInfo(NORMAL_PERMISSION, PermissionInfo.PROTECTION_NORMAL);
-    shadowOf(devicePolicyManager).setProfileOwner(testDpcAdmin);
+    shadowOf(mDevicePolicyManager).setProfileOwner(TESTDPC_ADMIN);
 
     boolean requiredPermissionsGranted = PermissionsHelper
-        .ensureRequiredPermissions(new String[]{NORMAL_PERMISSION}, testDpcAdmin, context);
+        .ensureRequiredPermissions(new String[]{NORMAL_PERMISSION}, TESTDPC_ADMIN, mContext);
 
     assertTrue(requiredPermissionsGranted);
-    assertThat(devicePolicyManager
-        .getPermissionGrantState(testDpcAdmin, context.getPackageName(), NORMAL_PERMISSION))
+    assertThat(mDevicePolicyManager
+        .getPermissionGrantState(TESTDPC_ADMIN, mContext.getPackageName(), NORMAL_PERMISSION))
         .isEqualTo(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
   }
 
@@ -152,18 +154,18 @@ public void ensureRequiredPermissions_ifPermissionIsNormal_shouldReturnTrueAndNo
   public void ensureRequiredPermissions_ifAllPermissionsAreGranted_shouldReturnTrue() {
     addPermissionInfo(NORMAL_PERMISSION, PermissionInfo.PROTECTION_NORMAL);
     addPermissionInfo(DANGEROUS_PERMISSION, PermissionInfo.PROTECTION_DANGEROUS);
-    shadowOf(devicePolicyManager).setProfileOwner(testDpcAdmin);
+    shadowOf(mDevicePolicyManager).setProfileOwner(TESTDPC_ADMIN);
 
     boolean requiredPermissionsGranted = PermissionsHelper
         .ensureRequiredPermissions(new String[]{NORMAL_PERMISSION, DANGEROUS_PERMISSION},
-            testDpcAdmin, context);
+            TESTDPC_ADMIN, mContext);
 
     assertTrue(requiredPermissionsGranted);
-    assertThat(devicePolicyManager
-        .getPermissionGrantState(testDpcAdmin, context.getPackageName(), NORMAL_PERMISSION))
+    assertThat(mDevicePolicyManager
+        .getPermissionGrantState(TESTDPC_ADMIN, mContext.getPackageName(), NORMAL_PERMISSION))
         .isEqualTo(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-    assertThat(devicePolicyManager
-        .getPermissionGrantState(testDpcAdmin, context.getPackageName(), DANGEROUS_PERMISSION))
+    assertThat(mDevicePolicyManager
+        .getPermissionGrantState(TESTDPC_ADMIN, mContext.getPackageName(), DANGEROUS_PERMISSION))
         .isEqualTo(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
   }
 
@@ -171,11 +173,11 @@ public void ensureRequiredPermissions_ifAllPermissionsAreGranted_shouldReturnTru
   public void ensureRequiredPermissions_ifAtLeastOnePermissionNotGranted_shouldReturnFalse() {
     addPermissionInfo(NORMAL_PERMISSION, PermissionInfo.PROTECTION_NORMAL);
     addPermissionInfo(DANGEROUS_PERMISSION, PermissionInfo.PROTECTION_DANGEROUS);
-    shadowOf(devicePolicyManager).setProfileOwner(nonTestDpcAdmin);
+    shadowOf(mDevicePolicyManager).setProfileOwner(NON_TESTDPC_ADMIN);
 
     boolean requiredPermissionsGranted = PermissionsHelper
         .ensureRequiredPermissions(new String[]{NORMAL_PERMISSION, DANGEROUS_PERMISSION},
-            nonTestDpcAdmin, context);
+            NON_TESTDPC_ADMIN, mContext);
 
     assertFalse(requiredPermissionsGranted);
   }
@@ -184,7 +186,7 @@ private void addPermissionInfo(String permission, int protectionLevel) {
     PermissionInfo permissionInfo = new PermissionInfo();
     permissionInfo.name = permission;
     permissionInfo.protectionLevel = protectionLevel;
-    shadowOf(context.getPackageManager()).addPermissionInfo(permissionInfo);
+    shadowOf(mContext.getPackageManager()).addPermissionInfo(permissionInfo);
   }
 
 }
\ No newline at end of file
diff --git a/app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java b/app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java
index da9cdd28..dd3961ab 100644
--- a/app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java
+++ b/app/src/test/java/com/afwsamples/testdpc/feedback/AppStatesServiceTest.java
@@ -78,28 +78,28 @@ public class AppStatesServiceTest {
   private static final ReceivedKeyedAppState INFO_STATE = STATE1;
   private static final ReceivedKeyedAppState ERROR_STATE = STATE2;
 
-  private final Context context = RuntimeEnvironment.application;
-  private final SharedPreferences preferences =
-      PreferenceManager.getDefaultSharedPreferences(context);
-  private final NotificationManager notificationManager =
-    context.getSystemService(NotificationManager.class);
-  private final AppStatesService service =
+  private final Context mContext = RuntimeEnvironment.application;
+  private final SharedPreferences mPreferences =
+      PreferenceManager.getDefaultSharedPreferences(mContext);
+  private final NotificationManager mNotificationManager =
+    mContext.getSystemService(NotificationManager.class);
+  private final AppStatesService mService =
     Robolectric.buildService(AppStatesService.class).get();
 
   @Test
   public void onReceive_shouldNotNotify_noNotification() {
     setNotificationPreference(false);
 
-    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
 
-    assertThat(shadowOf(notificationManager).getActiveNotifications()).isEmpty();
+    assertThat(shadowOf(mNotificationManager).getActiveNotifications()).isEmpty();
   }
 
   @Test
   public void onReceive_shouldNotNotify_noLogs() {
     setNotificationPreference(false);
 
-    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
 
     assertThat(ShadowLog.getLogsForTag(AppStatesService.TAG)).isEmpty();
   }
@@ -108,7 +108,7 @@ public void onReceive_shouldNotNotify_noLogs() {
   public void onReceive_shouldNotify_logContainsRequiredInformation() {
     setNotificationPreference(true);
 
-    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
 
     assertThatLogContainsRequiredInformation(
         ShadowLog.getLogsForTag(AppStatesService.TAG).get(0), STATE1);
@@ -127,7 +127,7 @@ private void assertThatLogContainsRequiredInformation(
   public void onReceive_infoLog_shouldNotify_logIsInfoLevel() {
     setNotificationPreference(true);
 
-    service.onReceive(Arrays.asList(INFO_STATE), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(INFO_STATE), /* requestSync= */ false);
 
     assertThat(ShadowLog.getLogsForTag(AppStatesService.TAG).get(0).type).isEqualTo(INFO);
   }
@@ -136,7 +136,7 @@ public void onReceive_infoLog_shouldNotify_logIsInfoLevel() {
   public void onReceive_errorLog_shouldNotify_logIsErrorLevel() {
     setNotificationPreference(true);
 
-    service.onReceive(Arrays.asList(ERROR_STATE), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(ERROR_STATE), /* requestSync= */ false);
 
     assertThat(ShadowLog.getLogsForTag(AppStatesService.TAG).get(0).type).isEqualTo(ERROR);
   }
@@ -145,7 +145,7 @@ public void onReceive_errorLog_shouldNotify_logIsErrorLevel() {
   public void onReceive_shouldNotify_noRequestSync_logDoesNotContainRequestSync() {
     setNotificationPreference(true);
 
-    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
 
     ShadowLog.LogItem logItem = ShadowLog.getLogsForTag(AppStatesService.TAG).get(0);
     assertThat(logItem.msg).doesNotContain(REQUEST_SYNC_LOG_TEXT);
@@ -155,7 +155,7 @@ public void onReceive_shouldNotify_noRequestSync_logDoesNotContainRequestSync()
   public void onReceive_shouldNotify_requestSync_logContainsRequestSync() {
     setNotificationPreference(true);
 
-    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ true);
+    mService.onReceive(Arrays.asList(STATE1), /* requestSync= */ true);
 
     ShadowLog.LogItem logItem = ShadowLog.getLogsForTag(AppStatesService.TAG).get(0);
     assertThat(logItem.msg).contains(REQUEST_SYNC_LOG_TEXT);
@@ -165,7 +165,7 @@ public void onReceive_shouldNotify_requestSync_logContainsRequestSync() {
   public void onReceive_shouldNotify_oneLogPerState() {
     setNotificationPreference(true);
 
-    service.onReceive(Arrays.asList(STATE1, STATE2), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(STATE1, STATE2), /* requestSync= */ false);
 
     assertThat(ShadowLog.getLogsForTag(AppStatesService.TAG)).hasSize(2);
   }
@@ -174,10 +174,10 @@ public void onReceive_shouldNotify_oneLogPerState() {
   public void onReceive_shouldNotify_notificationContainsRequiredInformation() {
     setNotificationPreference(true);
 
-    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
 
     assertThatNotificationContainsRequiredInformation(
-        shadowOf(notificationManager).getAllNotifications().get(0),
+        shadowOf(mNotificationManager).getAllNotifications().get(0),
         STATE1
     );
   }
@@ -196,9 +196,9 @@ private void assertThatNotificationContainsRequiredInformation(
   public void onReceive_infoLog_shouldNotify_notificationTitleIncludesInfo() {
     setNotificationPreference(true);
 
-    service.onReceive(Arrays.asList(INFO_STATE), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(INFO_STATE), /* requestSync= */ false);
 
-    final Notification notification = shadowOf(notificationManager).getAllNotifications().get(0);
+    final Notification notification = shadowOf(mNotificationManager).getAllNotifications().get(0);
     assertThat(
         shadowOf(notification).getContentTitle().toString()).contains("INFO");
   }
@@ -207,9 +207,9 @@ public void onReceive_infoLog_shouldNotify_notificationTitleIncludesInfo() {
   public void onReceive_errorLog_shouldNotify_notificationTitleIncludesError() {
     setNotificationPreference(true);
 
-    service.onReceive(Arrays.asList(ERROR_STATE), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(ERROR_STATE), /* requestSync= */ false);
 
-    final Notification notification = shadowOf(notificationManager).getAllNotifications().get(0);
+    final Notification notification = shadowOf(mNotificationManager).getAllNotifications().get(0);
     assertThat(
         shadowOf(notification).getContentTitle().toString()).contains("ERROR");
   }
@@ -218,9 +218,9 @@ public void onReceive_errorLog_shouldNotify_notificationTitleIncludesError() {
   public void onReceive_shouldNotify_noRequestSync_notificationDoesNotContainRequestSync() {
     setNotificationPreference(true);
 
-    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
 
-    final Notification notification = shadowOf(notificationManager).getAllNotifications().get(0);
+    final Notification notification = shadowOf(mNotificationManager).getAllNotifications().get(0);
     assertThat(
         shadowOf(notification).getContentText().toString()).doesNotContain(REQUEST_SYNC_LOG_TEXT);
   }
@@ -229,9 +229,9 @@ public void onReceive_shouldNotify_noRequestSync_notificationDoesNotContainReque
   public void onReceive_shouldNotify_requestSync_notificationContainsRequestSync() {
     setNotificationPreference(true);
 
-    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ true);
+    mService.onReceive(Arrays.asList(STATE1), /* requestSync= */ true);
 
-    final Notification notification = shadowOf(notificationManager).getAllNotifications().get(0);
+    final Notification notification = shadowOf(mNotificationManager).getAllNotifications().get(0);
     assertThat(shadowOf(notification).getContentText().toString()).contains(REQUEST_SYNC_LOG_TEXT);
   }
 
@@ -239,34 +239,34 @@ public void onReceive_shouldNotify_requestSync_notificationContainsRequestSync()
   public void onReceive_shouldNotify_oneNotificationPerKey() {
     setNotificationPreference(true);
 
-    service.onReceive(Arrays.asList(STATE1, STATE2), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(STATE1, STATE2), /* requestSync= */ false);
 
-    assertThat(shadowOf(notificationManager).getAllNotifications()).hasSize(2);
+    assertThat(shadowOf(mNotificationManager).getAllNotifications()).hasSize(2);
   }
 
   @Test
   public void onReceive_multiple_shouldNotify_oneNotificationPerKey() {
     setNotificationPreference(true);
-    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
 
-    service.onReceive(Arrays.asList(STATE2), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(STATE2), /* requestSync= */ false);
 
-    assertThat(shadowOf(notificationManager).getAllNotifications()).hasSize(2);
+    assertThat(shadowOf(mNotificationManager).getAllNotifications()).hasSize(2);
   }
 
   @Test
   public void onReceive_shouldNotify_sameKeyUpdatesNotification() {
     setNotificationPreference(true);
-    service.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(STATE1), /* requestSync= */ false);
 
-    service.onReceive(Arrays.asList(STATE1_DIFFERENT_MESSAGE), /* requestSync= */ false);
+    mService.onReceive(Arrays.asList(STATE1_DIFFERENT_MESSAGE), /* requestSync= */ false);
 
-    assertThat(shadowOf(notificationManager).getAllNotifications()).hasSize(1);
+    assertThat(shadowOf(mNotificationManager).getAllNotifications()).hasSize(1);
   }
 
   private void setNotificationPreference(boolean shouldNotify) {
-    preferences.edit()
-        .putBoolean(context.getString(R.string.app_feedback_notifications), shouldNotify)
+    mPreferences.edit()
+        .putBoolean(mContext.getString(R.string.app_feedback_notifications), shouldNotify)
         .commit();
   }
 }
\ No newline at end of file

From 7de9b2a4f48b825d7cc3cde4addbb0683b8b793c Mon Sep 17 00:00:00 2001
From: Frank Alcantara <falcantara@google.com>
Date: Fri, 19 Jul 2019 19:50:20 -0400
Subject: [PATCH 14/73] Ensure a new task is actually created when relaunching
 in lock task mode and not just brought to the foreground. Retain default
 behavior when already in lock task mode.

Added tests.

Added several NullPointerException checks to deal with failures during
testing.

Fixes: 137922683
Change-Id: Icd4b6b9d95acbb0701090fe06b6722f30b4745e5
---
 .../com/afwsamples/testdpc/common/Util.java   |   9 +-
 .../preference/DpcPreferenceHelper.java       |  14 ++-
 .../policy/PolicyManagementFragment.java      |  23 +++-
 .../policy/PolicyManagementFragmentTest.java  | 107 ++++++++++++++++++
 4 files changed, 147 insertions(+), 6 deletions(-)
 create mode 100644 app/src/test/java/com/afwsamples/testdpc/policy/PolicyManagementFragmentTest.java

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 241dcdbe..048591ce 100644
--- a/app/src/main/java/com/afwsamples/testdpc/common/Util.java
+++ b/app/src/main/java/com/afwsamples/testdpc/common/Util.java
@@ -164,8 +164,13 @@ public static List<UserHandle> getBindDeviceAdminTargetUsers(Context context) {
             return Collections.emptyList();
         }
 
-        final DevicePolicyManager dpm = getDevicePolicyManager(context);
-        return dpm.getBindDeviceAdminTargetUsers(DeviceAdminReceiver.getComponentName(context));
+        List<UserHandle> targetUsers = getDevicePolicyManager(context).
+            getBindDeviceAdminTargetUsers(DeviceAdminReceiver.getComponentName(context));
+        if (targetUsers != null){
+            return targetUsers;
+        }
+
+        return Collections.emptyList();
     }
 
     public static void showFileViewer(PreferenceFragment fragment, int requestCode) {
diff --git a/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java b/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
index 7a380489..7cc3c1ed 100644
--- a/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
+++ b/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
@@ -260,12 +260,18 @@ private int getCurrentAdmin() {
 
     private List<String> getCurrentDelegations() {
         if (Util.SDK_INT < VERSION_CODES.O) {
-            return Collections.EMPTY_LIST;
+            return Collections.emptyList();
         }
+
+        if (!Util.isDeviceOwner(mContext) && !Util.isProfileOwner(mContext)) {
+            return Collections.emptyList();
+        }
+
         final DevicePolicyManager dpm =
                 (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
         final String packageName = mContext.getPackageName();
-            return dpm.getDelegatedScopes(null, packageName);
+        return dpm.getDelegatedScopes(null, packageName);
+
     }
 
     private int getCurrentUser() {
@@ -289,6 +295,10 @@ private boolean isEnabledForAdmin(@AdminKind int admin) {
     }
 
     private boolean hasDelegation(List<String> delegations) {
+        if (mDelegationConstraint == null) {
+            return false;
+        }
+
         return delegations.contains(mDelegationConstraint);
     }
 
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 a0095406..b8e4ce00 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -27,6 +27,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.AlertDialog;
 import android.app.DialogFragment;
@@ -750,8 +751,11 @@ private boolean isDelegatedApp() {
         if (Util.SDK_INT < VERSION_CODES.O) {
             return false;
         }
+
         DevicePolicyManager dpm = getActivity().getSystemService(DevicePolicyManager.class);
-        return !dpm.getDelegatedScopes(null, getActivity().getPackageName()).isEmpty();
+        List<String> scopes = dpm.getDelegatedScopes(null, getActivity().getPackageName());
+
+        return scopes == null ? false : !scopes.isEmpty();
     }
 
     @Override
@@ -2206,7 +2210,15 @@ private void reloadAutoBrightnessUi() {
         if (mAutoBrightnessPreference.isEnabled()) {
             final String brightnessMode = Settings.System.getString(
                     getActivity().getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE);
-            mAutoBrightnessPreference.setChecked(Integer.parseInt(brightnessMode) == 1);
+            mAutoBrightnessPreference.setChecked(parseInt(brightnessMode, /* defaultValue= */ 0) == 1);
+        }
+    }
+
+    static private int parseInt(String str, int defaultValue) {
+        try {
+            return Integer.parseInt(str);
+        } catch (NumberFormatException e) {
+            return defaultValue;
         }
     }
 
@@ -3323,9 +3335,16 @@ private void showFragment(final Fragment fragment, String tag) {
 
     @TargetApi(VERSION_CODES.P)
     private void relaunchInLockTaskMode() {
+        ActivityManager activityManager = getContext().getSystemService(ActivityManager.class);
+
         final Intent intent = new Intent(getActivity(), getActivity().getClass());
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
+        // Ensure a new task is actually created if not already running in lock task mode
+        if (!activityManager.isInLockTaskMode()){
+            intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        }
+
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLockTaskEnabled(true);
 
diff --git a/app/src/test/java/com/afwsamples/testdpc/policy/PolicyManagementFragmentTest.java b/app/src/test/java/com/afwsamples/testdpc/policy/PolicyManagementFragmentTest.java
new file mode 100644
index 00000000..62be9679
--- /dev/null
+++ b/app/src/test/java/com/afwsamples/testdpc/policy/PolicyManagementFragmentTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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<>());
+  }
+}
+

From 511ecab02fb4a785b835a2e455e66968c56d3fe9 Mon Sep 17 00:00:00 2001
From: Jonathan Scott <scottjonathan@google.com>
Date: Wed, 2 Oct 2019 11:52:14 +0100
Subject: [PATCH 15/73] Bump version for full Q release.

Test: builds
Bug: 141961516
Change-Id: I9f52ee61333afa3538b58c0b01f13ff37ccf1ee6
---
 app/build.gradle | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index 793e8532..9bc58b88 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -7,9 +7,9 @@ ext {
     // 1 or more digits
     versionMajor = 6
     // exactly 1 digit
-    versionMinor = 1
+    versionMinor = 2
     // exactly 2 digits
-    versionBuild = 02
+    versionBuild = 00
 }
 
 android {

From d2cd938d5dec39e526bd92f862693391f61b4063 Mon Sep 17 00:00:00 2001
From: Alex Kershaw <alexkershaw@google.com>
Date: Fri, 11 Oct 2019 16:16:38 +0100
Subject: [PATCH 16/73] Add pre-O support for wifi configuration.

Note: Ideally, the tests wouldn't test such an implementation detail but
Robolectric doesn't understand the pre-O and post-O differences. It's
probably not worth changing Robolectric for this. I modified the Javadoc
of the public API slightly so that at least we're testing against
something mentioned in the public interface.

Fixes: 141458443
Test: gradlew test
Change-Id: I919662e2998f407efd761101836bf9c9273f4f97
---
 .../policy/wifimanagement/WifiConfigUtil.java | 60 ++++++++++----
 .../wifimanagement/WifiConfigUtilTest.java    | 83 +++++++++++++++++++
 2 files changed, 128 insertions(+), 15 deletions(-)
 create mode 100644 app/src/test/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtilTest.java

diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtil.java b/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtil.java
index f9010158..fdf62638 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtil.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtil.java
@@ -19,33 +19,63 @@
 import android.content.Context;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
+import android.os.Build;
 
 public class WifiConfigUtil {
 
   /**
-   * Save or replace the wifi configuration.
+   * Save or replace the WiFi configuration.
    *
-   * @return success to add/replace the wifi configuration
+   * <p>Correctly 'saves' the network with the {@link WifiManager} pre-O, as required.
+   *
+   * @return whether it was successful.
    */
-  public static boolean saveWifiConfiguration(Context context, WifiConfiguration
-      wifiConfiguration) {
+  public static boolean saveWifiConfiguration(
+      Context context, WifiConfiguration wifiConfiguration) {
     WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-    final int networkId;
-
-    // WifiManager deprecated APIs including #addNetwork, #updateNetwork, #enableNetwork are restricted to system apps and DPCs
-    // https://developer.android.com/preview/privacy/camera-connectivity#wifi-network-config-restrictions
-    if (wifiConfiguration.networkId == -1) {
-      // new wifi configuration, add it and then save it.
-      networkId = wifiManager.addNetwork(wifiConfiguration);
-    } else {
-      // existing wifi configuration, update it and then save it.
-      networkId = wifiManager.updateNetwork(wifiConfiguration);
+    return wifiConfiguration.networkId == -1
+        ? addWifiNetwork(wifiManager, wifiConfiguration)
+        : updateWifiNetwork(wifiManager, wifiConfiguration);
+  }
+
+  private static boolean addWifiNetwork(
+      WifiManager wifiManager, WifiConfiguration wifiConfiguration) {
+    // WifiManager APIs are marked as deprecated but still explicitly supported for DPCs.
+    int networkId = wifiManager.addNetwork(wifiConfiguration);
+    if (networkId == -1) {
+      return false;
+    }
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+      // Saving the configuration is required pre-O.
+      return saveAddedWifiConfiguration(wifiManager, networkId);
+    }
+    return true;
+  }
+
+  private static boolean saveAddedWifiConfiguration(WifiManager wifiManager, int networkId) {
+    boolean saveConfigurationSuccess = wifiManager.saveConfiguration();
+    if (!saveConfigurationSuccess) {
+      wifiManager.removeNetwork(networkId);
+      return false;
     }
+    return true;
+  }
 
+  private static boolean updateWifiNetwork(
+      WifiManager wifiManager, WifiConfiguration wifiConfiguration) {
+    // WifiManager APIs are marked as deprecated but still explicitly supported for DPCs.
+    int networkId = wifiManager.updateNetwork(wifiConfiguration);
     if (networkId == -1) {
       return false;
     }
-    wifiManager.enableNetwork(networkId, /* disableOthers= */ false);
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+      // Saving the configuration is required pre-O.
+      return saveUpdatedWifiConfiguration(wifiManager);
+    }
     return true;
   }
+
+  private static boolean saveUpdatedWifiConfiguration(WifiManager wifiManager) {
+    return wifiManager.saveConfiguration();
+  }
 }
diff --git a/app/src/test/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtilTest.java b/app/src/test/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtilTest.java
new file mode 100644
index 00000000..56ea9818
--- /dev/null
+++ b/app/src/test/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtilTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2019 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.wifimanagement;
+
+import static android.os.Build.VERSION_CODES.LOLLIPOP;
+import static android.os.Build.VERSION_CODES.N;
+import static android.os.Build.VERSION_CODES.O;
+import static com.google.common.truth.Truth.assertThat;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+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.ShadowWifiManager;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(minSdk = LOLLIPOP)
+public class WifiConfigUtilTest {
+  private final Context mContext = RuntimeEnvironment.application;
+  private final WifiManager mWifiManager =
+      (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+
+  @Test
+  @Config(maxSdk = N)
+  public void saveWifiConfiguration_preO_addNetwork_savesWifiConfiguration() {
+    WifiConfigUtil.saveWifiConfiguration(mContext, buildWifiConfigurationToAddNetwork());
+    assertThat(shadowOf(mWifiManager).wasConfigurationSaved()).isTrue();
+  }
+
+  @Test
+  @Config(minSdk = O)
+  public void saveWifiConfiguration_postO_addNetwork_doesNotSaveWifiConfiguration() {
+    WifiConfigUtil.saveWifiConfiguration(mContext, buildWifiConfigurationToAddNetwork());
+    assertThat(shadowOf(mWifiManager).wasConfigurationSaved()).isFalse();
+  }
+
+  @Test
+  @Config(maxSdk = N)
+  public void saveWifiConfiguration_preO_updateNetwork_savesWifiConfiguration() {
+    WifiConfigUtil.saveWifiConfiguration(mContext, buildWifiConfigurationToUpdateNetwork());
+    assertThat(shadowOf(mWifiManager).wasConfigurationSaved()).isTrue();
+  }
+
+  @Test
+  @Config(minSdk = O)
+  public void saveWifiConfiguration_postO_updateNetwork_doesNotSaveWifiConfiguration() {
+    WifiConfigUtil.saveWifiConfiguration(mContext, buildWifiConfigurationToUpdateNetwork());
+    assertThat(shadowOf(mWifiManager).wasConfigurationSaved()).isFalse();
+  }
+
+  private WifiConfiguration buildWifiConfigurationToAddNetwork() {
+    return buildWifiConfigurationWithNetworkId(-1);
+  }
+
+  private WifiConfiguration buildWifiConfigurationToUpdateNetwork() {
+    return buildWifiConfigurationWithNetworkId(1);
+  }
+
+  private WifiConfiguration buildWifiConfigurationWithNetworkId(int networkId) {
+    WifiConfiguration wifiConfiguration = new WifiConfiguration();
+    wifiConfiguration.networkId = networkId;
+    return wifiConfiguration;
+  }
+}

From aa7f40b912c1f1949b9cbb3479b4c4fbb66252e2 Mon Sep 17 00:00:00 2001
From: Eran Messeri <eranm@google.com>
Date: Fri, 18 Oct 2019 14:30:10 +0100
Subject: [PATCH 17/73] Remove all references to SafetyNet

The code for SafetyNet attestation no longer exists. Remove UI elements
that referred to it which now do nothing.

A much better source for examples how to work with SafetyNet can be
found at:
https://developer.android.com/training/safetynet/attestation.html

Test: That it compiles
Change-Id: I9508a396b5e548dbdf9d880bd051130e927689b6
---
 .../res/layout/safety_net_attest_dialog.xml   | 27 -------------------
 app/src/main/res/values/strings.xml           | 15 -----------
 app/src/main/res/values/styles.xml            |  7 -----
 app/src/main/res/xml/device_policy_header.xml |  8 ------
 4 files changed, 57 deletions(-)
 delete mode 100644 app/src/main/res/layout/safety_net_attest_dialog.xml

diff --git a/app/src/main/res/layout/safety_net_attest_dialog.xml b/app/src/main/res/layout/safety_net_attest_dialog.xml
deleted file mode 100644
index e3d68e68..00000000
--- a/app/src/main/res/layout/safety_net_attest_dialog.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        style="@style/contentContainer">
-
-    <TextView
-            android:id="@+id/message_view"
-            style="@style/safety_net_attest_message"
-            android:scrollbars = "vertical"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 18af6a80..3972137a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1013,21 +1013,6 @@
     <string name="transfer_ownership_affiliated_complete_title">Transfer of affiliated profile ownership is complete</string>
     <string name="transfer_ownership_affiliated_complete_message">The transferred user is %1$s</string>
 
-    <!-- SafetyNet -->
-    <string name="safetynet_preference_title">SafetyNet</string>
-    <string name="attest_preference_title">SafetyNet Attestation</string>
-    <string name="attest_preference_summary">Checking Device Compatibility with SafetyNet</string>
-    <string name="safetynet_dialog_title">SafetyNet Attestation</string>
-    <string name="safetynet_dialog_msg_running">Running...</string>
-    <string name="cancel_safetynet_msg">Cancel SafetyNet Attestation</string>
-    <string name="safetynet_fail_to_run_api">Fail to run SafetyNet Attestation API</string>
-    <string name="safetynet_fail_reason_no_internet">Error: No Internet connecion</string>
-    <string name="safetynet_fail_reason_gmscore_upgrade">Error: Google Play Service is out of date</string>
-    <string name="safetynet_fail_reason_error_code">Error code: %1$d</string>
-    <string name="safetynet_fail_reason_invalid_jws">Invalid JWS response</string>
-    <string name="safetynet_running">Running&#8230; Please wait</string>
-    <string name="safetynet_verify_on_server">You should verify the response in your server.</string>
-
     <string name="request_to_set_new_password">Request user to set new password</string>
     <string name="request_to_set_new_password_with_complexity">Set new password and specify minimum complexity</string>
     <string name="request_to_set_profile_parent_new_password">Request user to set profile parent new password</string>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index b5d40b3d..a404654c 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -86,13 +86,6 @@
         <item name="android:textSize">18sp</item>
     </style>
 
-    <style name="safety_net_attest_message">
-        <item name="android:layout_width">match_parent</item>
-        <item name="android:layout_height">wrap_content</item>
-        <item name="android:layout_marginTop">8dp</item>
-        <item name="android:textSize">16sp</item>
-    </style>
-
     <style name="SelectionTitle">
         <item name="android:textSize">@dimen/selection_detailed_title_size</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 5dbadd1e..94308836 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -711,14 +711,6 @@
             testdpc:minSdkVersion="P" />
     </PreferenceCategory>
 
-    <PreferenceCategory android:title="@string/safetynet_preference_title">
-        <com.afwsamples.testdpc.common.preference.DpcPreference
-            android:key="safetynet_attest"
-            android:summary="@string/attest_preference_summary"
-            android:title="@string/attest_preference_title"
-            testdpc:minSdkVersion="L" />
-    </PreferenceCategory>
-
     <PreferenceCategory android:title="@string/cross_profile_section">
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="cross_profile_apps"

From 3087acfaa9cff56146aa64bc455a76e8421c55e0 Mon Sep 17 00:00:00 2001
From: Eran Messeri <eranm@google.com>
Date: Thu, 7 Nov 2019 14:17:28 +0000
Subject: [PATCH 18/73] Add UI for calling relinquishDevice

Add a TestDPC policy fragment for calling
DevicePolicyManager.relinquishDevice (a new method meant for a profile
owner of a corp-owned asset to remove the work profile and let the user
use the device as a personal device).

This is very similar to other policies - a key in the
device_policy_header and a corresponding handler in
PolicyManagementFragment.

Because this in an R API, attrs.xml and Util.java had to be changed to
recognize R APIs correctly.

Bug: 138709470
Test: Manual
Change-Id: Ic9b73be99cf6e2e58ceec70e0befbc1ef1c060d0
---
 .../com/afwsamples/testdpc/common/Util.java    | 11 +++++++----
 .../policy/PolicyManagementFragment.java       | 18 ++++++++++++++++++
 app/src/main/res/values/attrs.xml              |  1 +
 app/src/main/res/values/strings.xml            |  2 ++
 app/src/main/res/xml/device_policy_header.xml  |  5 +++++
 5 files changed, 33 insertions(+), 4 deletions(-)

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 048591ce..84c90450 100644
--- a/app/src/main/java/com/afwsamples/testdpc/common/Util.java
+++ b/app/src/main/java/com/afwsamples/testdpc/common/Util.java
@@ -57,9 +57,11 @@ public class Util {
     private static final String GMSCORE_PACKAGE = "com.google.android.gms";
     private static final String PERSISTENT_DEVICE_OWNER_STATE = "persistentDeviceOwnerState";
 
-    // TODO:(133143789): Remove when we no longer need to support pre-release Q
-    private static final boolean IS_RUNNING_Q =
-        VERSION.CODENAME.length() == 1 && VERSION.CODENAME.charAt(0) == 'Q';
+    // TODO: Update to S when VERSION_CODES.R becomes available.
+    public static final int R_VERSION_CODE = 30;
+
+    private static final boolean IS_RUNNING_R =
+        VERSION.CODENAME.length() == 1 && VERSION.CODENAME.charAt(0) == 'R';
 
     /**
      * A replacement for {@link VERSION.SDK_INT} that is compatible with pre-release SDKs
@@ -67,7 +69,8 @@ public class Util {
      * <p>This will be set to the version SDK, or {@link VERSION_CODES.CUR_DEVELOPMENT} if the SDK
      * int is not yet assigned.
      **/
-    public static final int SDK_INT = IS_RUNNING_Q ? VERSION_CODES.Q : VERSION.SDK_INT;
+    public static final int SDK_INT =
+        IS_RUNNING_R ? VERSION_CODES.CUR_DEVELOPMENT : VERSION.SDK_INT;
 
     /**
      * Format a friendly datetime for the current locale according to device policy documentation.
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 b8e4ce00..78903b95 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -98,6 +98,8 @@
 import com.afwsamples.testdpc.common.CertificateUtil;
 import com.afwsamples.testdpc.common.MediaDisplayFragment;
 import com.afwsamples.testdpc.common.PackageInstallationUtils;
+import com.afwsamples.testdpc.common.ReflectionUtil;
+import com.afwsamples.testdpc.common.ReflectionUtil.ReflectionIsTemporaryException;
 import com.afwsamples.testdpc.common.UserArrayAdapter;
 import com.afwsamples.testdpc.common.Util;
 import com.afwsamples.testdpc.common.preference.CustomConstraint;
@@ -388,6 +390,8 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
 
     private static final String SET_PRIVATE_DNS_MODE_KEY = "set_private_dns_mode";
 
+    private static final String RELINQUISH_DEVICE = "relinquish_device";
+
     private static final String BATTERY_PLUGGED_ANY = Integer.toString(
             BatteryManager.BATTERY_PLUGGED_AC |
             BatteryManager.BATTERY_PLUGGED_USB |
@@ -673,6 +677,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         findPreference(MANAGED_SYSTEM_UPDATES_KEY).setOnPreferenceClickListener(this);
 
         findPreference(CROSS_PROFILE_CALENDAR_KEY).setOnPreferenceClickListener(this);
+        findPreference(RELINQUISH_DEVICE).setOnPreferenceClickListener(this);
 
         DpcPreference bindDeviceAdminPreference =
                 (DpcPreference) findPreference(BIND_DEVICE_ADMIN_POLICIES);
@@ -1141,6 +1146,9 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
             case SET_PROFILE_NAME_KEY:
                 showSetProfileNameDialog();
                 return true;
+            case RELINQUISH_DEVICE:
+                relinquishDevice();
+                return true;
         }
         return false;
     }
@@ -3640,6 +3648,16 @@ private int validateAffiliatedUserAfterP() {
         return NO_CUSTOM_CONSTRIANT;
     }
 
+    @TargetApi(30)
+    private void relinquishDevice() {
+        try {
+            ReflectionUtil.invoke(mDevicePolicyManager, "relinquishControl",
+                new Class<?>[]{ComponentName.class}, mAdminComponentName);
+        } catch (ReflectionIsTemporaryException e) {
+            Log.e(TAG, "Error invoking relinquishControl", e);
+        }
+    }
+
     private int validateDeviceOwnerBeforeP() {
         if (Util.SDK_INT < VERSION_CODES.P) {
             if (!mDevicePolicyManager.isDeviceOwnerApp(mPackageName)) {
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 51a96f70..fd371c90 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -29,6 +29,7 @@
             <enum name="O_MR1" value="27" />
             <enum name="P" value="28" />
             <enum name="Q" value="29" />
+            <enum name="R" value="30" />
         </attr>
 
         <!-- Constrain a preference to DO or PO admins. -->
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3972137a..1ae167c9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -139,6 +139,8 @@
     <string name="persistent_device_owner_state_hint">Test state to be restored alongside the device owner after factory reset.</string>
     <string name="set_persistent_device_owner_label">Set</string>
     <string name="clear_persistent_device_owner_label">Clear</string>
+    <!-- Corporate-owned work profile falls under device owner management, for convenience. -->
+    <string name="relinquish_device">Relinquish ownership of device</string>
 
     <!-- Strings for device reboot -->
     <string name="reboot">Reboot device</string>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 94308836..6d5c7b12 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -701,6 +701,11 @@
             android:title="@string/persistent_device_owner"
             testdpc:admin="deviceOwner"
             testdpc:minSdkVersion="O_MR1" />
+        <com.afwsamples.testdpc.common.preference.DpcPreference
+            android:key="relinquish_device"
+            android:title="@string/relinquish_device"
+            testdpc:admin="profileOwner"
+            testdpc:minSdkVersion="R" />
     </PreferenceCategory>
 
     <PreferenceCategory android:title="@string/transfer_ownership">

From 04ea91f7caf9136620a5a339be013a354e5afe70 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Fri, 1 Nov 2019 17:49:18 +0000
Subject: [PATCH 19/73] Call getPasswordComplexity on the parent profile

Previously, this API did not support explicitly querying the parent profile.
This CL will allow WP TestDPC to show the password complexity of the personal
and WP.
Screenshot of TestDPC: https://hsv.googleplex.com/4804408720228352 (WP DPC)
                       https://hsv.googleplex.com/5189846769336320
Bug: 138709470
Test: manual testing using Personal and WP TestDPC

Change-Id: Id40e5b53ff00eb47f6a67a028c472ad94d9332b3
---
 .../testdpc/policy/PolicyManagementFragment.java   | 14 ++++++++++++--
 app/src/main/res/values/strings.xml                |  1 +
 2 files changed, 13 insertions(+), 2 deletions(-)

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 78903b95..5db49f6b 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -2179,8 +2179,18 @@ private void loadPasswordComplexity() {
             return;
         }
 
-        int complexity = mDevicePolicyManager.getPasswordComplexity();
-        passwordComplexityPreference.setSummary(PASSWORD_COMPLEXITY.get(complexity));
+        String summary;
+        int complexity = PASSWORD_COMPLEXITY.get(mDevicePolicyManager.getPasswordComplexity());
+        if (Util.isManagedProfileOwner(getActivity()) && Util.SDK_INT >= Util.R_VERSION_CODE) {
+            DevicePolicyManager parentDpm
+                    = mDevicePolicyManager.getParentProfileInstance(mAdminComponentName);
+            int parentComplexity = PASSWORD_COMPLEXITY.get(parentDpm.getPasswordComplexity());
+            summary = String.format(getString(R.string.password_complexity_profile_summary),
+                    getString(parentComplexity), getString(complexity));
+        } else {
+            summary = getString(complexity);
+        }
+        passwordComplexityPreference.setSummary(summary);
     }
 
     @TargetApi(VERSION_CODES.N)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1ae167c9..d8048e8c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -939,6 +939,7 @@
     <string name="password_complexity_low">Low</string>
     <string name="password_complexity_medium">Medium</string>
     <string name="password_complexity_high">High</string>
+    <string name="password_complexity_profile_summary">Personal: %1$s, Work: %2$s</string>
 
     <string name="separate_challenge_title">Separate challenge</string>
     <string name="separate_challenge_summary">Separate challenge enabled: %s</string>

From cdcd925f0d87f2b3836fc48d15cd1aaeb221f217 Mon Sep 17 00:00:00 2001
From: Eran Messeri <eranm@google.com>
Date: Wed, 27 Nov 2019 17:11:05 +0000
Subject: [PATCH 20/73] Relinquish device via wipeData

As we've chosen to express relinquishing a device by using the wipeData
DevicePolicyManager method, switch to using that for relinquishing
device.

Calling wipeData on the parent profile instance factory resets the
device, so support that in TestDPC.

Bug: 138709470
Change-Id: Id7fc35ff6a3245f03736846e0a47f8f4c60d9463
---
 .../policy/PolicyManagementFragment.java        | 17 ++++++-----------
 app/src/main/res/values/strings.xml             |  2 +-
 app/src/main/res/xml/device_policy_header.xml   |  4 ++--
 3 files changed, 9 insertions(+), 14 deletions(-)

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 5db49f6b..688db0fd 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -390,7 +390,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
 
     private static final String SET_PRIVATE_DNS_MODE_KEY = "set_private_dns_mode";
 
-    private static final String RELINQUISH_DEVICE = "relinquish_device";
+    private static final String FACTORY_RESET_ORG_OWNED_DEVICE = "factory_reset_org_owned_device";
 
     private static final String BATTERY_PLUGGED_ANY = Integer.toString(
             BatteryManager.BATTERY_PLUGGED_AC |
@@ -677,7 +677,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         findPreference(MANAGED_SYSTEM_UPDATES_KEY).setOnPreferenceClickListener(this);
 
         findPreference(CROSS_PROFILE_CALENDAR_KEY).setOnPreferenceClickListener(this);
-        findPreference(RELINQUISH_DEVICE).setOnPreferenceClickListener(this);
+        findPreference(FACTORY_RESET_ORG_OWNED_DEVICE).setOnPreferenceClickListener(this);
 
         DpcPreference bindDeviceAdminPreference =
                 (DpcPreference) findPreference(BIND_DEVICE_ADMIN_POLICIES);
@@ -1146,8 +1146,8 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
             case SET_PROFILE_NAME_KEY:
                 showSetProfileNameDialog();
                 return true;
-            case RELINQUISH_DEVICE:
-                relinquishDevice();
+            case FACTORY_RESET_ORG_OWNED_DEVICE:
+                factoryResetOrgOwnedDevice();
                 return true;
         }
         return false;
@@ -3659,13 +3659,8 @@ private int validateAffiliatedUserAfterP() {
     }
 
     @TargetApi(30)
-    private void relinquishDevice() {
-        try {
-            ReflectionUtil.invoke(mDevicePolicyManager, "relinquishControl",
-                new Class<?>[]{ComponentName.class}, mAdminComponentName);
-        } catch (ReflectionIsTemporaryException e) {
-            Log.e(TAG, "Error invoking relinquishControl", e);
-        }
+    private void factoryResetOrgOwnedDevice() {
+        mDevicePolicyManager.getParentProfileInstance(mAdminComponentName).wipeData(/*flags=*/ 0);
     }
 
     private int validateDeviceOwnerBeforeP() {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d8048e8c..b9a6a697 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -140,7 +140,7 @@
     <string name="set_persistent_device_owner_label">Set</string>
     <string name="clear_persistent_device_owner_label">Clear</string>
     <!-- Corporate-owned work profile falls under device owner management, for convenience. -->
-    <string name="relinquish_device">Relinquish ownership of device</string>
+    <string name="factory_reset_org_owned">Factory reset Org-Owned Work Profile device</string>
 
     <!-- Strings for device reboot -->
     <string name="reboot">Reboot device</string>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 6d5c7b12..6553268e 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -702,8 +702,8 @@
             testdpc:admin="deviceOwner"
             testdpc:minSdkVersion="O_MR1" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
-            android:key="relinquish_device"
-            android:title="@string/relinquish_device"
+            android:key="factory_reset_org_owned_device"
+            android:title="@string/factory_reset_org_owned"
             testdpc:admin="profileOwner"
             testdpc:minSdkVersion="R" />
     </PreferenceCategory>

From 5d082d0bc0678d7cfa72931d9c88e308b3f852c4 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Thu, 14 Nov 2019 17:00:39 +0000
Subject: [PATCH 21/73] Replace setAutoTimeRequired and getAutoTimeRequired
 with setAutoTime and getAutoTime

* In DPM, setAutoTimeRequired and getAutoTimeRequired must be called either by
  the device owner or the profile owner (since Android 8). This CL allows TestDPC
  to call this method on the profile owner and not just the device owner.
* This CL also adds a preference to call setAutoTime and getAutoTime.
* There will be a follow-up CL to enforce setAutoTime and getAutoTime is only called
  by the device owner, profile owner of the primary user  or profile owner of an
  organization owned device.

Bug: 138709470
Test: manual testing with Personal and WP TestDPC, Clock and Settings

Change-Id: I38f62cfa4d535a1fb91d3dc47383d887359e088d
---
 .../policy/PolicyManagementFragment.java      | 64 +++++++++++++++----
 app/src/main/res/values/strings.xml           |  1 +
 app/src/main/res/xml/device_policy_header.xml |  7 +-
 3 files changed, 58 insertions(+), 14 deletions(-)

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 78903b95..01e5f6ca 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -59,14 +59,6 @@
 import android.security.KeyChain;
 import android.security.KeyChainAliasCallback;
 import android.service.notification.NotificationListenerService;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.StringRes;
-import androidx.preference.SwitchPreference;
-import androidx.core.content.FileProvider;
-import androidx.preference.EditTextPreference;
-import androidx.preference.ListPreference;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceManager;
 import android.telephony.TelephonyManager;
 import android.text.InputType;
 import android.text.TextUtils;
@@ -86,6 +78,16 @@
 import android.widget.RadioButton;
 import android.widget.RadioGroup;
 import android.widget.Toast;
+
+import androidx.annotation.RequiresApi;
+import androidx.annotation.StringRes;
+import androidx.core.content.FileProvider;
+import androidx.preference.EditTextPreference;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
+import androidx.preference.SwitchPreference;
+
 import com.afwsamples.testdpc.AddAccountActivity;
 import com.afwsamples.testdpc.BuildConfig;
 import com.afwsamples.testdpc.CrossProfileAppsFragment;
@@ -133,6 +135,7 @@
 import com.afwsamples.testdpc.profilepolicy.permission.ManageAppPermissionsFragment;
 import com.afwsamples.testdpc.transferownership.PickTransferComponentFragment;
 import com.afwsamples.testdpc.util.MainThreadExecutor;
+
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -332,6 +335,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private static final String CLEAR_GLOBAL_HTTP_PROXY_KEY = "clear_global_http_proxy";
     private static final String SET_DEVICE_ORGANIZATION_NAME_KEY = "set_device_organization_name";
     private static final String SET_AUTO_TIME_REQUIRED_KEY = "set_auto_time_required";
+    private static final String SET_AUTO_TIME_KEY = "set_auto_time";
     private static final String SET_DISABLE_ACCOUNT_MANAGEMENT_KEY
             = "set_disable_account_management";
     private static final String SET_INPUT_METHODS_KEY = "set_input_methods";
@@ -449,7 +453,8 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private SwitchPreference mEnableBackupServicePreference;
     private SwitchPreference mEnableSecurityLoggingPreference;
     private SwitchPreference mEnableNetworkLoggingPreference;
-    private SwitchPreference mSetAutoTimeRequiredPreference;
+    private DpcSwitchPreference mSetAutoTimeRequiredPreference;
+    private DpcSwitchPreference mSetAutoTimePreference;
     private DpcPreference mLogoutUserPreference;
     private DpcPreference mManageLockTaskListPreference;
     private DpcPreference mSetLockTaskFeaturesPreference;
@@ -687,9 +692,12 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
                         : R.string.require_one_po_to_bind);
         bindDeviceAdminPreference.setOnPreferenceClickListener(this);
 
-        mSetAutoTimeRequiredPreference = (SwitchPreference) findPreference(
+        mSetAutoTimeRequiredPreference = (DpcSwitchPreference) findPreference(
                 SET_AUTO_TIME_REQUIRED_KEY);
+        mSetAutoTimeRequiredPreference.addCustomConstraint(this::validateDeviceOwnerBeforeO);
         mSetAutoTimeRequiredPreference.setOnPreferenceChangeListener(this);
+        mSetAutoTimePreference = (DpcSwitchPreference) findPreference(SET_AUTO_TIME_KEY);
+        mSetAutoTimePreference.setOnPreferenceChangeListener(this);
 
         mSetDeviceOrganizationNamePreference =
         (EditTextPreference) findPreference(SET_DEVICE_ORGANIZATION_NAME_KEY);
@@ -710,6 +718,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         reloadEnableSecurityLoggingUi();
         reloadEnableNetworkLoggingUi();
         reloadSetAutoTimeRequiredUi();
+        reloadSetAutoTimeUi();
         reloadEnableLogoutUi();
         reloadAutoBrightnessUi();
     }
@@ -1331,6 +1340,16 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
                         newValue.equals(true));
                 reloadSetAutoTimeRequiredUi();
                 return true;
+            case SET_AUTO_TIME_KEY:
+                try {
+                    ReflectionUtil.invoke(mDevicePolicyManager, "setAutoTime",
+                            new Class<?>[]{ComponentName.class, boolean.class},
+                            mAdminComponentName, newValue.equals(true));
+                } catch (ReflectionIsTemporaryException e) {
+                    Log.e(TAG, "Error invoking setAutoTime", e);
+                }
+                reloadSetAutoTimeUi();
+                return true;
             case SET_DEVICE_ORGANIZATION_NAME_KEY:
                 mDevicePolicyManager.setOrganizationName(mAdminComponentName, (String) newValue);
                 mSetDeviceOrganizationNamePreference.setSummary((String) newValue);
@@ -2297,9 +2316,19 @@ private void reloadScreenCaptureDisableUi() {
 
     @TargetApi(VERSION_CODES.LOLLIPOP)
     private void reloadSetAutoTimeRequiredUi() {
-        if (mDevicePolicyManager.isDeviceOwnerApp(mPackageName)) {
-            boolean isAutoTimeRequired = mDevicePolicyManager.getAutoTimeRequired();
-            mSetAutoTimeRequiredPreference.setChecked(isAutoTimeRequired);
+        boolean isAutoTimeRequired = mDevicePolicyManager.getAutoTimeRequired();
+        mSetAutoTimeRequiredPreference.setChecked(isAutoTimeRequired);
+    }
+
+    @TargetApi(Util.R_VERSION_CODE)
+    private void reloadSetAutoTimeUi() {
+        try {
+            boolean isAutoTime = (Boolean) ReflectionUtil.invoke(mDevicePolicyManager,
+                    "getAutoTime", new Class<?>[]{ComponentName.class},
+                    mAdminComponentName);
+            mSetAutoTimePreference.setChecked(isAutoTime);
+        } catch (ReflectionIsTemporaryException e) {
+            Log.e(TAG, "Error invoking getAutoTime", e);
         }
     }
 
@@ -3658,6 +3687,15 @@ private void relinquishDevice() {
         }
     }
 
+    private int validateDeviceOwnerBeforeO() {
+        if (Util.SDK_INT < VERSION_CODES.O) {
+            if (!mDevicePolicyManager.isDeviceOwnerApp(mPackageName)) {
+                return R.string.requires_device_owner;
+            }
+        }
+        return NO_CUSTOM_CONSTRIANT;
+    }
+
     private int validateDeviceOwnerBeforeP() {
         if (Util.SDK_INT < VERSION_CODES.P) {
             if (!mDevicePolicyManager.isDeviceOwnerApp(mPackageName)) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1ae167c9..eb865549 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -124,6 +124,7 @@
     <!-- Strings for device owner management -->
     <string name="device_owner_management">Device owner management</string>
     <string name="set_auto_time_required">Set auto (network) time required</string>
+    <string name="set_auto_time">Set auto (network) time</string>
     <string name="wipe_data">Wipe data</string>
     <string name="wipe_data_title">Wipe data?</string>
     <string name="wipe_data_confirmation">Are you sure you want to wipe the data?</string>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 6d5c7b12..bc278b7e 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -643,8 +643,13 @@
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="set_auto_time_required"
             android:title="@string/set_auto_time_required"
-            testdpc:admin="deviceOwner"
+            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="L" />
+        <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
+            android:key="set_auto_time"
+            android:title="@string/set_auto_time"
+            testdpc:admin="deviceOwner|profileOwner"
+            testdpc:minSdkVersion="R" />
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="enable_security_logging"
             android:title="@string/enable_security_logging"

From 4ff42352d8f176beac71625a7aacf8a1b8868e67 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Thu, 5 Dec 2019 12:13:59 +0000
Subject: [PATCH 22/73] Call setCameraDisabled and getCameraDisabled on the
 parent profile

Previously, these APIs did not support explicitly querying the parent profile.
This CL allows the WP DPC to now call these methods, which means the WP DPC
can disable the camera device-wide and not just for the WP.

Added a new disable camera on parent toggle to TestDPC which can only be called
by a PO of an organization-owned device.

Bug: 138709470
Test: manual testing using Personal and WP TestDPC and Camera

Change-Id: Ifba278ed78b03b6fe29a85b8fa4128420dad7390
---
 .../policy/PolicyManagementFragment.java      | 36 ++++++++++++++++++-
 app/src/main/res/values/strings.xml           |  2 ++
 app/src/main/res/xml/device_policy_header.xml |  4 +++
 3 files changed, 41 insertions(+), 1 deletion(-)

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 78903b95..3b00334f 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -268,6 +268,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private static final String PASSWORD_COMPLEXITY_KEY = "password_complexity";
     private static final String SEPARATE_CHALLENGE_KEY = "separate_challenge";
     private static final String DISABLE_CAMERA_KEY = "disable_camera";
+    private static final String DISABLE_CAMERA_ON_PARENT_KEY = "disable_camera_on_parent";
     private static final String DISABLE_KEYGUARD = "disable_keyguard";
     private static final String DISABLE_METERED_DATA_KEY = "disable_metered_data";
     private static final String DISABLE_SCREEN_CAPTURE_KEY = "disable_screen_capture";
@@ -435,6 +436,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private DpcPreference mInstallExistingPackagePreference;
 
     private SwitchPreference mDisableCameraSwitchPreference;
+    private DpcSwitchPreference mDisableCameraOnParentSwitchPreference;
     private SwitchPreference mDisableScreenCaptureSwitchPreference;
     private SwitchPreference mMuteAudioSwitchPreference;
 
@@ -530,9 +532,14 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         mAffiliatedUserPreference = findPreference(AFFILIATED_USER_KEY);
         mEphemeralUserPreference = findPreference(EPHEMERAL_USER_KEY);
         mDisableCameraSwitchPreference = (SwitchPreference) findPreference(DISABLE_CAMERA_KEY);
+        mDisableCameraSwitchPreference.setOnPreferenceChangeListener(this);
+        mDisableCameraOnParentSwitchPreference = (DpcSwitchPreference)
+                findPreference(DISABLE_CAMERA_ON_PARENT_KEY);
+        mDisableCameraOnParentSwitchPreference.setOnPreferenceChangeListener(this);
+        mDisableCameraOnParentSwitchPreference
+                .setCustomConstraint(this::validateProfileOwnerOfOrganizationOwnedDevice);
         findPreference(CAPTURE_IMAGE_KEY).setOnPreferenceClickListener(this);
         findPreference(CAPTURE_VIDEO_KEY).setOnPreferenceClickListener(this);
-        mDisableCameraSwitchPreference.setOnPreferenceChangeListener(this);
         mDisableScreenCaptureSwitchPreference = (SwitchPreference) findPreference(
                 DISABLE_SCREEN_CAPTURE_KEY);
         mDisableScreenCaptureSwitchPreference.setOnPreferenceChangeListener(this);
@@ -1285,6 +1292,10 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
                 // Reload UI to verify the camera is enable / disable correctly.
                 reloadCameraDisableUi();
                 return true;
+            case DISABLE_CAMERA_ON_PARENT_KEY:
+                setCameraDisabledOnParent((Boolean) newValue);
+                reloadCameraDisableOnParentUi();
+                return true;
             case ENABLE_BACKUP_SERVICE:
                 setBackupServiceEnabled((Boolean) newValue);
                 reloadEnableBackupServiceUi();
@@ -1368,6 +1379,13 @@ private void setCameraDisabled(boolean disabled) {
         mDevicePolicyManager.setCameraDisabled(mAdminComponentName, disabled);
     }
 
+    @TargetApi(Util.R_VERSION_CODE)
+    private void setCameraDisabledOnParent(boolean disabled) {
+        DevicePolicyManager parentDpm = mDevicePolicyManager
+                .getParentProfileInstance(mAdminComponentName);
+        parentDpm.setCameraDisabled(mAdminComponentName, disabled);
+    }
+
     @TargetApi(VERSION_CODES.N)
     private void setSecurityLoggingEnabled(boolean enabled) {
         mDevicePolicyManager.setSecurityLoggingEnabled(mAdminComponentName, enabled);
@@ -2260,6 +2278,14 @@ private void reloadCameraDisableUi() {
         mDisableCameraSwitchPreference.setChecked(isCameraDisabled);
     }
 
+    @TargetApi(Util.R_VERSION_CODE)
+    private void reloadCameraDisableOnParentUi() {
+        DevicePolicyManager parentDpm
+                = mDevicePolicyManager.getParentProfileInstance(mAdminComponentName);
+        boolean isCameraDisabled = parentDpm.getCameraDisabled(mAdminComponentName);
+        mDisableCameraOnParentSwitchPreference.setChecked(isCameraDisabled);
+    }
+
     @TargetApi(VERSION_CODES.O)
     private void reloadEnableNetworkLoggingUi() {
         if (mEnableNetworkLoggingPreference.isEnabled()) {
@@ -3667,6 +3693,14 @@ private int validateDeviceOwnerBeforeP() {
         return NO_CUSTOM_CONSTRIANT;
     }
 
+    private int validateProfileOwnerOfOrganizationOwnedDevice() {
+        // TODO(b/144486887): Need to check if in COPE mode
+        if (!Util.isManagedProfileOwner(getActivity()) || Util.SDK_INT < Util.R_VERSION_CODE) {
+            return R.string.requires_profile_owner_organization_owned_device;
+        }
+        return NO_CUSTOM_CONSTRIANT;
+    }
+
     abstract class ManageLockTaskListCallback {
         public abstract void onPositiveButtonClicked(String[] lockTaskArray);
     }
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1ae167c9..a229a696 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -105,6 +105,7 @@
     <string name="secondary_user">secondary user</string>
     <string name="managed_profile">managed profile</string>
     <string name="requires_device_owner">Requires device owner</string>
+    <string name="requires_profile_owner_organization_owned_device">Requires profile owner of an organization owned device</string>
 
     <string name="not_for_parent_profile">Not applicable to parent profile</string>
     <string name="user_restricted">Disallowed by user restriction</string>
@@ -173,6 +174,7 @@
     <string name="camera_title">Camera, screen capture and audio</string>
     <string name="display_media">Display Media</string>
     <string name="disable_camera">Disable camera</string>
+    <string name="disable_camera_on_parent">Disable camera on parent</string>
     <string name="capture_image">Capture image</string>
     <string name="capture_video">Capture video</string>
     <string name="camera_app_not_found">Ensure that a camera app is enabled</string>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 6d5c7b12..73aafdbd 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -221,6 +221,10 @@
             android:key="disable_camera"
             android:title="@string/disable_camera"
             testdpc:minSdkVersion="L" />
+        <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
+            android:key="disable_camera_on_parent"
+            android:title="@string/disable_camera_on_parent"
+            testdpc:minSdkVersion="R" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="capture_image"
             android:title="@string/capture_image"

From d47fa47dd7baa21eb69d15b130aaeb4f7d2d4b14 Mon Sep 17 00:00:00 2001
From: Eran Messeri <eranm@google.com>
Date: Mon, 9 Dec 2019 18:20:56 +0000
Subject: [PATCH 23/73] Allow location control via old and new API

Add TestDPC switch preferences for the following capabilities:
* Setting location enabled using the new setLocationEnabled R API.
* Controlling the LOCATION_MODE secure setting.

Bug: 143517561
Test: Manual
Change-Id: I943c89e2b02caa60a29df3e6d677e1c90b6e3ca4
---
 .../policy/PolicyManagementFragment.java      | 53 +++++++++++++++++++
 app/src/main/res/values/strings.xml           |  2 +
 app/src/main/res/xml/device_policy_header.xml | 10 ++++
 3 files changed, 65 insertions(+)

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 0545419b..5142c339 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -45,6 +45,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.location.LocationManager;
 import android.net.ProxyInfo;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -56,6 +57,7 @@
 import android.os.UserManager;
 import android.provider.MediaStore;
 import android.provider.Settings;
+import android.provider.Settings.Secure;
 import android.security.KeyChain;
 import android.security.KeyChainAliasCallback;
 import android.service.notification.NotificationListenerService;
@@ -397,6 +399,9 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
 
     private static final String FACTORY_RESET_ORG_OWNED_DEVICE = "factory_reset_org_owned_device";
 
+    private static final String SET_LOCATION_ENABLED_KEY = "set_location_enabled";
+    private static final String SET_LOCATION_MODE_KEY = "set_location_mode";
+
     private static final String BATTERY_PLUGGED_ANY = Integer.toString(
             BatteryManager.BATTERY_PLUGGED_AC |
             BatteryManager.BATTERY_PLUGGED_USB |
@@ -473,6 +478,9 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
 
     private DpcSwitchPreference mEnableAppFeedbackNotificationsPreference;
 
+    private DpcSwitchPreference mSetLocationEnabledPreference;
+    private DpcSwitchPreference mSetLocationModePreference;
+
     private GetAccessibilityServicesTask mGetAccessibilityServicesTask = null;
     private GetInputMethodsTask mGetInputMethodsTask = null;
     private GetNotificationListenersTask mGetNotificationListenersTask = null;
@@ -710,6 +718,14 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         (EditTextPreference) findPreference(SET_DEVICE_ORGANIZATION_NAME_KEY);
         mSetDeviceOrganizationNamePreference.setOnPreferenceChangeListener(this);
 
+        mSetLocationEnabledPreference = (DpcSwitchPreference) findPreference(
+            SET_LOCATION_ENABLED_KEY);
+        mSetLocationEnabledPreference.setOnPreferenceChangeListener(this);
+
+        mSetLocationModePreference = (DpcSwitchPreference) findPreference(SET_LOCATION_MODE_KEY);
+        mSetLocationModePreference.setOnPreferenceChangeListener(this);
+
+
         onCreateSetNewPasswordWithComplexityPreference();
         constrainSpecialCasePreferences();
 
@@ -1389,6 +1405,29 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
                         R.string.app_feedback_notifications), newValue.equals(true));
                 editor.commit();
                 return true;
+            case SET_LOCATION_ENABLED_KEY:
+                try {
+                    ReflectionUtil.invoke(mDevicePolicyManager, "setLocationEnabled",
+                        new Class<?>[]{ComponentName.class, boolean.class},
+                        mAdminComponentName, newValue.equals(true));
+                } catch (ReflectionIsTemporaryException e) {
+                    Log.e(TAG, "Error invoking setLocationEnabled", e);
+                }
+                reloadLocationEnabledUi();
+                reloadLocationModeUi();
+                return true;
+            case SET_LOCATION_MODE_KEY:
+                final int locationMode;
+                if (newValue.equals(true)) {
+                    locationMode = Secure.LOCATION_MODE_HIGH_ACCURACY;
+                } else {
+                    locationMode = Secure.LOCATION_MODE_OFF;
+                }
+                mDevicePolicyManager.setSecureSetting(mAdminComponentName, Secure.LOCATION_MODE,
+                    String.format("%d", locationMode));
+                reloadLocationEnabledUi();
+                reloadLocationModeUi();
+                return true;
         }
         return false;
     }
@@ -2269,6 +2308,20 @@ private void reloadAutoBrightnessUi() {
         }
     }
 
+    @TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
+    private void reloadLocationModeUi() {
+        final String locationMode = Settings.System.getString(
+            getActivity().getContentResolver(), Secure.LOCATION_MODE);
+        mSetLocationModePreference
+            .setChecked(parseInt(locationMode, 0) != Secure.LOCATION_MODE_OFF);
+    }
+
+    @TargetApi(Util.R_VERSION_CODE)
+    private void reloadLocationEnabledUi() {
+        LocationManager locationManager = getActivity().getSystemService(LocationManager.class);
+        mSetLocationEnabledPreference.setChecked(locationManager.isLocationEnabled());
+    }
+
     static private int parseInt(String str, int defaultValue) {
         try {
             return Integer.parseInt(str);
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6eda1b81..bc107426 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -965,6 +965,8 @@
     <string name="settings_management_title">Settings management</string>
     <string name="stay_on_while_plugged_in">Keep the device on while plugged in</string>
     <string name="install_non_market_apps">Allow installs from unknown sources</string>
+    <string name="set_location_enabled">Set location enabled</string>
+    <string name="set_location_mode">Set location mode (deprecated version of set location enabled)</string>
 
     <!-- Strings for setting support messages -->
     <string name="support_messages">Support messages</string>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 32d32501..071eea64 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -625,6 +625,16 @@
             android:key="install_nonmarket_apps"
             android:title="@string/install_non_market_apps"
             testdpc:minSdkVersion="L" />
+        <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
+            android:key="set_location_enabled"
+            android:title="@string/set_location_enabled"
+            testdpc:admin="deviceOwner|profileOwner"
+            testdpc:minSdkVersion="R" />
+        <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
+            android:key="set_location_mode"
+            android:title="@string/set_location_mode"
+            testdpc:admin="deviceOwner|profileOwner"
+            testdpc:minSdkVersion="L" />
     </PreferenceCategory>
 
     <PreferenceCategory android:title="@string/support_messages">

From 502bb3f9240c1710f6ce31e52bbb9a1159859969 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Tue, 26 Nov 2019 17:13:57 +0000
Subject: [PATCH 24/73] Add new 'Set user restrictions on parent' preference to
 TestDPC

* Added new display fragment that contains the list of restrictions that
  can be set from the WP DPC.
* Added list of user restrictions that are set on the parent DPM and can
  only be set by profile owners of an organization owned device
* Added new preference to TestDPC policy management fragment
* This is part of the work to make the user restrictions APIs callable
  on the parent instance.

Bug: 138709470
Test: Manual testing with testDPC
Change-Id: I2c257ce4c66157257b530b4cb343ffb095458d87
---
 .../policy/PolicyManagementFragment.java      |  10 ++
 .../testdpc/policy/UserRestriction.java       |  26 ++++
 ...UserRestrictionsParentDisplayFragment.java | 112 ++++++++++++++++++
 app/src/main/res/values/strings.xml           |   1 +
 app/src/main/res/xml/device_policy_header.xml |   4 +
 5 files changed, 153 insertions(+)
 create mode 100644 app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java

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 5142c339..61c3c756 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -348,6 +348,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private static final String SET_PERMISSION_POLICY_KEY = "set_permission_policy";
     private static final String SET_SHORT_SUPPORT_MESSAGE_KEY = "set_short_support_message";
     private static final String SET_USER_RESTRICTIONS_KEY = "set_user_restrictions";
+    private static final String SET_USER_RESTRICTIONS_PARENT_KEY = "set_user_restrictions_parent";
     private static final String SHOW_WIFI_MAC_ADDRESS_KEY = "show_wifi_mac_address";
     private static final String START_KIOSK_MODE = "start_kiosk_mode";
     private static final String START_LOCK_TASK = "start_lock_task";
@@ -481,6 +482,8 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private DpcSwitchPreference mSetLocationEnabledPreference;
     private DpcSwitchPreference mSetLocationModePreference;
 
+    private DpcPreference mUserRestrictionsParentPreference;
+
     private GetAccessibilityServicesTask mGetAccessibilityServicesTask = null;
     private GetInputMethodsTask mGetInputMethodsTask = null;
     private GetNotificationListenersTask mGetNotificationListenersTask = null;
@@ -676,6 +679,10 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
                         : NO_CUSTOM_CONSTRIANT);
         mInstallNonMarketAppsPreference.setOnPreferenceChangeListener(this);
         findPreference(SET_USER_RESTRICTIONS_KEY).setOnPreferenceClickListener(this);
+        mUserRestrictionsParentPreference = (DpcPreference) findPreference(SET_USER_RESTRICTIONS_PARENT_KEY);
+        mUserRestrictionsParentPreference.setOnPreferenceClickListener(this);
+        mUserRestrictionsParentPreference.setCustomConstraint(this::validateProfileOwnerOfOrganizationOwnedDevice);
+
         findPreference(REBOOT_KEY).setOnPreferenceClickListener(this);
         findPreference(SET_SHORT_SUPPORT_MESSAGE_KEY).setOnPreferenceClickListener(this);
         findPreference(SET_LONG_SUPPORT_MESSAGE_KEY).setOnPreferenceClickListener(this);
@@ -1122,6 +1129,9 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
             case SET_USER_RESTRICTIONS_KEY:
                 showFragment(new UserRestrictionsDisplayFragment());
                 return true;
+            case SET_USER_RESTRICTIONS_PARENT_KEY:
+                showFragment(new UserRestrictionsParentDisplayFragment());
+                return true;
             case REBOOT_KEY:
                 reboot();
                 return true;
diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java
index 6036ceba..d6b0e7fd 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java
@@ -205,6 +205,21 @@ public UserRestriction(String key, int titleResId) {
                     R.string.disallow_config_private_dns),
     };
 
+    /**
+     * These user restrictions are set on the parent DPM and can only be set by
+     * profile owners of an organization owned device.
+     */
+    public static final UserRestriction[] PROFILE_OWNER_ORG_DEVICE_RESTRICTIONS = {
+            new UserRestriction(DISALLOW_CONFIG_DATE_TIME,
+                    R.string.disallow_config_date_time),
+            new UserRestriction(DISALLOW_CONFIG_TETHERING,
+                    R.string.disallow_config_tethering),
+            new UserRestriction(DISALLOW_DATA_ROAMING,
+                    R.string.disallow_data_roaming),
+            new UserRestriction(DISALLOW_DEBUGGING_FEATURES,
+                    R.string.disallow_debugging_features)
+    };
+
     /**
      * Setting these user restrictions only have effect on primary users.
      */
@@ -242,6 +257,17 @@ public UserRestriction(String key, int titleResId) {
             DISALLOW_USER_SWITCH
     };
 
+    /**
+     * These user restrictions are set on the parent DPM and can only be set by
+     * profile owners of an organization owned device.
+     */
+    public static final String[] PROFILE_OWNER_ORG_OWNED_RESTRICTIONS = {
+            DISALLOW_CONFIG_DATE_TIME,
+            DISALLOW_CONFIG_TETHERING,
+            DISALLOW_DATA_ROAMING,
+            DISALLOW_DEBUGGING_FEATURES
+    };
+
     /**
      * Setting these user restrictions only have effect on managed profiles.
      */
diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java
new file mode 100644
index 00000000..27736d35
--- /dev/null
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java
@@ -0,0 +1,112 @@
+package com.afwsamples.testdpc.policy;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.annotation.RequiresApi;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.afwsamples.testdpc.DeviceAdminReceiver;
+import com.afwsamples.testdpc.R;
+import com.afwsamples.testdpc.common.BaseSearchablePolicyPreferenceFragment;
+import com.afwsamples.testdpc.common.Util;
+import com.afwsamples.testdpc.common.preference.DpcPreferenceBase;
+import com.afwsamples.testdpc.common.preference.DpcSwitchPreference;
+
+public class UserRestrictionsParentDisplayFragment extends BaseSearchablePolicyPreferenceFragment
+        implements Preference.OnPreferenceChangeListener {
+    private static final String TAG = "UserRestrictionsParent";
+
+    private DevicePolicyManager mParentDevicePolicyManager;
+    private UserManager mUserManager;
+    private ComponentName mAdminComponentName;
+
+    @RequiresApi(api = Util.R_VERSION_CODE)
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        DevicePolicyManager mDevicePolicyManager = (DevicePolicyManager)
+                getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
+        mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
+        mAdminComponentName = DeviceAdminReceiver.getComponentName(getActivity());
+        mParentDevicePolicyManager = mDevicePolicyManager
+                .getParentProfileInstance(mAdminComponentName);
+
+        getActivity().getActionBar().setTitle(R.string.user_restrictions_management_title);
+    }
+
+    @Override
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+        PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
+                getPreferenceManager().getContext());
+        setPreferenceScreen(preferenceScreen);
+
+        final Context preferenceContext = getPreferenceManager().getContext();
+        for (UserRestriction restriction : UserRestriction.PROFILE_OWNER_ORG_DEVICE_RESTRICTIONS) {
+            DpcSwitchPreference preference = new DpcSwitchPreference(preferenceContext);
+            preference.setTitle(restriction.titleResId);
+            preference.setKey(restriction.key);
+            preference.setOnPreferenceChangeListener(this);
+            preferenceScreen.addPreference(preference);
+        }
+
+        updateAllUserRestrictions();
+        constrainPreferences();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        updateAllUserRestrictions();
+    }
+
+    @Override
+    public boolean isAvailable(Context context) {
+        return true;
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        String restriction = preference.getKey();
+        try {
+            if (newValue.equals(true)) {
+                mParentDevicePolicyManager.addUserRestriction(mAdminComponentName, restriction);
+            } else {
+                mParentDevicePolicyManager.clearUserRestriction(mAdminComponentName, restriction);
+            }
+            updateUserRestriction(restriction);
+            return true;
+        } catch (SecurityException e) {
+            Toast.makeText(getActivity(), R.string.user_restriction_error_msg,
+                    Toast.LENGTH_SHORT).show();
+            Log.e(TAG, "Error occurred while updating user restriction: " + restriction, e);
+            return false;
+        }
+    }
+
+    private void updateAllUserRestrictions() {
+        for (UserRestriction restriction : UserRestriction.PROFILE_OWNER_ORG_DEVICE_RESTRICTIONS) {
+            updateUserRestriction(restriction.key);
+        }
+    }
+
+    private void updateUserRestriction(String userRestriction) {
+        DpcSwitchPreference preference = (DpcSwitchPreference) findPreference(userRestriction);
+        boolean disallowed = mUserManager.hasUserRestriction(userRestriction);
+        preference.setChecked(disallowed);
+    }
+
+    private void constrainPreferences() {
+        for (String restriction : UserRestriction.PROFILE_OWNER_ORG_OWNED_RESTRICTIONS) {
+            DpcPreferenceBase pref = (DpcPreferenceBase) findPreference(restriction);
+            pref.setMinSdkVersion(Util.R_VERSION_CODE);
+        }
+    }
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bc107426..6d5bd389 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -424,6 +424,7 @@
 
     <!-- Strings for user restriction management -->
     <string name="user_restrictions_preference_title">Set user restrictions</string>
+    <string name="user_restrictions_parent_preference_title">Set user restrictions on parent</string>
     <string name="user_restrictions_management_title">User restrictions</string>
     <string name="allow_parent_profile_app_linking">Allow web links to apps of the parent</string>
     <string name="disallow_add_managed_profile">Disallow add managed profile</string>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 071eea64..81b73dba 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -613,6 +613,10 @@
             android:key="set_user_restrictions"
             android:title="@string/user_restrictions_preference_title"
             testdpc:minSdkVersion="L" />
+        <com.afwsamples.testdpc.common.preference.DpcPreference
+            android:key="set_user_restrictions_parent"
+            android:title="@string/user_restrictions_parent_preference_title"
+            testdpc:minSdkVersion="R" />
     </PreferenceCategory>
 
     <PreferenceCategory android:title="@string/settings_management_title">

From 069215a2e1160b4fcb6b27df93526159985db14e Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Tue, 10 Dec 2019 18:08:39 +0000
Subject: [PATCH 25/73] Move super.onCreate below initialization

UserRestrictionsParentDisplayFragment in TestDPC was unable to open
because super.onCreate was being called before the local variables
were initialized.

This CL moves this logic to fix UserRestrictionsParentDisplayFragment.

Bug: 138709470
Test: manual testing with TestDPC
Change-Id: I48156b2c47afba959b256bd0db743d8450c2ed52
---
 .../testdpc/policy/UserRestrictionsParentDisplayFragment.java | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java
index 27736d35..a7cc4158 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java
@@ -30,15 +30,13 @@ public class UserRestrictionsParentDisplayFragment extends BaseSearchablePolicyP
     @RequiresApi(api = Util.R_VERSION_CODE)
     @Override
     public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
         DevicePolicyManager mDevicePolicyManager = (DevicePolicyManager)
                 getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
         mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
         mAdminComponentName = DeviceAdminReceiver.getComponentName(getActivity());
         mParentDevicePolicyManager = mDevicePolicyManager
                 .getParentProfileInstance(mAdminComponentName);
-
+        super.onCreate(savedInstanceState);
         getActivity().getActionBar().setTitle(R.string.user_restrictions_management_title);
     }
 

From 54857a023017a5be5ff05f5f0baef29325e20223 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Thu, 12 Dec 2019 16:08:00 +0000
Subject: [PATCH 26/73] Add set auto time zone preference to TestDPC

* This CL also adds a preference to call setAutoTimeZone and getAutoTimeZone.
* There will be a follow-up CL to enforce setAutoTimeZone and getAutoTimeZone
  is only called by the device owner, profile owner of the primary user or
  profile owner of an organization owned device.

Bug: 138709470
Test: manual testing with Personal and WP TestDPC, Clock and Settings
Change-Id: Ib04c8ab39bcb4d2ec78b492e84f9d8bedf059026
---
 .../policy/PolicyManagementFragment.java      | 27 +++++++++++++++++++
 app/src/main/res/values/strings.xml           |  1 +
 app/src/main/res/xml/device_policy_header.xml |  5 ++++
 3 files changed, 33 insertions(+)

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 61c3c756..2af5859a 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -339,6 +339,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private static final String SET_DEVICE_ORGANIZATION_NAME_KEY = "set_device_organization_name";
     private static final String SET_AUTO_TIME_REQUIRED_KEY = "set_auto_time_required";
     private static final String SET_AUTO_TIME_KEY = "set_auto_time";
+    private static final String SET_AUTO_TIME_ZONE_KEY = "set_auto_time_zone";
     private static final String SET_DISABLE_ACCOUNT_MANAGEMENT_KEY
             = "set_disable_account_management";
     private static final String SET_INPUT_METHODS_KEY = "set_input_methods";
@@ -463,6 +464,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private SwitchPreference mEnableNetworkLoggingPreference;
     private DpcSwitchPreference mSetAutoTimeRequiredPreference;
     private DpcSwitchPreference mSetAutoTimePreference;
+    private DpcSwitchPreference mSetAutoTimeZonePreference;
     private DpcPreference mLogoutUserPreference;
     private DpcPreference mManageLockTaskListPreference;
     private DpcPreference mSetLockTaskFeaturesPreference;
@@ -720,6 +722,8 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         mSetAutoTimeRequiredPreference.setOnPreferenceChangeListener(this);
         mSetAutoTimePreference = (DpcSwitchPreference) findPreference(SET_AUTO_TIME_KEY);
         mSetAutoTimePreference.setOnPreferenceChangeListener(this);
+        mSetAutoTimeZonePreference = (DpcSwitchPreference) findPreference(SET_AUTO_TIME_ZONE_KEY);
+        mSetAutoTimeZonePreference.setOnPreferenceChangeListener(this);
 
         mSetDeviceOrganizationNamePreference =
         (EditTextPreference) findPreference(SET_DEVICE_ORGANIZATION_NAME_KEY);
@@ -749,6 +753,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         reloadEnableNetworkLoggingUi();
         reloadSetAutoTimeRequiredUi();
         reloadSetAutoTimeUi();
+        reloadSetAutoTimeZoneUi();
         reloadEnableLogoutUi();
         reloadAutoBrightnessUi();
     }
@@ -1387,6 +1392,16 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
                 }
                 reloadSetAutoTimeUi();
                 return true;
+            case SET_AUTO_TIME_ZONE_KEY:
+                try {
+                    ReflectionUtil.invoke(mDevicePolicyManager, "setAutoTimeZone",
+                            new Class<?>[]{ComponentName.class, boolean.class},
+                            mAdminComponentName, newValue.equals(true));
+                } catch (ReflectionIsTemporaryException e) {
+                    Log.e(TAG, "Error invoking setAutoTimeZone", e);
+                }
+                reloadSetAutoTimeZoneUi();
+                return true;
             case SET_DEVICE_ORGANIZATION_NAME_KEY:
                 mDevicePolicyManager.setOrganizationName(mAdminComponentName, (String) newValue);
                 mSetDeviceOrganizationNamePreference.setSummary((String) newValue);
@@ -2431,6 +2446,18 @@ private void reloadSetAutoTimeUi() {
         }
     }
 
+    @TargetApi(Util.R_VERSION_CODE)
+    private void reloadSetAutoTimeZoneUi() {
+        try {
+            boolean isAutoTimeZone = (Boolean) ReflectionUtil.invoke(mDevicePolicyManager,
+                    "getAutoTimeZone", new Class<?>[]{ComponentName.class},
+                    mAdminComponentName);
+            mSetAutoTimeZonePreference.setChecked(isAutoTimeZone);
+        } catch (ReflectionIsTemporaryException e) {
+            Log.e(TAG, "Error invoking getAutoTimeZone", e);
+        }
+    }
+
     @TargetApi(VERSION_CODES.LOLLIPOP)
     private void reloadMuteAudioUi() {
         if (mMuteAudioSwitchPreference.isEnabled()) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6d5bd389..97c063df 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -126,6 +126,7 @@
     <string name="device_owner_management">Device owner management</string>
     <string name="set_auto_time_required">Set auto (network) time required</string>
     <string name="set_auto_time">Set auto (network) time</string>
+    <string name="set_auto_time_zone">Set auto (network) time zone</string>
     <string name="wipe_data">Wipe data</string>
     <string name="wipe_data_title">Wipe data?</string>
     <string name="wipe_data_confirmation">Are you sure you want to wipe the data?</string>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 81b73dba..8be85bcc 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -668,6 +668,11 @@
             android:title="@string/set_auto_time"
             testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="R" />
+        <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
+            android:key="set_auto_time_zone"
+            android:title="@string/set_auto_time_zone"
+            testdpc:admin="deviceOwner|profileOwner"
+            testdpc:minSdkVersion="R" />
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="enable_security_logging"
             android:title="@string/enable_security_logging"

From 1997881d44e835bdd3361f4b3e1a5b9130688e0d Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Wed, 8 Jan 2020 12:58:57 +0000
Subject: [PATCH 27/73] Update 'Set user restrictions on parent' preference in
 TestDPC

Added to the list of user restrictions that are applied globally
when set on the parent profile DPM instance by the profile
owner of an organization-owned device.

Bug: 147339402
Test: Manual testing
Change-Id: I143f6baed2d615e89a9e2b2b5df715e6de6addb9
---
 .../testdpc/policy/UserRestriction.java       | 35 ++++++++++++++++++-
 app/src/main/res/values/strings.xml           |  2 ++
 2 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java
index d6b0e7fd..53bffa4b 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java
@@ -15,6 +15,7 @@
 import static android.os.UserManager.DISALLOW_CONFIG_CELL_BROADCASTS;
 import static android.os.UserManager.DISALLOW_CONFIG_CREDENTIALS;
 import static android.os.UserManager.DISALLOW_CONFIG_DATE_TIME;
+import static android.os.UserManager.DISALLOW_CONFIG_LOCALE;
 import static android.os.UserManager.DISALLOW_CONFIG_LOCATION;
 import static android.os.UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS;
 import static android.os.UserManager.DISALLOW_CONFIG_PRIVATE_DNS;
@@ -22,6 +23,8 @@
 import static android.os.UserManager.DISALLOW_CONFIG_TETHERING;
 import static android.os.UserManager.DISALLOW_CONFIG_VPN;
 import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
+import static android.os.UserManager.DISALLOW_CONTENT_CAPTURE;
+import static android.os.UserManager.DISALLOW_CONTENT_SUGGESTIONS;
 import static android.os.UserManager.DISALLOW_CREATE_WINDOWS;
 import static android.os.UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE;
 import static android.os.UserManager.DISALLOW_DATA_ROAMING;
@@ -217,7 +220,37 @@ public UserRestriction(String key, int titleResId) {
             new UserRestriction(DISALLOW_DATA_ROAMING,
                     R.string.disallow_data_roaming),
             new UserRestriction(DISALLOW_DEBUGGING_FEATURES,
-                    R.string.disallow_debugging_features)
+                    R.string.disallow_debugging_features),
+            new UserRestriction(DISALLOW_ADD_USER,
+                    R.string.disallow_add_user),
+            new UserRestriction(DISALLOW_BLUETOOTH,
+                    R.string.disallow_bluetooth),
+            new UserRestriction(DISALLOW_BLUETOOTH_SHARING,
+                    R.string.disallow_bluetooth_sharing),
+            new UserRestriction(DISALLOW_CONFIG_BLUETOOTH,
+                    R.string.disallow_config_bluetooth),
+            new UserRestriction(DISALLOW_CONFIG_CELL_BROADCASTS,
+                    R.string.disallow_config_cell_broadcasts),
+            new UserRestriction(DISALLOW_CONFIG_LOCATION,
+                    R.string.disallow_config_location),
+            new UserRestriction(DISALLOW_CONFIG_MOBILE_NETWORKS,
+                    R.string.disallow_config_mobile_networks),
+            new UserRestriction(DISALLOW_CONFIG_PRIVATE_DNS,
+                    R.string.disallow_config_private_dns),
+            new UserRestriction(DISALLOW_CONFIG_WIFI,
+                    R.string.disallow_config_wifi),
+            new UserRestriction(DISALLOW_CONTENT_CAPTURE,
+                    R.string.disallow_content_capture),
+            new UserRestriction(DISALLOW_CONTENT_SUGGESTIONS,
+                    R.string.disallow_content_suggestions),
+            new UserRestriction(DISALLOW_SAFE_BOOT,
+                    R.string.disallow_safe_boot),
+            new UserRestriction(DISALLOW_SHARE_LOCATION,
+                    R.string.disallow_share_location),
+            new UserRestriction(DISALLOW_SMS,
+                    R.string.disallow_sms),
+            new UserRestriction(DISALLOW_USB_FILE_TRANSFER,
+                    R.string.disallow_usb_file_transfer),
     };
 
     /**
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 97c063df..079fdda8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -483,6 +483,8 @@
     <string name="disallow_share_into_work_profile">Disallow sharing data into the profile</string>
     <string name="disallow_printing">Disallow printing</string>
     <string name="disallow_config_private_dns">Disallow config Private DNS</string>
+    <string name="disallow_content_capture">Disallow content capture</string>
+    <string name="disallow_content_suggestions">Disallow content suggestions</string>
     <string name="user_restriction_error_msg">Operation not allowed</string>
 
     <!-- Strings for select app -->

From 15589780b92236cf3509c5a0d947d904624cd0e3 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Mon, 20 Jan 2020 11:13:29 +0000
Subject: [PATCH 28/73] Modify setApplicationHidden and isApplicationHidden
 APIs

* Introduced new logic that allows the profile owner of an
  organization-owned device (COPE PO) to hide/unhide system
  apps in the personal profile.
* Modified both APIs to be callable on the parent profile
  instance if called by the COPE PO.
* Added new preferences to TestDPC to hide apps on the parent
  profile. To hide/unhide an app, the package name needs to
  be entered and the app must be a system app.

Bug: 147413198
Test: manual testing
Change-Id: Ieeadd4e2c6a22754fa230ea39d47d709981a6de9
---
 .../policy/PolicyManagementFragment.java      | 64 +++++++++++++++++++
 app/src/main/res/values/strings.xml           |  6 ++
 app/src/main/res/xml/device_policy_header.xml | 10 +++
 3 files changed, 80 insertions(+)

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 2af5859a..e7a0297b 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -296,6 +296,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private static final String ADD_ACCOUNT_KEY = "add_account";
     private static final String REMOVE_ACCOUNT_KEY = "remove_account";
     private static final String HIDE_APPS_KEY = "hide_apps";
+    private static final String HIDE_APPS_PARENT_KEY = "hide_apps_parent";
     private static final String INSTALL_CA_CERTIFICATE_KEY = "install_ca_certificate";
     private static final String INSTALL_KEY_CERTIFICATE_KEY = "install_key_certificate";
     private static final String INSTALL_NONMARKET_APPS_KEY
@@ -361,6 +362,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private static final String TEST_KEY_USABILITY_KEY = "test_key_usability";
 
     private static final String UNHIDE_APPS_KEY = "unhide_apps";
+    private static final String UNHIDE_APPS_PARENT_KEY = "unhide_apps_parent";
     private static final String UNSUSPEND_APPS_KEY = "unsuspend_apps";
     private static final String CLEAR_APP_DATA_KEY = "clear_app_data";
     private static final String KEEP_UNINSTALLED_PACKAGES = "keep_uninstalled_packages";
@@ -468,6 +470,8 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private DpcPreference mLogoutUserPreference;
     private DpcPreference mManageLockTaskListPreference;
     private DpcPreference mSetLockTaskFeaturesPreference;
+    private DpcPreference mUnhideAppsParentPreference;
+    private DpcPreference mHideAppsParentPreference;
 
     private DpcSwitchPreference mEnableLogoutPreference;
     private Preference mAffiliatedUserPreference;
@@ -646,7 +650,15 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         findPreference(INSTALL_APK_PACKAGE_KEY).setOnPreferenceClickListener(this);
         findPreference(UNINSTALL_PACKAGE_KEY).setOnPreferenceClickListener(this);
         findPreference(HIDE_APPS_KEY).setOnPreferenceClickListener(this);
+        mHideAppsParentPreference = (DpcPreference) findPreference(HIDE_APPS_PARENT_KEY);
+        mHideAppsParentPreference.setOnPreferenceClickListener(this);
+        mHideAppsParentPreference.setCustomConstraint(
+                this::validateProfileOwnerOfOrganizationOwnedDevice);
         findPreference(UNHIDE_APPS_KEY).setOnPreferenceClickListener(this);
+        mUnhideAppsParentPreference = (DpcPreference) findPreference(UNHIDE_APPS_PARENT_KEY);
+        mUnhideAppsParentPreference.setOnPreferenceClickListener(this);
+        mUnhideAppsParentPreference.setCustomConstraint(
+                this::validateProfileOwnerOfOrganizationOwnedDevice);
         findPreference(SUSPEND_APPS_KEY).setOnPreferenceClickListener(this);
         findPreference(UNSUSPEND_APPS_KEY).setOnPreferenceClickListener(this);
         findPreference(CLEAR_APP_DATA_KEY).setOnPreferenceClickListener(this);
@@ -997,9 +1009,15 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
             case HIDE_APPS_KEY:
                 showHideAppsPrompt(false);
                 return true;
+            case HIDE_APPS_PARENT_KEY:
+                showHideAppsOnParentPrompt(false);
+                return true;
             case UNHIDE_APPS_KEY:
                 showHideAppsPrompt(true);
                 return true;
+            case UNHIDE_APPS_PARENT_KEY:
+                showHideAppsOnParentPrompt(true);
+                return true;
             case SUSPEND_APPS_KEY:
                 showSuspendAppsPrompt(false);
                 return true;
@@ -3074,6 +3092,52 @@ public void onClick(DialogInterface dialog, int position) {
         }
     }
 
+    @RequiresApi(api = Util.R_VERSION_CODE)
+    private void showHideAppsOnParentPrompt(final boolean showHiddenApps) {
+        final int dialogTitleResId;
+        final int successResId;
+        final int failureResId;
+        final int failureSystemResId;
+        if (showHiddenApps) {
+            // showing a dialog to unhide an app
+            dialogTitleResId = R.string.unhide_apps_parent_title;
+            successResId = R.string.unhide_apps_success;
+            failureResId = R.string.unhide_apps_failure;
+            failureSystemResId = R.string.unhide_apps_system_failure;
+        } else {
+            // showing a dialog to hide an app
+            dialogTitleResId = R.string.hide_apps_parent_title;
+            successResId = R.string.hide_apps_success;
+            failureResId = R.string.hide_apps_failure;
+            failureSystemResId = R.string.hide_apps_system_failure;
+        }
+
+        View view = getActivity().getLayoutInflater().inflate(R.layout.simple_edittext, null);
+        final EditText input = view.findViewById(R.id.input);
+        input.setHint(getString(R.string.input_package_name_hints));
+
+        new AlertDialog.Builder(getActivity())
+                .setTitle(getString(dialogTitleResId))
+                .setView(view)
+                .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+                    String packageName = input.getText().toString();
+                    try {
+                        if (mDevicePolicyManager.getParentProfileInstance(mAdminComponentName)
+                                .setApplicationHidden(mAdminComponentName, packageName,
+                                        !showHiddenApps)) {
+                            showToast(successResId, packageName);
+                        } else {
+                            showToast(getString(failureResId, packageName), Toast.LENGTH_LONG);
+                        }
+                    } catch (IllegalArgumentException e) {
+                        showToast(getString(failureSystemResId, packageName), Toast.LENGTH_LONG);
+                    }
+                    dialog.dismiss();
+                })
+                .setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())
+                .show();
+    }
+
     /**
      * Shows an alert dialog which displays a list of suspended/non-suspended apps.
      */
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 97c063df..91882ecc 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -361,14 +361,20 @@
     <string name="install_apk_package_title">Install package from APK file</string>
     <string name="uninstall_packages_title">Uninstall package</string>
     <string name="hide_apps_title">Hide apps</string>
+    <string name="hide_apps_parent_title">Hide apps on parent</string>
     <string name="hide_apps_empty">All apps are hidden.</string>
     <string name="hide_apps_success">%s is now hidden.</string>
     <string name="hide_apps_failure">%s cannot be hidden, because it is an active admin or
         required by the system.</string>
+    <string name="hide_apps_system_failure">%s cannot be hidden, because the provided package
+        is not a system package.</string>
     <string name="unhide_apps_title">Unhide apps</string>
+    <string name="unhide_apps_parent_title">Unhide apps on parent</string>
     <string name="unhide_apps_empty">No apps are currently hidden.</string>
     <string name="unhide_apps_success">%s is no longer hidden.</string>
     <string name="unhide_apps_failure">%s cannot be unhidden.</string>
+    <string name="unhide_apps_system_failure">%s cannot be unhidden, because the provided package
+        is not a system package.</string>
     <string name="suspend_apps_title">Suspend apps</string>
     <string name="suspend_apps_empty">All apps are suspended.</string>
     <string name="suspend_apps_success">%s is now suspended.</string>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 8be85bcc..31ddb5d2 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -143,11 +143,21 @@
             android:title="@string/hide_apps_title"
             testdpc:minSdkVersion="L"
             testdpc:delegation="delegation-package-access" />
+        <com.afwsamples.testdpc.common.preference.DpcPreference
+            android:key="hide_apps_parent"
+            android:title="@string/hide_apps_parent_title"
+            testdpc:minSdkVersion="R"
+            testdpc:delegation="delegation-package-access" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="unhide_apps"
             android:title="@string/unhide_apps_title"
             testdpc:minSdkVersion="L"
             testdpc:delegation="delegation-package-access" />
+        <com.afwsamples.testdpc.common.preference.DpcPreference
+            android:key="unhide_apps_parent"
+            android:title="@string/unhide_apps_parent_title"
+            testdpc:minSdkVersion="R"
+            testdpc:delegation="delegation-package-access" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="suspend_apps"
             android:title="@string/suspend_apps_title"

From a3c14c7cf3cfd7658086c0687da20c689ac05ca0 Mon Sep 17 00:00:00 2001
From: Pavel Grafov <pgrafov@google.com>
Date: Tue, 21 Jan 2020 11:00:40 +0000
Subject: [PATCH 29/73] Support for personal apps suspension.

Bug: 147414651
Test: manual
Change-Id: I4623c6973c9efd88fed482bc156ae6b8fb9fd873
---
 app/src/main/AndroidManifest.xml              |  5 ++
 .../policy/PolicyManagementFragment.java      | 69 +++++++++++++++----
 app/src/main/res/values/strings.xml           |  3 +
 app/src/main/res/xml/device_policy_header.xml |  5 ++
 4 files changed, 70 insertions(+), 12 deletions(-)

diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0aed44e1..c145106e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -41,11 +41,16 @@
       <activity
             android:name=".PolicyManagementActivity"
             android:label="@string/app_name"
+            android:exported="true"
             android:windowSoftInputMode="adjustPan">
         <intent-filter>
             <action android:name="android.intent.action.MAIN"/>
             <category android:name="android.intent.category.LAUNCHER"/>
         </intent-filter>
+        <intent-filter>
+            <action android:name="android.app.action.CHECK_POLICY_COMPLIANCE"/>
+            <category android:name="android.intent.category.DEFAULT"/>
+        </intent-filter>
       </activity>
 
         <activity
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 2af5859a..da33f225 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -359,7 +359,6 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private static final String SYSTEM_UPDATE_POLICY_KEY = "system_update_policy";
     private static final String SYSTEM_UPDATE_PENDING_KEY = "system_update_pending";
     private static final String TEST_KEY_USABILITY_KEY = "test_key_usability";
-
     private static final String UNHIDE_APPS_KEY = "unhide_apps";
     private static final String UNSUSPEND_APPS_KEY = "unsuspend_apps";
     private static final String CLEAR_APP_DATA_KEY = "clear_app_data";
@@ -382,27 +381,20 @@ 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 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";
     private static final String SET_SCREEN_OFF_TIMEOUT_KEY = "set_screen_off_timeout";
-
     private static final String SET_TIME_KEY = "set_time";
     private static final String SET_TIME_ZONE_KEY = "set_time_zone";
-
     private static final String SET_PROFILE_NAME_KEY = "set_profile_name";
-
     private static final String MANAGE_OVERRIDE_APN_KEY = "manage_override_apn";
-
     private static final String MANAGED_SYSTEM_UPDATES_KEY = "managed_system_updates";
-
     private static final String SET_PRIVATE_DNS_MODE_KEY = "set_private_dns_mode";
-
     private static final String FACTORY_RESET_ORG_OWNED_DEVICE = "factory_reset_org_owned_device";
-
     private static final String SET_LOCATION_ENABLED_KEY = "set_location_enabled";
     private static final String SET_LOCATION_MODE_KEY = "set_location_mode";
+    private static final String SUSPEND_PERSONAL_APPS_KEY = "suspend_personal_apps";
 
     private static final String BATTERY_PLUGGED_ANY = Integer.toString(
             BatteryManager.BATTERY_PLUGGED_AC |
@@ -485,6 +477,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private DpcSwitchPreference mSetLocationModePreference;
 
     private DpcPreference mUserRestrictionsParentPreference;
+    private DpcSwitchPreference mSuspendPersonalApps;
 
     private GetAccessibilityServicesTask mGetAccessibilityServicesTask = null;
     private GetInputMethodsTask mGetInputMethodsTask = null;
@@ -736,6 +729,10 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         mSetLocationModePreference = (DpcSwitchPreference) findPreference(SET_LOCATION_MODE_KEY);
         mSetLocationModePreference.setOnPreferenceChangeListener(this);
 
+        mSuspendPersonalApps = (DpcSwitchPreference) findPreference(SUSPEND_PERSONAL_APPS_KEY);
+        mSuspendPersonalApps.setOnPreferenceChangeListener(this);
+        mSuspendPersonalApps.setCustomConstraint(
+                this::validateProfileOwnerOfOrganizationOwnedDevice);
 
         onCreateSetNewPasswordWithComplexityPreference();
         constrainSpecialCasePreferences();
@@ -756,6 +753,40 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         reloadSetAutoTimeZoneUi();
         reloadEnableLogoutUi();
         reloadAutoBrightnessUi();
+        reloadPersonalAppsSuspendedUi();
+    }
+
+    @TargetApi(Util.R_VERSION_CODE)
+    private void reloadPersonalAppsSuspendedUi() {
+        // TODO: nuke it when R sdk is available
+        final int PERSONAL_APPS_NOT_SUSPENDED = 0;
+        if (mSuspendPersonalApps.isEnabled()) {
+            int suspendReasons = getPersonalAppsSuspensionReasons();
+            mSuspendPersonalApps.setChecked(suspendReasons != 0);
+        }
+    }
+
+    // TODO: nuke it when R sdk is available.
+    int getPersonalAppsSuspensionReasons() {
+        try {
+            return (Integer) ReflectionUtil.invoke(mDevicePolicyManager,
+                    "getPersonalAppsSuspendedReasons", new Class<?>[]{ComponentName.class},
+                    mAdminComponentName);
+        } catch (ReflectionIsTemporaryException e) {
+            Log.e(TAG, "Error invoking getPersonalAppsSuspendedReasons", e);
+            return 0;
+        }
+    }
+
+    // TODO: nuke it when R sdk is available.
+    void setPersonalAppsSuspended(boolean suspended) {
+        try {
+            ReflectionUtil.invoke(mDevicePolicyManager, "setPersonalAppsSuspended",
+                    new Class<?>[]{ComponentName.class, boolean.class},
+                    mAdminComponentName, suspended);
+        } catch (ReflectionIsTemporaryException e) {
+            Log.e(TAG, "Error invoking setPersonalAppsSuspended", e);
+        }
     }
 
     private void onCreateSetNewPasswordWithComplexityPreference() {
@@ -1453,6 +1484,10 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
                 reloadLocationEnabledUi();
                 reloadLocationModeUi();
                 return true;
+            case SUSPEND_PERSONAL_APPS_KEY:
+                setPersonalAppsSuspended((Boolean) newValue);
+                reloadPersonalAppsSuspendedUi();
+                return true;
         }
         return false;
     }
@@ -3826,15 +3861,25 @@ private int validateDeviceOwnerBeforeP() {
         return NO_CUSTOM_CONSTRIANT;
     }
 
+    // TODO: nuke it when R sdk is available
+    private boolean isOrganizationOwnedDeviceWithManagedProfile() {
+        try {
+            return (Boolean) ReflectionUtil.invoke(
+                    mDevicePolicyManager, "isOrganizationOwnedDeviceWithManagedProfile");
+        } catch (ReflectionIsTemporaryException e) {
+            Log.e(TAG, "Error invoking isOrganizationOwnedDeviceWithManagedProfile", e);
+            return false;
+        }
+    }
+
     private int validateProfileOwnerOfOrganizationOwnedDevice() {
-        // TODO(b/144486887): Need to check if in COPE mode
-        if (!Util.isManagedProfileOwner(getActivity()) || Util.SDK_INT < Util.R_VERSION_CODE) {
+        if (Util.SDK_INT < Util.R_VERSION_CODE || !isOrganizationOwnedDeviceWithManagedProfile()) {
             return R.string.requires_profile_owner_organization_owned_device;
         }
         return NO_CUSTOM_CONSTRIANT;
     }
 
-    abstract class ManageLockTaskListCallback {
+    abstract static class ManageLockTaskListCallback {
         public abstract void onPositiveButtonClicked(String[] lockTaskArray);
     }
 }
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 079fdda8..766b0b28 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -613,6 +613,9 @@
     <!-- Backup and Restore for Device Owner -->
     <string name="enable_backup_service">Enable backup service</string>
 
+    <!-- Suspension of personal apps -->
+    <string name="suspend_personal_apps">Suspend personal apps</string>
+
     <!-- Enterprise security logging -->
     <string name="enable_security_logging">Enable security logging</string>
     <string name="request_security_logs">Request security logs</string>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 8be85bcc..a74ee37b 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -734,6 +734,11 @@
             android:title="@string/factory_reset_org_owned"
             testdpc:admin="profileOwner"
             testdpc:minSdkVersion="R" />
+        <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
+            android:key="suspend_personal_apps"
+            android:title="@string/suspend_personal_apps"
+            testdpc:admin="profileOwner"
+            testdpc:minSdkVersion="R" />
     </PreferenceCategory>
 
     <PreferenceCategory android:title="@string/transfer_ownership">

From edc4e4f080fcd94af644bf5af756d3719097f979 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Wed, 15 Jan 2020 12:01:04 +0000
Subject: [PATCH 30/73] Add new APIs to allow modification of admin configured
 networks

* Historically, only the device owner could prevent the user from modifying
  networks that have been configured by the device owner.
* In New COPE mode, the COPE profile owner should now be able to prevent the
  user from modifying networks as well as the device owner.
* This CL updates TestDPC to allow a COPE profile owner to modify the preference
  that sets whether modification of configured networks is allowed. This CL
  also updates this preference to call the new API.

Bug: 147476790
Test: manual testing with TestDPC
Change-Id: I46dd723328a94ecfbff621904f89b4a865c2eec7
---
 .../policy/PolicyManagementFragment.java      | 33 +++++++++++++++----
 app/src/main/res/xml/device_policy_header.xml |  2 +-
 2 files changed, 27 insertions(+), 8 deletions(-)

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 2af5859a..6e143717 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -373,8 +373,6 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private static final String MODIFY_WIFI_CONFIGURATION_KEY = "modify_wifi_configuration";
     private static final String TRANSFER_OWNERSHIP_KEY = "transfer_ownership_to_component";
     private static final String TAG_WIFI_CONFIG_CREATION = "wifi_config_creation";
-    private static final String WIFI_CONFIG_LOCKDOWN_ON = "1";
-    private static final String WIFI_CONFIG_LOCKDOWN_OFF = "0";
     private static final String SECURITY_PATCH_FORMAT = "yyyy-MM-dd";
     private static final String SET_NEW_PASSWORD = "set_new_password";
     private static final String SET_NEW_PASSWORD_WITH_COMPLEXITY =
@@ -486,6 +484,8 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
 
     private DpcPreference mUserRestrictionsParentPreference;
 
+    private DpcSwitchPreference mLockdownAdminConfiguredNetworksPreference;
+
     private GetAccessibilityServicesTask mGetAccessibilityServicesTask = null;
     private GetInputMethodsTask mGetInputMethodsTask = null;
     private GetNotificationListenersTask mGetNotificationListenersTask = null;
@@ -667,7 +667,9 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         findPreference(MANAGE_APP_PERMISSIONS_KEY).setOnPreferenceClickListener(this);
         findPreference(CREATE_WIFI_CONFIGURATION_KEY).setOnPreferenceClickListener(this);
         findPreference(CREATE_EAP_TLS_WIFI_CONFIGURATION_KEY).setOnPreferenceClickListener(this);
-        findPreference(WIFI_CONFIG_LOCKDOWN_ENABLE_KEY).setOnPreferenceChangeListener(this);
+        mLockdownAdminConfiguredNetworksPreference = (DpcSwitchPreference)
+                findPreference(WIFI_CONFIG_LOCKDOWN_ENABLE_KEY);
+        mLockdownAdminConfiguredNetworksPreference.setOnPreferenceChangeListener(this);
         findPreference(MODIFY_WIFI_CONFIGURATION_KEY).setOnPreferenceClickListener(this);
         findPreference(TRANSFER_OWNERSHIP_KEY).setOnPreferenceClickListener(this);
         findPreference(SHOW_WIFI_MAC_ADDRESS_KEY).setOnPreferenceClickListener(this);
@@ -1366,10 +1368,15 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
                 updateStayOnWhilePluggedInPreference();
                 return true;
             case WIFI_CONFIG_LOCKDOWN_ENABLE_KEY:
-                mDevicePolicyManager.setGlobalSetting(mAdminComponentName,
-                        Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
-                        newValue.equals(Boolean.TRUE) ?
-                                WIFI_CONFIG_LOCKDOWN_ON : WIFI_CONFIG_LOCKDOWN_OFF);
+                try {
+                    ReflectionUtil.invoke(mDevicePolicyManager,
+                            "setLockdownAdminConfiguredNetworks",
+                            new Class<?>[]{ComponentName.class, boolean.class},
+                            mAdminComponentName, newValue.equals(true));
+                } catch (ReflectionIsTemporaryException e) {
+                    Log.e(TAG, "Error invoking setLockdownAdminConfiguredNetworks", e);
+                }
+                reloadLockdownAdminConfiguredNetworksUi();
                 return true;
             case INSTALL_NONMARKET_APPS_KEY:
                 mDevicePolicyManager.setSecureSetting(mAdminComponentName,
@@ -2347,6 +2354,18 @@ private void reloadLocationEnabledUi() {
         mSetLocationEnabledPreference.setChecked(locationManager.isLocationEnabled());
     }
 
+    @TargetApi(Util.R_VERSION_CODE)
+    private void reloadLockdownAdminConfiguredNetworksUi() {
+        try {
+            boolean lockdown = (Boolean) ReflectionUtil.invoke(mDevicePolicyManager,
+                    "isLockdownAdminConfiguredNetworks",
+                    new Class<?>[]{ComponentName.class}, mAdminComponentName);
+            mLockdownAdminConfiguredNetworksPreference.setChecked(lockdown);
+        } catch (ReflectionIsTemporaryException e) {
+            Log.e(TAG, "Error invoking isLockdownAdminConfiguredNetworks", e);
+        }
+    }
+
     static private int parseInt(String str, int defaultValue) {
         try {
             return Integer.parseInt(str);
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 8be85bcc..931d7faf 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -305,7 +305,7 @@
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="enable_wifi_config_lockdown"
             android:title="@string/enable_wifi_config_lockdown"
-            testdpc:admin="deviceOwner"
+            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="M" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="modify_wifi_configuration"

From b868844f666d1b48389766468798cbddc81bb463 Mon Sep 17 00:00:00 2001
From: Pavel Grafov <pgrafov@google.com>
Date: Wed, 22 Jan 2020 16:00:36 +0000
Subject: [PATCH 31/73] Support for maximum profile time off.

Bug: 143517719
Test: manual
Change-Id: I492b9713325aa19f6f67300ba60412c0d0691e6a
---
 .../policy/PolicyManagementFragment.java      | 53 ++++++++++++++++++-
 app/src/main/res/values/strings.xml           |  3 ++
 app/src/main/res/xml/device_policy_header.xml |  8 +++
 3 files changed, 62 insertions(+), 2 deletions(-)

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 da33f225..018ae869 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -107,6 +107,7 @@
 import com.afwsamples.testdpc.common.UserArrayAdapter;
 import com.afwsamples.testdpc.common.Util;
 import com.afwsamples.testdpc.common.preference.CustomConstraint;
+import com.afwsamples.testdpc.common.preference.DpcEditTextPreference;
 import com.afwsamples.testdpc.common.preference.DpcPreference;
 import com.afwsamples.testdpc.common.preference.DpcPreferenceBase;
 import com.afwsamples.testdpc.common.preference.DpcPreferenceHelper;
@@ -395,6 +396,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private static final String SET_LOCATION_ENABLED_KEY = "set_location_enabled";
     private static final String SET_LOCATION_MODE_KEY = "set_location_mode";
     private static final String SUSPEND_PERSONAL_APPS_KEY = "suspend_personal_apps";
+    private static final String PROFILE_MAX_TIME_OFF_KEY = "profile_max_time_off";
 
     private static final String BATTERY_PLUGGED_ANY = Integer.toString(
             BatteryManager.BATTERY_PLUGGED_AC |
@@ -478,6 +480,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
 
     private DpcPreference mUserRestrictionsParentPreference;
     private DpcSwitchPreference mSuspendPersonalApps;
+    private DpcEditTextPreference mProfileMaxTimeOff;
 
     private GetAccessibilityServicesTask mGetAccessibilityServicesTask = null;
     private GetInputMethodsTask mGetInputMethodsTask = null;
@@ -734,6 +737,12 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         mSuspendPersonalApps.setCustomConstraint(
                 this::validateProfileOwnerOfOrganizationOwnedDevice);
 
+        mProfileMaxTimeOff = (DpcEditTextPreference) findPreference(PROFILE_MAX_TIME_OFF_KEY);
+        mProfileMaxTimeOff.setOnPreferenceChangeListener(this);
+        mProfileMaxTimeOff.setCustomConstraint(
+                this::validateProfileOwnerOfOrganizationOwnedDevice);
+        maybeUpdateProfileMaxTimeOff();
+
         onCreateSetNewPasswordWithComplexityPreference();
         constrainSpecialCasePreferences();
 
@@ -756,6 +765,14 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         reloadPersonalAppsSuspendedUi();
     }
 
+    private void maybeUpdateProfileMaxTimeOff() {
+        if (mProfileMaxTimeOff.isEnabled()) {
+            final String currentValueAsString = Long.toString(getManagedProfileMaximumTimeOff());
+            mProfileMaxTimeOff.setText(currentValueAsString);
+            mProfileMaxTimeOff.setSummary(currentValueAsString);
+        }
+    }
+
     @TargetApi(Util.R_VERSION_CODE)
     private void reloadPersonalAppsSuspendedUi() {
         // TODO: nuke it when R sdk is available
@@ -773,7 +790,7 @@ int getPersonalAppsSuspensionReasons() {
                     "getPersonalAppsSuspendedReasons", new Class<?>[]{ComponentName.class},
                     mAdminComponentName);
         } catch (ReflectionIsTemporaryException e) {
-            Log.e(TAG, "Error invoking getPersonalAppsSuspendedReasons", e);
+            logAndShowToast("Error invoking getPersonalAppsSuspendedReasons", e);
             return 0;
         }
     }
@@ -785,10 +802,38 @@ void setPersonalAppsSuspended(boolean suspended) {
                     new Class<?>[]{ComponentName.class, boolean.class},
                     mAdminComponentName, suspended);
         } catch (ReflectionIsTemporaryException e) {
-            Log.e(TAG, "Error invoking setPersonalAppsSuspended", e);
+            logAndShowToast("Error invoking setPersonalAppsSuspended", e);
+        }
+    }
+
+    //TODO: nuke it when R sdk is available.
+    public long getManagedProfileMaximumTimeOff() {
+        try {
+            return (Long) ReflectionUtil.invoke(mDevicePolicyManager,
+                    "getManagedProfileMaximumTimeOff", new Class<?>[]{ComponentName.class},
+                    mAdminComponentName);
+        } catch (ReflectionIsTemporaryException e) {
+            logAndShowToast("Error invoking getManagedProfileMaximumTimeOff", e);
+            return 0;
+        }
+    }
+
+    //TODO: nuke it when R sdk is available.
+    public void setManagedProfileMaximumTimeOff(long timeoutMs) {
+        try {
+            ReflectionUtil.invoke(mDevicePolicyManager, "setManagedProfileMaximumTimeOff",
+                    new Class<?>[]{ComponentName.class, long.class},
+                    mAdminComponentName, timeoutMs);
+        } catch (ReflectionIsTemporaryException e) {
+            logAndShowToast("Error invoking setManagedProfileMaximumTimeOff", e);
         }
     }
 
+    private void logAndShowToast(String message, Exception e) {
+        Log.e(TAG, message, e);
+        showToast(message + ": " + e.getMessage());
+    }
+
     private void onCreateSetNewPasswordWithComplexityPreference() {
         ListPreference complexityPref =
             (ListPreference) findPreference(SET_NEW_PASSWORD_WITH_COMPLEXITY);
@@ -1488,6 +1533,10 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
                 setPersonalAppsSuspended((Boolean) newValue);
                 reloadPersonalAppsSuspendedUi();
                 return true;
+            case PROFILE_MAX_TIME_OFF_KEY:
+                setManagedProfileMaximumTimeOff(Long.parseLong((String) newValue));
+                maybeUpdateProfileMaxTimeOff();
+                return true;
         }
         return false;
     }
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 766b0b28..ae86d4de 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -616,6 +616,9 @@
     <!-- Suspension of personal apps -->
     <string name="suspend_personal_apps">Suspend personal apps</string>
 
+    <!-- Maximum time work profile is allowed to be off -->
+    <string name="profile_max_time_off">Work profile max time off (seconds)</string>
+
     <!-- Enterprise security logging -->
     <string name="enable_security_logging">Enable security logging</string>
     <string name="request_security_logs">Request security logs</string>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index a74ee37b..063c6f98 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -739,6 +739,14 @@
             android:title="@string/suspend_personal_apps"
             testdpc:admin="profileOwner"
             testdpc:minSdkVersion="R" />
+        <com.afwsamples.testdpc.common.preference.DpcEditTextPreference
+            android:defaultValue="0"
+            android:dialogTitle="@string/profile_max_time_off"
+            android:inputType="number"
+            android:key="profile_max_time_off"
+            android:title="@string/profile_max_time_off"
+            testdpc:admin="profileOwner"
+            testdpc:minSdkVersion="R" />
     </PreferenceCategory>
 
     <PreferenceCategory android:title="@string/transfer_ownership">

From 6d11ad07ffab8065afda253dcd9dc730de5ddf6e Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Tue, 28 Jan 2020 16:49:30 +0000
Subject: [PATCH 32/73] Update 'Set user restrictions on parent' preference in
 TestDPC

Added to the list of user restrictions that are applied globally
when set on the parent profile DPM instance by the profile
owner of an organization-owned device.

Bug: 148437699
Test: Manual testing
Change-Id: I12d079d62e9a13ec7a2b5a3fa3a2548305e44ef2
---
 .../com/afwsamples/testdpc/policy/UserRestriction.java    | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java
index 53bffa4b..42ade733 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java
@@ -251,6 +251,14 @@ public UserRestriction(String key, int titleResId) {
                     R.string.disallow_sms),
             new UserRestriction(DISALLOW_USB_FILE_TRANSFER,
                     R.string.disallow_usb_file_transfer),
+            new UserRestriction(DISALLOW_AIRPLANE_MODE,
+                    R.string.disallow_airplane_mode),
+            new UserRestriction(DISALLOW_MOUNT_PHYSICAL_MEDIA,
+                    R.string.disallow_mount_physical_media),
+            new UserRestriction(DISALLOW_OUTGOING_CALLS,
+                    R.string.disallow_outgoing_calls),
+            new UserRestriction(DISALLOW_UNMUTE_MICROPHONE,
+                    R.string.disallow_unmute_microphone),
     };
 
     /**

From 03b60d9d7580b37e082a41f8a48f3069ac31ea81 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Tue, 4 Feb 2020 11:12:16 +0000
Subject: [PATCH 33/73] Modify LockScreenPolicyFragment in TestDPC

* Introduced new logic that allows the profile owner of an
  organization-owned device (COPE PO) to set the restriction
  KEYGUARD_DISABLE_SECURE_CAMERA on the parent profile.

Bug: 148656201
Test: Manual testing with TestDPC
Change-Id: Ic6c63b0b566b875420eaaa3d6195494865bdf9ca
---
 .../testdpc/policy/keyguard/LockScreenPolicyFragment.java        | 1 -
 app/src/main/res/xml/lock_screen_preferences.xml                 | 1 -
 2 files changed, 2 deletions(-)

diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/keyguard/LockScreenPolicyFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/keyguard/LockScreenPolicyFragment.java
index 25fb7568..6ae828b7 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/keyguard/LockScreenPolicyFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/keyguard/LockScreenPolicyFragment.java
@@ -80,7 +80,6 @@ abstract static class Keys {
 
         static final Set<String> NOT_APPLICABLE_TO_PARENT
                 = new HashSet<>(Arrays.asList(new String[] {
-            KEYGUARD_DISABLE_SECURE_CAMERA,
             KEYGUARD_DISABLE_SECURE_NOTIFICATIONS,
             KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS,
         }));
diff --git a/app/src/main/res/xml/lock_screen_preferences.xml b/app/src/main/res/xml/lock_screen_preferences.xml
index c7486867..52dbf14f 100644
--- a/app/src/main/res/xml/lock_screen_preferences.xml
+++ b/app/src/main/res/xml/lock_screen_preferences.xml
@@ -71,7 +71,6 @@
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="keyguard_disable_secure_camera"
             android:title="@string/keyguard_disable_secure_camera"
-            testdpc:admin="deviceOwner"
             testdpc:minSdkVersion="L" />
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="keyguard_disable_secure_notifications"

From 13ecdd5378bc68b96fbb4e5d94504bc0bf07ce2b Mon Sep 17 00:00:00 2001
From: Rubin Xu <rubinxu@google.com>
Date: Mon, 3 Feb 2020 14:28:43 +0000
Subject: [PATCH 34/73] Fix TestDPC as a delegated app

Also revert some unnecessary null guards.

Bug: 141616907
Test: builds
Change-Id: Iee7609fc1c475b432f79550cf1a64560e8e9457f
(cherry picked from commit cc29221c91c258542286dbbe197cbb924db52cd9)
---
 .../main/java/com/afwsamples/testdpc/common/Util.java    | 9 ++-------
 .../testdpc/common/preference/DpcPreferenceHelper.java   | 8 --------
 .../testdpc/policy/PolicyManagementFragment.java         | 4 +---
 3 files changed, 3 insertions(+), 18 deletions(-)

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 84c90450..5e315c5a 100644
--- a/app/src/main/java/com/afwsamples/testdpc/common/Util.java
+++ b/app/src/main/java/com/afwsamples/testdpc/common/Util.java
@@ -167,13 +167,8 @@ public static List<UserHandle> getBindDeviceAdminTargetUsers(Context context) {
             return Collections.emptyList();
         }
 
-        List<UserHandle> targetUsers = getDevicePolicyManager(context).
-            getBindDeviceAdminTargetUsers(DeviceAdminReceiver.getComponentName(context));
-        if (targetUsers != null){
-            return targetUsers;
-        }
-
-        return Collections.emptyList();
+        final DevicePolicyManager dpm = getDevicePolicyManager(context);
+        return dpm.getBindDeviceAdminTargetUsers(DeviceAdminReceiver.getComponentName(context));
     }
 
     public static void showFileViewer(PreferenceFragment fragment, int requestCode) {
diff --git a/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java b/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
index 7cc3c1ed..01f19634 100644
--- a/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
+++ b/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
@@ -263,10 +263,6 @@ private List<String> getCurrentDelegations() {
             return Collections.emptyList();
         }
 
-        if (!Util.isDeviceOwner(mContext) && !Util.isProfileOwner(mContext)) {
-            return Collections.emptyList();
-        }
-
         final DevicePolicyManager dpm =
                 (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
         final String packageName = mContext.getPackageName();
@@ -295,10 +291,6 @@ private boolean isEnabledForAdmin(@AdminKind int admin) {
     }
 
     private boolean hasDelegation(List<String> delegations) {
-        if (mDelegationConstraint == null) {
-            return false;
-        }
-
         return delegations.contains(mDelegationConstraint);
     }
 
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 85b6ba41..ecf5afc8 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -892,9 +892,7 @@ private boolean isDelegatedApp() {
         }
 
         DevicePolicyManager dpm = getActivity().getSystemService(DevicePolicyManager.class);
-        List<String> scopes = dpm.getDelegatedScopes(null, getActivity().getPackageName());
-
-        return scopes == null ? false : !scopes.isEmpty();
+        return !dpm.getDelegatedScopes(null, getActivity().getPackageName()).isEmpty();
     }
 
     @Override

From 0140961a5bb23b3b3910ac102b7b7a8f22565b16 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Fri, 7 Feb 2020 14:10:22 +0000
Subject: [PATCH 35/73] Add 'Disable screen capture on parent' preference

* Previously, setScreenCapturedDisabled and getScreenCaptureDisabled
  did not support explicitly querying the parent profile.
* This CL allows the COPE profile owner call these APIs on the parent
  profile to disable screen capture device-wide.

Bug: 149006854
Test: Manual testing with TestDPC
Change-Id: Iae742d9b524e790950b620aa49dd62e1e10d320b
---
 .../policy/PolicyManagementFragment.java      | 27 +++++++++++++++++++
 app/src/main/res/values/strings.xml           |  1 +
 app/src/main/res/xml/device_policy_header.xml |  4 +++
 3 files changed, 32 insertions(+)

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 85b6ba41..112cd36e 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -278,6 +278,8 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private static final String DISABLE_KEYGUARD = "disable_keyguard";
     private static final String DISABLE_METERED_DATA_KEY = "disable_metered_data";
     private static final String DISABLE_SCREEN_CAPTURE_KEY = "disable_screen_capture";
+    private static final String DISABLE_SCREEN_CAPTURE_ON_PARENT_KEY =
+            "disable_screen_capture_on_parent";
     private static final String DISABLE_STATUS_BAR = "disable_status_bar";
     private static final String ENABLE_BACKUP_SERVICE = "enable_backup_service";
     private static final String APP_FEEDBACK_NOTIFICATIONS = "app_feedback_notifications";
@@ -443,6 +445,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private SwitchPreference mDisableCameraSwitchPreference;
     private DpcSwitchPreference mDisableCameraOnParentSwitchPreference;
     private SwitchPreference mDisableScreenCaptureSwitchPreference;
+    private DpcSwitchPreference mDisableScreenCaptureOnParentSwitchPreference;
     private SwitchPreference mMuteAudioSwitchPreference;
 
     private DpcPreference mDisableStatusBarPreference;
@@ -561,6 +564,11 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         mDisableScreenCaptureSwitchPreference = (SwitchPreference) findPreference(
                 DISABLE_SCREEN_CAPTURE_KEY);
         mDisableScreenCaptureSwitchPreference.setOnPreferenceChangeListener(this);
+        mDisableScreenCaptureOnParentSwitchPreference = (DpcSwitchPreference) findPreference(
+                DISABLE_SCREEN_CAPTURE_ON_PARENT_KEY);
+        mDisableScreenCaptureOnParentSwitchPreference.setOnPreferenceChangeListener(this);
+        mDisableScreenCaptureOnParentSwitchPreference
+                .setCustomConstraint(this::validateProfileOwnerOfOrganizationOwnedDevice);
         mMuteAudioSwitchPreference = (SwitchPreference) findPreference(
                 MUTE_AUDIO_KEY);
         mMuteAudioSwitchPreference.setOnPreferenceChangeListener(this);
@@ -1450,6 +1458,10 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
                 // Reload UI to verify that screen capture was enabled / disabled correctly.
                 reloadScreenCaptureDisableUi();
                 return true;
+            case DISABLE_SCREEN_CAPTURE_ON_PARENT_KEY:
+                setScreenCaptureDisabledOnParent((Boolean) newValue);
+                reloadScreenCaptureDisableOnParentUi();
+                return true;
             case MUTE_AUDIO_KEY:
                 mDevicePolicyManager.setMasterVolumeMuted(mAdminComponentName,
                         (Boolean) newValue);
@@ -1605,6 +1617,13 @@ private void setScreenCaptureDisabled(boolean disabled) {
         mDevicePolicyManager.setScreenCaptureDisabled(mAdminComponentName, disabled);
     }
 
+    @TargetApi(Util.R_VERSION_CODE)
+    private void setScreenCaptureDisabledOnParent(boolean disabled) {
+        DevicePolicyManager parentDpm = mDevicePolicyManager
+                .getParentProfileInstance(mAdminComponentName);
+        parentDpm.setScreenCaptureDisabled(mAdminComponentName, disabled);
+    }
+
     @TargetApi(VERSION_CODES.O)
     private boolean isNetworkLoggingEnabled() {
         return mDevicePolicyManager.isNetworkLoggingEnabled(mAdminComponentName);
@@ -2549,6 +2568,14 @@ private void reloadScreenCaptureDisableUi() {
         mDisableScreenCaptureSwitchPreference.setChecked(isScreenCaptureDisabled);
     }
 
+    @TargetApi(Util.R_VERSION_CODE)
+    private void reloadScreenCaptureDisableOnParentUi() {
+        DevicePolicyManager parentDpm
+                = mDevicePolicyManager.getParentProfileInstance(mAdminComponentName);
+        boolean isScreenCaptureDisabled = parentDpm.getScreenCaptureDisabled(mAdminComponentName);
+        mDisableScreenCaptureOnParentSwitchPreference.setChecked(isScreenCaptureDisabled);
+    }
+
     @TargetApi(VERSION_CODES.LOLLIPOP)
     private void reloadSetAutoTimeRequiredUi() {
         boolean isAutoTimeRequired = mDevicePolicyManager.getAutoTimeRequired();
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a0bca962..0dfe1413 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -184,6 +184,7 @@
     <string name="stop_button_label">Stop</string>
     <string name="delete_button_label">Delete</string>
     <string name="disable_screen_capture">Disable screen capture</string>
+    <string name="disable_screen_capture_on_parent">Disable screen capture on parent</string>
     <string name="mute_audio">Mute audio</string>
 
     <!-- Stings for work profile management -->
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 87fd6f45..e314dbf1 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -249,6 +249,10 @@
             android:key="disable_screen_capture"
             android:title="@string/disable_screen_capture"
             testdpc:minSdkVersion="L" />
+        <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
+            android:key="disable_screen_capture_on_parent"
+            android:title="@string/disable_screen_capture_on_parent"
+            testdpc:minSdkVersion="R" />
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="mute_audio"
             android:title="@string/mute_audio"

From a2ffc83b6391a4f6e51c7948dabf8e1f65884569 Mon Sep 17 00:00:00 2001
From: Pavel Grafov <pgrafov@google.com>
Date: Wed, 12 Feb 2020 16:56:51 +0000
Subject: [PATCH 36/73] Convert seconds to millis for
 setManagedProfileMaximumTimeOff

The UI uses seconds for convenience.

Bug: 149301396
Test: manual
Change-Id: Ie4d025e5d24a740e78f6d3da294f255ce430b691
---
 .../testdpc/policy/PolicyManagementFragment.java           | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

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 ecf5afc8..62bcb26c 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -164,6 +164,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -781,7 +782,8 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
 
     private void maybeUpdateProfileMaxTimeOff() {
         if (mProfileMaxTimeOff.isEnabled()) {
-            final String currentValueAsString = Long.toString(getManagedProfileMaximumTimeOff());
+            final String currentValueAsString = Long.toString(
+                    TimeUnit.MILLISECONDS.toSeconds(getManagedProfileMaximumTimeOff()));
             mProfileMaxTimeOff.setText(currentValueAsString);
             mProfileMaxTimeOff.setSummary(currentValueAsString);
         }
@@ -1557,7 +1559,8 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
                 reloadPersonalAppsSuspendedUi();
                 return true;
             case PROFILE_MAX_TIME_OFF_KEY:
-                setManagedProfileMaximumTimeOff(Long.parseLong((String) newValue));
+                final long timeoutSec = Long.parseLong((String) newValue);
+                setManagedProfileMaximumTimeOff(TimeUnit.SECONDS.toMillis(timeoutSec));
                 maybeUpdateProfileMaxTimeOff();
                 return true;
         }

From 064269bb7906f073c020ee99b20de172019a41a7 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Fri, 14 Feb 2020 14:17:28 +0000
Subject: [PATCH 37/73] Modify LockScreenPolicyFragment in TestDPC

* Introduced new logic that allows the profile owner of an
  organization-owned device (COPE PO) to set the restriction
  KEYGUARD_DISABLE_SECURE_NOTIFICATIONS on the parent profile.

Bug: 149007069
Test: Manual testing with TestDPC
Change-Id: I542471368d981ca0ba9b69e0f9250269798c6f21
---
 .../testdpc/policy/keyguard/LockScreenPolicyFragment.java        | 1 -
 app/src/main/res/xml/lock_screen_preferences.xml                 | 1 -
 2 files changed, 2 deletions(-)

diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/keyguard/LockScreenPolicyFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/keyguard/LockScreenPolicyFragment.java
index 6ae828b7..77685694 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/keyguard/LockScreenPolicyFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/keyguard/LockScreenPolicyFragment.java
@@ -80,7 +80,6 @@ abstract static class Keys {
 
         static final Set<String> NOT_APPLICABLE_TO_PARENT
                 = new HashSet<>(Arrays.asList(new String[] {
-            KEYGUARD_DISABLE_SECURE_NOTIFICATIONS,
             KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS,
         }));
     }
diff --git a/app/src/main/res/xml/lock_screen_preferences.xml b/app/src/main/res/xml/lock_screen_preferences.xml
index 52dbf14f..61ad4214 100644
--- a/app/src/main/res/xml/lock_screen_preferences.xml
+++ b/app/src/main/res/xml/lock_screen_preferences.xml
@@ -75,7 +75,6 @@
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="keyguard_disable_secure_notifications"
             android:title="@string/keyguard_disable_secure_notifications"
-            testdpc:admin="deviceOwner"
             testdpc:minSdkVersion="L" />
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="keyguard_disable_unredacted_notifications"

From 4d2b3d4d87e17502b3219e803917a08a6a313bb5 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Tue, 18 Feb 2020 14:58:18 +0000
Subject: [PATCH 38/73] Fix lockdown admin configured networks in TestDPC

* In the framework, 'isLockdownAdminConfiguredNetworks'
  was renamed to 'hasLockdownAdminConfiguredNetworks' and
 'setLockdownAdminConfiguredNetworks' was renamed to
 'setConfiguredNetworksLockdownState'.
* Updated TestDPC to use the new names of these APIs.

Bug: 149691781
Test: Manual testing
Change-Id: I2f0911e0df48e05205419af0dd180c00767c154a
---
 .../testdpc/policy/PolicyManagementFragment.java          | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

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 fc39f9b8..c2031c15 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -1476,11 +1476,11 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
             case WIFI_CONFIG_LOCKDOWN_ENABLE_KEY:
                 try {
                     ReflectionUtil.invoke(mDevicePolicyManager,
-                            "setLockdownAdminConfiguredNetworks",
+                            "setConfiguredNetworksLockdownState",
                             new Class<?>[]{ComponentName.class, boolean.class},
                             mAdminComponentName, newValue.equals(true));
                 } catch (ReflectionIsTemporaryException e) {
-                    Log.e(TAG, "Error invoking setLockdownAdminConfiguredNetworks", e);
+                    Log.e(TAG, "Error invoking setConfiguredNetworksLockdownState", e);
                 }
                 reloadLockdownAdminConfiguredNetworksUi();
                 return true;
@@ -2480,11 +2480,11 @@ private void reloadLocationEnabledUi() {
     private void reloadLockdownAdminConfiguredNetworksUi() {
         try {
             boolean lockdown = (Boolean) ReflectionUtil.invoke(mDevicePolicyManager,
-                    "isLockdownAdminConfiguredNetworks",
+                    "hasLockdownAdminConfiguredNetworks",
                     new Class<?>[]{ComponentName.class}, mAdminComponentName);
             mLockdownAdminConfiguredNetworksPreference.setChecked(lockdown);
         } catch (ReflectionIsTemporaryException e) {
-            Log.e(TAG, "Error invoking isLockdownAdminConfiguredNetworks", e);
+            Log.e(TAG, "Error invoking hasLockdownAdminConfiguredNetworks", e);
         }
     }
 

From a0a9a935021435e8ec3d9bfc3916ef0eb40ae502 Mon Sep 17 00:00:00 2001
From: Rubin Xu <rubinxu@google.com>
Date: Wed, 19 Feb 2020 15:53:15 +0000
Subject: [PATCH 39/73] Introduce admin constraints for org-owned profile owner

* Convert some existing preferences to use this new constraint

* Allow security logging to be called by org-owned profile owner
  * Small tweaks to security logging event date printing

* Remove redundant constaints "deviceOwner|profileOwner" (which
  is the default behaviour) from preferences

* Remove unused constaints "notDeviceOwner" and "notProfileOwner"

* Update constraints for some API
  * Enabling backup service is callable by PO since Q
  * Setting time & timezone is callbele by org-owned profile owner
  * WiFi config lockdown is callable by org-owned profile owner
  * Setting system update policy is callably by org-owned profile owner
  * Installing system update is callable by org-owned profile owner
  * Setting organization name is callable by PO and DO
  * Setting lockscreen message is callable by org-owned profile owner
  * Changing location mode is callable by DO only
  * Setting auto time and timezone is callable by org-owned profile owner
  * Factory resetting device is callable by org-owned profile owner

* Document custom constraints for some preferences

Bug: 149916080
Test: manual
Change-Id: Ic7bc08405a586de2d675ffa3b3e392ea63385d43
---
 .../preference/DpcPreferenceHelper.java       | 32 +++++--
 .../policy/PolicyManagementFragment.java      | 41 ++++-----
 .../testdpc/policy/SecurityLogsFragment.java  | 10 ++-
 app/src/main/res/values/attrs.xml             | 13 +--
 app/src/main/res/values/strings.xml           |  4 +-
 app/src/main/res/xml/device_policy_header.xml | 84 +++++++++----------
 .../main/res/xml/lock_screen_preferences.xml  |  2 +-
 .../xml/lock_task_features_preferences.xml    |  6 --
 8 files changed, 102 insertions(+), 90 deletions(-)

diff --git a/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java b/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
index 01f19634..fe0c1224 100644
--- a/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
+++ b/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
@@ -29,6 +29,8 @@
 import android.view.View;
 import android.widget.TextView;
 import com.afwsamples.testdpc.R;
+import com.afwsamples.testdpc.common.ReflectionUtil;
+import com.afwsamples.testdpc.common.ReflectionUtil.ReflectionIsTemporaryException;
 import com.afwsamples.testdpc.common.Util;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -47,15 +49,18 @@
  */
 public class DpcPreferenceHelper {
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true, value = {ADMIN_NONE, ADMIN_DEVICE_OWNER, ADMIN_PROFILE_OWNER})
+    @IntDef(flag = true, value = {ADMIN_NONE, ADMIN_DEVICE_OWNER, ADMIN_BYOD_PROFILE_OWNER,
+        ADMIN_ORG_OWNED_PROFILE_OWNER})
     public @interface AdminKind {}
     public static final int ADMIN_NONE = 0x1;
     public static final int ADMIN_DEVICE_OWNER = 0x2;
-    public static final int ADMIN_PROFILE_OWNER = 0x4;
-    public static final int ADMIN_ANY = ADMIN_NONE | ADMIN_DEVICE_OWNER | ADMIN_PROFILE_OWNER;
+    public static final int ADMIN_BYOD_PROFILE_OWNER = 0x4;
+    public static final int ADMIN_ORG_OWNED_PROFILE_OWNER = 0x8;
+    public static final int ADMIN_PROFILE_OWNER =
+        ADMIN_BYOD_PROFILE_OWNER | ADMIN_ORG_OWNED_PROFILE_OWNER;
+    public static final int ADMIN_ANY =
+        ADMIN_NONE | ADMIN_DEVICE_OWNER | ADMIN_PROFILE_OWNER | ADMIN_ORG_OWNED_PROFILE_OWNER;
     public static final int ADMIN_NOT_NONE = ADMIN_ANY & ~ADMIN_NONE;
-    public static final int ADMIN_NOT_DEVICE_OWNER = ADMIN_ANY & ~ADMIN_DEVICE_OWNER;
-    public static final int ADMIN_NOT_PROFILE_OWNER = ADMIN_ANY & ~ADMIN_PROFILE_OWNER;
     public static final @AdminKind int ADMIN_DEFAULT = ADMIN_NOT_NONE;
     public static final int NO_CUSTOM_CONSTRIANT = 0;
 
@@ -252,6 +257,16 @@ private int getCurrentAdmin() {
         if (dpm.isDeviceOwnerApp(packageName)) {
             return ADMIN_DEVICE_OWNER;
         }
+        Boolean orgOwned;
+        try {
+            orgOwned = (Boolean) ReflectionUtil.invoke(dpm,
+                "isOrganizationOwnedDeviceWithManagedProfile");
+        } catch (ReflectionIsTemporaryException e) {
+            orgOwned = false;
+        }
+        if (orgOwned) {
+            return ADMIN_ORG_OWNED_PROFILE_OWNER;
+        }
         if (dpm.isProfileOwnerApp(packageName)) {
             return ADMIN_PROFILE_OWNER;
         }
@@ -304,7 +319,12 @@ private String getAdminConstraintSummary() {
         if (isEnabledForAdmin(ADMIN_DEVICE_OWNER)) {
             admins.add(mContext.getString(R.string.device_owner));
         }
-        if (isEnabledForAdmin(ADMIN_PROFILE_OWNER)) {
+        // Only add the org-owned profile message if the constraint is specific to org-owned profile
+        // and not all profile types, to reduce verbosity of the message.
+        if (isEnabledForAdmin(ADMIN_ORG_OWNED_PROFILE_OWNER) &&
+            !isEnabledForAdmin(ADMIN_PROFILE_OWNER)) {
+            admins.add(mContext.getString(R.string.org_owned_profile_owner));
+        } else if (isEnabledForAdmin(ADMIN_PROFILE_OWNER)) {
             admins.add(mContext.getString(R.string.profile_owner));
         }
         if (!TextUtils.isEmpty(mDelegationConstraint)) {
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 c2031c15..34929f9d 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -457,7 +457,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private SwitchPreference mStayOnWhilePluggedInSwitchPreference;
     private DpcSwitchPreference mInstallNonMarketAppsPreference;
 
-    private SwitchPreference mEnableBackupServicePreference;
+    private DpcSwitchPreference mEnableBackupServicePreference;
     private SwitchPreference mEnableSecurityLoggingPreference;
     private SwitchPreference mEnableNetworkLoggingPreference;
     private DpcSwitchPreference mSetAutoTimeRequiredPreference;
@@ -558,8 +558,6 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         mDisableCameraOnParentSwitchPreference = (DpcSwitchPreference)
                 findPreference(DISABLE_CAMERA_ON_PARENT_KEY);
         mDisableCameraOnParentSwitchPreference.setOnPreferenceChangeListener(this);
-        mDisableCameraOnParentSwitchPreference
-                .setCustomConstraint(this::validateProfileOwnerOfOrganizationOwnedDevice);
         findPreference(CAPTURE_IMAGE_KEY).setOnPreferenceClickListener(this);
         findPreference(CAPTURE_VIDEO_KEY).setOnPreferenceClickListener(this);
         mDisableScreenCaptureSwitchPreference = (SwitchPreference) findPreference(
@@ -568,8 +566,6 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         mDisableScreenCaptureOnParentSwitchPreference = (DpcSwitchPreference) findPreference(
                 DISABLE_SCREEN_CAPTURE_ON_PARENT_KEY);
         mDisableScreenCaptureOnParentSwitchPreference.setOnPreferenceChangeListener(this);
-        mDisableScreenCaptureOnParentSwitchPreference
-                .setCustomConstraint(this::validateProfileOwnerOfOrganizationOwnedDevice);
         mMuteAudioSwitchPreference = (SwitchPreference) findPreference(
                 MUTE_AUDIO_KEY);
         mMuteAudioSwitchPreference.setOnPreferenceChangeListener(this);
@@ -608,8 +604,10 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         findPreference(WIPE_DATA_KEY).setOnPreferenceClickListener(this);
         findPreference(PERSISTENT_DEVICE_OWNER_KEY).setOnPreferenceClickListener(this);
         findPreference(REMOVE_DEVICE_OWNER_KEY).setOnPreferenceClickListener(this);
-        mEnableBackupServicePreference = (SwitchPreference) findPreference(ENABLE_BACKUP_SERVICE);
+        mEnableBackupServicePreference = (DpcSwitchPreference) findPreference(
+            ENABLE_BACKUP_SERVICE);
         mEnableBackupServicePreference.setOnPreferenceChangeListener(this);
+        mEnableBackupServicePreference.setCustomConstraint(this::validateDeviceOwnerBeforeQ);
         findPreference(REQUEST_BUGREPORT_KEY).setOnPreferenceClickListener(this);
         mEnableSecurityLoggingPreference =
             (SwitchPreference) findPreference(ENABLE_SECURITY_LOGGING);
@@ -657,13 +655,9 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         findPreference(HIDE_APPS_KEY).setOnPreferenceClickListener(this);
         mHideAppsParentPreference = (DpcPreference) findPreference(HIDE_APPS_PARENT_KEY);
         mHideAppsParentPreference.setOnPreferenceClickListener(this);
-        mHideAppsParentPreference.setCustomConstraint(
-                this::validateProfileOwnerOfOrganizationOwnedDevice);
         findPreference(UNHIDE_APPS_KEY).setOnPreferenceClickListener(this);
         mUnhideAppsParentPreference = (DpcPreference) findPreference(UNHIDE_APPS_PARENT_KEY);
         mUnhideAppsParentPreference.setOnPreferenceClickListener(this);
-        mUnhideAppsParentPreference.setCustomConstraint(
-                this::validateProfileOwnerOfOrganizationOwnedDevice);
         findPreference(SUSPEND_APPS_KEY).setOnPreferenceClickListener(this);
         findPreference(UNSUSPEND_APPS_KEY).setOnPreferenceClickListener(this);
         findPreference(CLEAR_APP_DATA_KEY).setOnPreferenceClickListener(this);
@@ -702,7 +696,6 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         findPreference(SET_USER_RESTRICTIONS_KEY).setOnPreferenceClickListener(this);
         mUserRestrictionsParentPreference = (DpcPreference) findPreference(SET_USER_RESTRICTIONS_PARENT_KEY);
         mUserRestrictionsParentPreference.setOnPreferenceClickListener(this);
-        mUserRestrictionsParentPreference.setCustomConstraint(this::validateProfileOwnerOfOrganizationOwnedDevice);
 
         findPreference(REBOOT_KEY).setOnPreferenceClickListener(this);
         findPreference(SET_SHORT_SUPPORT_MESSAGE_KEY).setOnPreferenceClickListener(this);
@@ -757,13 +750,9 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
 
         mSuspendPersonalApps = (DpcSwitchPreference) findPreference(SUSPEND_PERSONAL_APPS_KEY);
         mSuspendPersonalApps.setOnPreferenceChangeListener(this);
-        mSuspendPersonalApps.setCustomConstraint(
-                this::validateProfileOwnerOfOrganizationOwnedDevice);
 
         mProfileMaxTimeOff = (DpcEditTextPreference) findPreference(PROFILE_MAX_TIME_OFF_KEY);
         mProfileMaxTimeOff.setOnPreferenceChangeListener(this);
-        mProfileMaxTimeOff.setCustomConstraint(
-                this::validateProfileOwnerOfOrganizationOwnedDevice);
         maybeUpdateProfileMaxTimeOff();
 
         onCreateSetNewPasswordWithComplexityPreference();
@@ -2359,7 +2348,11 @@ private void loadAppFeedbackNotifications() {
     private void loadAppStatus() {
         final @StringRes int appStatusStringId;
         if (mDevicePolicyManager.isProfileOwnerApp(mPackageName)) {
-            appStatusStringId = R.string.this_is_a_profile_owner;
+            if (isOrganizationOwnedDeviceWithManagedProfile()) {
+                appStatusStringId = R.string.this_is_an_org_owned_profile_owner;
+            } else {
+                appStatusStringId = R.string.this_is_a_profile_owner;
+            }
         } else if (mDevicePolicyManager.isDeviceOwnerApp(mPackageName)) {
             appStatusStringId = R.string.this_is_a_device_owner;
         } else if (isDelegatedApp()) {
@@ -4021,6 +4014,15 @@ private int validateDeviceOwnerBeforeP() {
         return NO_CUSTOM_CONSTRIANT;
     }
 
+    private int validateDeviceOwnerBeforeQ() {
+        if (Util.SDK_INT < VERSION_CODES.Q) {
+            if (!mDevicePolicyManager.isDeviceOwnerApp(mPackageName)) {
+                return R.string.requires_device_owner;
+            }
+        }
+        return NO_CUSTOM_CONSTRIANT;
+    }
+
     // TODO: nuke it when R sdk is available
     private boolean isOrganizationOwnedDeviceWithManagedProfile() {
         try {
@@ -4032,13 +4034,6 @@ private boolean isOrganizationOwnedDeviceWithManagedProfile() {
         }
     }
 
-    private int validateProfileOwnerOfOrganizationOwnedDevice() {
-        if (Util.SDK_INT < Util.R_VERSION_CODE || !isOrganizationOwnedDeviceWithManagedProfile()) {
-            return R.string.requires_profile_owner_organization_owned_device;
-        }
-        return NO_CUSTOM_CONSTRIANT;
-    }
-
     abstract static class ManageLockTaskListCallback {
         public abstract void onPositiveButtonClicked(String[] lockTaskArray);
     }
diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/SecurityLogsFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/SecurityLogsFragment.java
index d08a5087..4c0b7d68 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/SecurityLogsFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/SecurityLogsFragment.java
@@ -32,6 +32,7 @@
 import com.afwsamples.testdpc.DeviceAdminReceiver;
 import com.afwsamples.testdpc.R;
 import com.afwsamples.testdpc.common.Util;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
@@ -96,15 +97,16 @@ private void processEvents(List<SecurityEvent> logs) {
                     : R.string.failed_to_retrieve_security_logs);
             mAdapter.add(message);
         } else {
+            SimpleDateFormat formatter = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
             Log.d(TAG, "Incoming logs size: " + logs.size());
             for (SecurityEvent event : logs) {
                 StringBuilder sb = new StringBuilder();
-                sb.append(getStringEventTagFromId(event.getTag()));
                 if (Util.SDK_INT >= VERSION_CODES.P) {
-                    sb.append(" (id: " + getEventId(event) + ")");
+                    sb.append(getEventId(event) + ": ");
                 }
-                sb.append(" (").append(new Date(TimeUnit.NANOSECONDS.toMillis(
-                        event.getTimeNanos()))).append("): ");
+                sb.append(getStringEventTagFromId(event.getTag()));
+                sb.append(" (").append(formatter.format(new Date(TimeUnit.NANOSECONDS.toMillis(
+                        event.getTimeNanos())))).append("): ");
                 printData(sb, event.getData());
                 mAdapter.add(sb.toString());
             }
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index fd371c90..58369bad 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -36,13 +36,14 @@
         <attr name="admin">
             <flag name="none" value="0x1" />
             <flag name="deviceOwner" value="0x2" />
-            <flag name="profileOwner" value="0x4" />
+            <!-- A regular non-org-owned managed profile owner. -->
+            <flag name="byodProfileOwner" value="0x4" />
+            <flag name="orgOwnedProfileOwner" value="0x8" />
 
-            <flag name="notNone" value="0x6" /> <!-- default -->
-            <flag name="notDeviceOwner" value="0x5" />
-            <flag name="notProfileOwner" value="0x3" />
-
-            <flag name="any" value="0x7" />
+            <!-- A regular or org-owned managed profile owner. -->
+            <flag name="profileOwner" value="0xC" />
+            <flag name="notNone" value="0xE" /> <!-- default -->
+            <flag name="any" value="0xF" />
         </attr>
 
         <!-- Constrain a preference to a delegated scope. -->
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0dfe1413..61a9467d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -100,6 +100,7 @@
     <string name="requires_delimiter">,\u0020</string>
     <string name="requires_or">\u0020or\u0020</string>
     <string name="device_owner">device owner</string>
+    <string name="org_owned_profile_owner">organization-owned profile owner</string>
     <string name="profile_owner">profile owner</string>
     <string name="primary_user">primary user</string>
     <string name="secondary_user">secondary user</string>
@@ -115,6 +116,7 @@
 
     <string name="this_is_a_device_owner">This app is the device owner.</string>
     <string name="this_is_a_profile_owner">This app is a profile owner.</string>
+    <string name="this_is_an_org_owned_profile_owner">This app is a profile owner of an organization-owned device.</string>
     <string name="this_is_a_delegated_app">This app has delegated permissions.</string>
     <string name="this_is_not_an_admin">This app is not an admin.</string>
 
@@ -316,7 +318,7 @@
     <string name="wifi_cancel">Cancel</string>
     <string name="wifi_config_success">WI-FI configuration saved.</string>
     <string name="wifi_config_fail">Failed to save the WI-FI configuration.</string>
-    <string name="enable_wifi_config_lockdown">DO created WI-FI configs are modifiable only by DO.</string>
+    <string name="enable_wifi_config_lockdown">DO created WI-FI configs are modifiable only by DO</string>
     <string name="modify_wifi_configuration">Modify WI-FI configuration</string>
     <string name="show_wifi_mac_address">Show Wi-Fi MAC address</string>
     <string name="show_wifi_mac_address_title">Wi-Fi MAC address</string>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index e314dbf1..62ce1bd3 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -40,12 +40,12 @@
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="set_time"
             android:title="@string/set_time"
-            testdpc:admin="deviceOwner"
+            testdpc:admin="deviceOwner|orgOwnedProfileOwner"
             testdpc:minSdkVersion="P" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="set_time_zone"
             android:title="@string/set_time_zone"
-            testdpc:admin="deviceOwner"
+            testdpc:admin="deviceOwner|orgOwnedProfileOwner"
             testdpc:minSdkVersion="P" />
     </PreferenceCategory>
 
@@ -65,7 +65,6 @@
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="set_profile_name"
             android:title="@string/set_profile_name"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="L" />
     </PreferenceCategory>
 
@@ -81,7 +80,6 @@
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="cross_profile_calendar"
             android:title="@string/cross_profile_calendar"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="Q" />
     </PreferenceCategory>
 
@@ -101,7 +99,6 @@
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="remove_account"
             android:title="@string/remove_account"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="P" />
     </PreferenceCategory>
 
@@ -121,6 +118,7 @@
             android:title="@string/enable_system_apps_by_intent"
             testdpc:minSdkVersion="L"
             testdpc:delegation="delegation-enable-system-app" />
+        <!-- custom constraint: validateAffiliatedUserAfterP -->
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="install_existing_packages"
             android:title="@string/install_existing_packages_title"
@@ -147,7 +145,7 @@
             android:key="hide_apps_parent"
             android:title="@string/hide_apps_parent_title"
             testdpc:minSdkVersion="R"
-            testdpc:delegation="delegation-package-access" />
+            testdpc:admin="orgOwnedProfileOwner" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="unhide_apps"
             android:title="@string/unhide_apps_title"
@@ -157,7 +155,7 @@
             android:key="unhide_apps_parent"
             android:title="@string/unhide_apps_parent_title"
             testdpc:minSdkVersion="R"
-            testdpc:delegation="delegation-package-access" />
+            testdpc:admin="orgOwnedProfileOwner" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="suspend_apps"
             android:title="@string/suspend_apps_title"
@@ -186,12 +184,10 @@
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="disable_metered_data"
             android:title="@string/metered_data_restriction"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="P" />
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="app_feedback_notifications"
             android:title="@string/app_feedback_notifications"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="N" />
     </PreferenceCategory>
 
@@ -199,12 +195,10 @@
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="app_restrictions_managing_package"
             android:title="@string/app_restrictions_managing_package"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="L" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="manage_cert_installer"
             android:title="@string/manage_cert_installer"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="M" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="generic_delegation"
@@ -234,6 +228,7 @@
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="disable_camera_on_parent"
             android:title="@string/disable_camera_on_parent"
+            testdpc:admin="orgOwnedProfileOwner"
             testdpc:minSdkVersion="R" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="capture_image"
@@ -252,6 +247,7 @@
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="disable_screen_capture_on_parent"
             android:title="@string/disable_screen_capture_on_parent"
+            testdpc:admin="orgOwnedProfileOwner"
             testdpc:minSdkVersion="R" />
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="mute_audio"
@@ -319,7 +315,7 @@
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="enable_wifi_config_lockdown"
             android:title="@string/enable_wifi_config_lockdown"
-            testdpc:admin="deviceOwner|profileOwner"
+            testdpc:admin="deviceOwner|orgOwnedProfileOwner"
             testdpc:minSdkVersion="M" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="modify_wifi_configuration"
@@ -380,6 +376,7 @@
             android:key="password_constraints"
             android:title="@string/password_constraints"
             testdpc:minSdkVersion="L" />
+        <!-- custom constraint: ClearConstraintsSinceN -->
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:dialogTitle="@string/reset_password"
             android:inputType="textPassword"
@@ -409,35 +406,31 @@
     </PreferenceCategory>
 
     <PreferenceCategory android:title="@string/lock_task_title">
+        <!-- custom constraint: validateAffiliatedUserAfterP -->
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="manage_lock_task"
             android:title="@string/manage_lock_task"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="L" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="check_lock_task_permitted"
             android:title="@string/check_lock_task_permitted"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="L" />
+        <!-- custom constraint: validateAffiliatedUserAfterP -->
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="set_lock_task_features"
             android:title="@string/set_lock_task_features_title"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="P" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="start_lock_task"
             android:title="@string/start_lock_task"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="M" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="relaunch_in_lock_task"
             android:title="@string/relaunch_in_lock_task_title"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="P" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="stop_lock_task"
             android:title="@string/stop_lock_task"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="M" />
     </PreferenceCategory>
 
@@ -450,6 +443,7 @@
     </PreferenceCategory>
 
     <PreferenceCategory android:title="@string/bind_device_admin_category_title">
+        <!-- custom constraint: bindDeviceAdminTargetUsers == 1 -->
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="bind_device_admin_policies"
             android:title="@string/bind_device_admin_policy_title"
@@ -498,28 +492,32 @@
     </PreferenceCategory>
 
     <PreferenceCategory android:title="@string/single_use_devices">
+        <!-- custom constraint: validateAffiliatedUserAfterP -->
+        <!-- custom constraint: validateDeviceOwnerBeforeP -->
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="disable_status_bar"
             android:title="@string/disable_status_bar"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="M"
             testdpc:user="notManagedProfile" />
+        <!-- custom constraint: validateAffiliatedUserAfterP -->
+        <!-- custom constraint: validateDeviceOwnerBeforeP -->
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="reenable_status_bar"
             android:title="@string/reenable_status_bar"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="M"
             testdpc:user="notManagedProfile" />
+        <!-- custom constraint: validateAffiliatedUserAfterP -->
+        <!-- custom constraint: validateDeviceOwnerBeforeP -->
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="disable_keyguard"
             android:title="@string/disable_keyguard"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="M"
             testdpc:user="notManagedProfile" />
+        <!-- custom constraint: validateAffiliatedUserAfterP -->
+        <!-- custom constraint: validateDeviceOwnerBeforeP -->
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="reenable_keyguard"
             android:title="@string/reenable_keyguard"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="M"
             testdpc:user="notManagedProfile" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
@@ -534,16 +532,15 @@
             android:key="system_update_policy"
             android:title="@string/system_update_policy"
             testdpc:minSdkVersion="M"
-            testdpc:user="primaryUser" />
+            testdpc:admin="deviceOwner|orgOwnedProfileOwner" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="system_update_pending"
             android:title="@string/system_update_pending"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="O" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="managed_system_updates"
             android:title="@string/install_update"
-            testdpc:admin="deviceOwner"
+            testdpc:admin="deviceOwner|orgOwnedProfileOwner"
             testdpc:minSdkVersion="Q" />
     </PreferenceCategory>
 
@@ -584,12 +581,12 @@
             testdpc:admin="deviceOwner"
             testdpc:minSdkVersion="P"
             testdpc:user="primaryUser" />
+        <!-- custom constraint: validateAffiliatedUserAfterP -->
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="logout_user"
             android:title="@string/logout_user"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="P"
-            testdpc:user="secondaryUser" />
+            testdpc:user="secondaryUser" /> 
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="enable_logout"
             android:title="@string/enable_logout"
@@ -605,7 +602,6 @@
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="set_affiliation_ids"
             android:title="@string/manage_affiliation_ids"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="O"
             testdpc:user="any" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
@@ -617,7 +613,6 @@
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="ephemeral_user"
             android:title="@string/ephemeral_user"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="P"
             testdpc:user="any" />
     </PreferenceCategory>
@@ -630,6 +625,7 @@
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="set_user_restrictions_parent"
             android:title="@string/user_restrictions_parent_preference_title"
+            testdpc:admin="orgOwnedProfileOwner"
             testdpc:minSdkVersion="R" />
     </PreferenceCategory>
 
@@ -639,6 +635,7 @@
             android:title="@string/stay_on_while_plugged_in"
             testdpc:admin="deviceOwner"
             testdpc:minSdkVersion="M" />
+        <!-- custom constraint: DISALLOW_INSTALL_UNKNOWN_SOURCES -->
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="install_nonmarket_apps"
             android:title="@string/install_non_market_apps"
@@ -646,12 +643,12 @@
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="set_location_enabled"
             android:title="@string/set_location_enabled"
-            testdpc:admin="deviceOwner|profileOwner"
+            testdpc:admin="deviceOwner"
             testdpc:minSdkVersion="R" />
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="set_location_mode"
             android:title="@string/set_location_mode"
-            testdpc:admin="deviceOwner|profileOwner"
+            testdpc:admin="deviceOwner"
             testdpc:minSdkVersion="L" />
     </PreferenceCategory>
 
@@ -670,37 +667,38 @@
         <com.afwsamples.testdpc.common.preference.DpcEditTextPreference
             android:key="set_device_organization_name"
             android:title="@string/set_organization_name"
-            testdpc:admin="deviceOwner"
             testdpc:minSdkVersion="O" />
+        <!-- custom constraint: validateDeviceOwnerBeforeO -->
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="set_auto_time_required"
             android:title="@string/set_auto_time_required"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="L" />
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="set_auto_time"
             android:title="@string/set_auto_time"
-            testdpc:admin="deviceOwner|profileOwner"
+            testdpc:admin="deviceOwner|orgOwnedProfileOwner"
             testdpc:minSdkVersion="R" />
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="set_auto_time_zone"
             android:title="@string/set_auto_time_zone"
-            testdpc:admin="deviceOwner|profileOwner"
+            testdpc:admin="deviceOwner|orgOwnedProfileOwner"
             testdpc:minSdkVersion="R" />
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="enable_security_logging"
             android:title="@string/enable_security_logging"
-            testdpc:admin="deviceOwner"
+            testdpc:admin="deviceOwner|orgOwnedProfileOwner"
             testdpc:minSdkVersion="N" />
+        <!-- custom constraint: securityLoggingChecker -->
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="request_security_logs"
             android:title="@string/request_security_logs"
-            testdpc:admin="deviceOwner"
+            testdpc:admin="deviceOwner|orgOwnedProfileOwner"
             testdpc:minSdkVersion="N" />
+        <!-- custom constraint: securityLoggingChecker -->
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="request_pre_reboot_security_logs"
             android:title="@string/request_pre_reboot_security_logs"
-            testdpc:admin="deviceOwner"
+            testdpc:admin="deviceOwner|orgOwnedProfileOwner"
             testdpc:minSdkVersion="N" />
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="enable_network_logging"
@@ -708,6 +706,7 @@
             testdpc:admin="deviceOwner"
             testdpc:delegation="delegation-network-logging"
             testdpc:minSdkVersion="O" />
+        <!-- custom constraint: networkLoggingEnabled -->
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="request_network_logs"
             android:title="@string/request_network_logs"
@@ -719,10 +718,10 @@
             android:title="@string/request_bugreport"
             testdpc:admin="deviceOwner"
             testdpc:minSdkVersion="N" />
+        <!-- custom constraint: validateDeviceOwnerBeforeQ -->
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="enable_backup_service"
             android:title="@string/enable_backup_service"
-            testdpc:admin="deviceOwner"
             testdpc:minSdkVersion="O" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="wipe_data"
@@ -746,12 +745,12 @@
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="factory_reset_org_owned_device"
             android:title="@string/factory_reset_org_owned"
-            testdpc:admin="profileOwner"
+            testdpc:admin="orgOwnedProfileOwner"
             testdpc:minSdkVersion="R" />
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="suspend_personal_apps"
             android:title="@string/suspend_personal_apps"
-            testdpc:admin="profileOwner"
+            testdpc:admin="orgOwnedProfileOwner"
             testdpc:minSdkVersion="R" />
         <com.afwsamples.testdpc.common.preference.DpcEditTextPreference
             android:defaultValue="0"
@@ -759,7 +758,7 @@
             android:inputType="number"
             android:key="profile_max_time_off"
             android:title="@string/profile_max_time_off"
-            testdpc:admin="profileOwner"
+            testdpc:admin="orgOwnedProfileOwner"
             testdpc:minSdkVersion="R" />
     </PreferenceCategory>
 
@@ -767,7 +766,6 @@
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="transfer_ownership_to_component"
             android:title="@string/transfer_ownership_to_component"
-            testdpc:admin="deviceOwner|profileOwner"
             testdpc:minSdkVersion="P" />
     </PreferenceCategory>
 
diff --git a/app/src/main/res/xml/lock_screen_preferences.xml b/app/src/main/res/xml/lock_screen_preferences.xml
index 61ad4214..06577924 100644
--- a/app/src/main/res/xml/lock_screen_preferences.xml
+++ b/app/src/main/res/xml/lock_screen_preferences.xml
@@ -23,7 +23,7 @@
             android:inputType="text"
             android:key="key_lock_screen_message"
             android:title="@string/lock_screen_message"
-            testdpc:admin="deviceOwner"
+            testdpc:admin="deviceOwner|orgOwnedProfileOwner"
             testdpc:minSdkVersion="N" />
     </PreferenceCategory>
 
diff --git a/app/src/main/res/xml/lock_task_features_preferences.xml b/app/src/main/res/xml/lock_task_features_preferences.xml
index 0123c465..54d7c945 100644
--- a/app/src/main/res/xml/lock_task_features_preferences.xml
+++ b/app/src/main/res/xml/lock_task_features_preferences.xml
@@ -24,7 +24,6 @@
         android:title="@string/lock_task_feature_system_info"
         android:persistent="false"
         android:defaultValue="false"
-        testdpc:admin="deviceOwner|profileOwner"
         testdpc:minSdkVersion="P" />
 
     <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
@@ -32,7 +31,6 @@
         android:title="@string/lock_task_feature_notifications"
         android:persistent="false"
         android:defaultValue="false"
-        testdpc:admin="deviceOwner|profileOwner"
         testdpc:minSdkVersion="P" />
 
     <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
@@ -40,7 +38,6 @@
         android:title="@string/lock_task_feature_home"
         android:persistent="false"
         android:defaultValue="false"
-        testdpc:admin="deviceOwner|profileOwner"
         testdpc:minSdkVersion="P" />
 
     <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
@@ -48,7 +45,6 @@
         android:title="@string/lock_task_feature_overview"
         android:persistent="false"
         android:defaultValue="false"
-        testdpc:admin="deviceOwner|profileOwner"
         testdpc:minSdkVersion="P" />
 
     <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
@@ -56,7 +52,6 @@
         android:title="@string/lock_task_feature_global_actions"
         android:persistent="false"
         android:defaultValue="false"
-        testdpc:admin="deviceOwner|profileOwner"
         testdpc:minSdkVersion="P" />
 
     <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
@@ -64,7 +59,6 @@
         android:title="@string/lock_task_feature_keyguard"
         android:persistent="false"
         android:defaultValue="false"
-        testdpc:admin="deviceOwner|profileOwner"
         testdpc:minSdkVersion="P" />
 
 </PreferenceScreen>
\ No newline at end of file

From 93ab9aae6ae2f73a5605ab0da1547fc80b9bc3c8 Mon Sep 17 00:00:00 2001
From: Rubin Xu <rubinxu@google.com>
Date: Fri, 28 Feb 2020 14:56:29 +0000
Subject: [PATCH 40/73] Enable 'Show Wi-Fi MAC address' for COPE

Bug: 150200079
Test: manual
Change-Id: I476eb058cacf64280b932f0064dd98d8e71ca62b
---
 app/src/main/res/xml/device_policy_header.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 62ce1bd3..b3b80f50 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -326,7 +326,7 @@
             android:key="show_wifi_mac_address"
             android:title="@string/show_wifi_mac_address"
             testdpc:minSdkVersion="N"
-            testdpc:user="primaryUser" />
+            testdpc:admin="deviceOwner|orgOwnedProfileOwner" />
     </PreferenceCategory>
 
     <PreferenceCategory android:title="@string/input_methods_title">

From c6ccd00a133e25e80be2952c3c27b88f04f5ccdc Mon Sep 17 00:00:00 2001
From: Rubin Xu <rubinxu@google.com>
Date: Fri, 28 Feb 2020 14:57:32 +0000
Subject: [PATCH 41/73] Fix COPE admin constraint bug

TestDPC is only a COPE PO if the device is in COPE mode plus
TestDPC itself is PO.

Bug: 150121247
Test: manual
Change-Id: I7c6469a7b4eb2be53be4f0928412374a0422104a
---
 .../testdpc/common/preference/DpcPreferenceHelper.java   | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java b/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
index fe0c1224..08900272 100644
--- a/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
+++ b/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
@@ -264,11 +264,12 @@ private int getCurrentAdmin() {
         } catch (ReflectionIsTemporaryException e) {
             orgOwned = false;
         }
-        if (orgOwned) {
-            return ADMIN_ORG_OWNED_PROFILE_OWNER;
-        }
         if (dpm.isProfileOwnerApp(packageName)) {
-            return ADMIN_PROFILE_OWNER;
+            if (orgOwned) {
+                return ADMIN_ORG_OWNED_PROFILE_OWNER;
+            } else {
+                return ADMIN_PROFILE_OWNER;
+            }
         }
         return ADMIN_NONE;
     }

From 845a66fdaa032373908da00a3c19c159de07f8bd Mon Sep 17 00:00:00 2001
From: Rubin Xu <rubinxu@google.com>
Date: Fri, 28 Feb 2020 14:59:51 +0000
Subject: [PATCH 42/73] Set auto time and timezone using new R APIs

COPE PO can only use the new APIs to set auto time and timezone.
Fall back to the legacy global settings (if running on Q and before)

Bug: 150121247
Test: manual
Change-Id: I288a61da0b8f39565f2871b547dacff0bda236d5
---
 .../policy/PolicyManagementFragment.java      | 33 ++++++++++++++++---
 1 file changed, 29 insertions(+), 4 deletions(-)

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 34929f9d..15ca095a 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -1265,13 +1265,11 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
                 return true;
             case SET_TIME_KEY:
                 // Disable auto time before we could set time manually.
-                mDevicePolicyManager.setGlobalSetting(mAdminComponentName,
-                        Settings.Global.AUTO_TIME, "0");
+                setAutoTimeEnabled(false);
                 showSetTimeDialog();
                 return true;
             case SET_TIME_ZONE_KEY:
-                mDevicePolicyManager.setGlobalSetting(mAdminComponentName,
-                        Settings.Global.AUTO_TIME_ZONE, "0");
+                setAutoTimeZoneEnabled(false);
                 showSetTimeZoneDialog();
                 return true;
             case MANAGE_OVERRIDE_APN_KEY:
@@ -4037,4 +4035,31 @@ private boolean isOrganizationOwnedDeviceWithManagedProfile() {
     abstract static class ManageLockTaskListCallback {
         public abstract void onPositiveButtonClicked(String[] lockTaskArray);
     }
+
+    private void setAutoTimeEnabled(boolean enabled) {
+        try {
+            ReflectionUtil.invoke(mDevicePolicyManager, "setAutoTime",
+                new Class<?>[]{ComponentName.class, boolean.class},
+                mAdminComponentName, enabled);
+        } catch (ReflectionIsTemporaryException e) {
+            Log.e(TAG, "Error invoking setAutoTime", e);
+            // Prior to R, auto time is controlled by a global setting
+            mDevicePolicyManager.setGlobalSetting(mAdminComponentName,
+                Settings.Global.AUTO_TIME, enabled ? "1" : "0");
+        }
+    }
+
+    private void setAutoTimeZoneEnabled(boolean enabled) {
+        try {
+            ReflectionUtil.invoke(mDevicePolicyManager, "setAutoTimeZone",
+                new Class<?>[]{ComponentName.class, boolean.class},
+                mAdminComponentName, enabled);
+        } catch (ReflectionIsTemporaryException e) {
+            Log.e(TAG, "Error invoking setAutoTimeZone", e);
+            // Prior to R, auto timezone is controlled by a global setting
+            mDevicePolicyManager.setGlobalSetting(mAdminComponentName,
+                Settings.Global.AUTO_TIME_ZONE, enabled ? "1" : "0");
+        }
+
+    }
 }

From 15dd06342d8b7594bdf7a853d8ce094d8fffe153 Mon Sep 17 00:00:00 2001
From: Rubin Xu <rubinxu@google.com>
Date: Wed, 4 Mar 2020 11:54:11 +0000
Subject: [PATCH 43/73] Enables Wifi network once it is added

Otherwise the framework will not automatically connect to the
newly-added network.

Bug: 150195416
Test: TestDPC -> Wi-Fi management -> Create Wi-Fi configuration,
      Enter details of an available Wi-Fi network and verify
      the device automatically connects to it after save.
Change-Id: I342cfcffa7dd1d5e7621f25ca6f7c52e53a75a9e
---
 .../policy/wifimanagement/WifiConfigUtil.java | 47 ++++++++++++++-----
 1 file changed, 34 insertions(+), 13 deletions(-)

diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtil.java b/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtil.java
index fdf62638..c5032143 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtil.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/wifimanagement/WifiConfigUtil.java
@@ -22,6 +22,7 @@
 import android.os.Build;
 
 public class WifiConfigUtil {
+  private static final int INVALID_NETWORK_ID = -1;
 
   /**
    * Save or replace the WiFi configuration.
@@ -33,23 +34,37 @@ public class WifiConfigUtil {
   public static boolean saveWifiConfiguration(
       Context context, WifiConfiguration wifiConfiguration) {
     WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-    return wifiConfiguration.networkId == -1
-        ? addWifiNetwork(wifiManager, wifiConfiguration)
-        : updateWifiNetwork(wifiManager, wifiConfiguration);
+    final int networkId;
+    if (wifiConfiguration.networkId == INVALID_NETWORK_ID) {
+      networkId = addWifiNetwork(wifiManager, wifiConfiguration);
+    } else {
+      networkId = updateWifiNetwork(wifiManager, wifiConfiguration);
+    }
+    if (networkId == INVALID_NETWORK_ID) {
+      return false;
+    }
+    wifiManager.enableNetwork(networkId, /* disableOthers= */ false);
+    return true;
   }
 
-  private static boolean addWifiNetwork(
+  /**
+   * Adds a new Wifi configuration, returning the configuration's networkId, or
+   * {@link #INVALID_NETWORK_ID} if the operation fails.
+   */
+  private static int addWifiNetwork(
       WifiManager wifiManager, WifiConfiguration wifiConfiguration) {
     // WifiManager APIs are marked as deprecated but still explicitly supported for DPCs.
     int networkId = wifiManager.addNetwork(wifiConfiguration);
-    if (networkId == -1) {
-      return false;
+    if (networkId == INVALID_NETWORK_ID) {
+      return INVALID_NETWORK_ID;
     }
     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
       // Saving the configuration is required pre-O.
-      return saveAddedWifiConfiguration(wifiManager, networkId);
+      if (!saveAddedWifiConfiguration(wifiManager, networkId)) {
+        return INVALID_NETWORK_ID;
+      }
     }
-    return true;
+    return networkId;
   }
 
   private static boolean saveAddedWifiConfiguration(WifiManager wifiManager, int networkId) {
@@ -61,18 +76,24 @@ private static boolean saveAddedWifiConfiguration(WifiManager wifiManager, int n
     return true;
   }
 
-  private static boolean updateWifiNetwork(
+  /**
+   * Saves an existing Wifi configuration, returning the configuration's networkId, or
+   * {@link #INVALID_NETWORK_ID} if the operation fails.
+   */
+  private static int updateWifiNetwork(
       WifiManager wifiManager, WifiConfiguration wifiConfiguration) {
     // WifiManager APIs are marked as deprecated but still explicitly supported for DPCs.
     int networkId = wifiManager.updateNetwork(wifiConfiguration);
-    if (networkId == -1) {
-      return false;
+    if (networkId == INVALID_NETWORK_ID) {
+      return INVALID_NETWORK_ID;
     }
     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
       // Saving the configuration is required pre-O.
-      return saveUpdatedWifiConfiguration(wifiManager);
+      if (!saveUpdatedWifiConfiguration(wifiManager)) {
+        return INVALID_NETWORK_ID;
+      }
     }
-    return true;
+    return networkId;
   }
 
   private static boolean saveUpdatedWifiConfiguration(WifiManager wifiManager) {

From 3d2bce4d9c461e9332ce149729e0df8b4301aa62 Mon Sep 17 00:00:00 2001
From: Rubin Xu <rubinxu@google.com>
Date: Wed, 11 Mar 2020 17:43:30 +0000
Subject: [PATCH 44/73] Add toggle for Common Criteria mode

Bug: 151226692
Test: builds
Change-Id: I56a04e8052ac85a5ec16bc3c821f598c2fd1caa3
---
 .../policy/PolicyManagementFragment.java      | 34 +++++++++++++++++++
 app/src/main/res/values/strings.xml           |  2 ++
 app/src/main/res/xml/device_policy_header.xml |  5 +++
 3 files changed, 41 insertions(+)

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 15ca095a..65295b8e 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -400,6 +400,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private static final String SET_LOCATION_MODE_KEY = "set_location_mode";
     private static final String SUSPEND_PERSONAL_APPS_KEY = "suspend_personal_apps";
     private static final String PROFILE_MAX_TIME_OFF_KEY = "profile_max_time_off";
+    private static final String COMMON_CRITERIA_MODE_KEY = "common_criteria_mode";
 
     private static final String BATTERY_PLUGGED_ANY = Integer.toString(
             BatteryManager.BATTERY_PLUGGED_AC |
@@ -458,6 +459,7 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private DpcSwitchPreference mInstallNonMarketAppsPreference;
 
     private DpcSwitchPreference mEnableBackupServicePreference;
+    private DpcSwitchPreference mCommonCriteriaModePreference;
     private SwitchPreference mEnableSecurityLoggingPreference;
     private SwitchPreference mEnableNetworkLoggingPreference;
     private DpcSwitchPreference mSetAutoTimeRequiredPreference;
@@ -608,6 +610,8 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
             ENABLE_BACKUP_SERVICE);
         mEnableBackupServicePreference.setOnPreferenceChangeListener(this);
         mEnableBackupServicePreference.setCustomConstraint(this::validateDeviceOwnerBeforeQ);
+        mCommonCriteriaModePreference = (DpcSwitchPreference) findPreference(
+            COMMON_CRITERIA_MODE_KEY);
         findPreference(REQUEST_BUGREPORT_KEY).setOnPreferenceClickListener(this);
         mEnableSecurityLoggingPreference =
             (SwitchPreference) findPreference(ENABLE_SECURITY_LOGGING);
@@ -1431,6 +1435,9 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
                 setBackupServiceEnabled((Boolean) newValue);
                 reloadEnableBackupServiceUi();
                 return true;
+            case COMMON_CRITERIA_MODE_KEY:
+                setCommonCriteriaModeEnabled((Boolean) newValue);
+                reloadCommonCriteriaModeUi();
             case ENABLE_SECURITY_LOGGING:
                 setSecurityLoggingEnabled((Boolean) newValue);
                 reloadEnableSecurityLoggingUi();
@@ -1588,6 +1595,18 @@ private void setBackupServiceEnabled(boolean enabled) {
         mDevicePolicyManager.setBackupServiceEnabled(mAdminComponentName, enabled);
     }
 
+    //@TargetApi(VERSION_CODES.R)
+    private void setCommonCriteriaModeEnabled(boolean enabled) {
+        try {
+            ReflectionUtil.invoke(mDevicePolicyManager,
+                "setCommonCriteriaModeEnabled",
+                new Class<?>[]{ComponentName.class, boolean.class},
+                mAdminComponentName, enabled);
+        } catch (ReflectionIsTemporaryException e) {
+            Log.e(TAG, "Error invoking setCommonCriteriaModeEnabled", e);
+        }
+    }
+
     @TargetApi(VERSION_CODES.M)
     private void setKeyGuardDisabled(boolean disabled) {
         if (!mDevicePolicyManager.setKeyguardDisabled(mAdminComponentName, disabled)) {
@@ -2549,7 +2568,22 @@ private void reloadEnableSecurityLoggingUi() {
     private void reloadEnableBackupServiceUi() {
         if (mEnableBackupServicePreference.isEnabled()) {
             mEnableBackupServicePreference.setChecked(mDevicePolicyManager.isBackupServiceEnabled(
+                mAdminComponentName));
+        }
+    }
+
+    //@TargetApi(VERSION_CODES.R)
+    private void reloadCommonCriteriaModeUi() {
+        if (mCommonCriteriaModePreference.isEnabled()) {
+            try {
+                mCommonCriteriaModePreference.setChecked(
+                    (Boolean) ReflectionUtil.invoke(mDevicePolicyManager,
+                    "isCommonCriteriaModeEnabled",
+                        new Class<?>[]{ComponentName.class},
                     mAdminComponentName));
+            } catch (ReflectionIsTemporaryException e) {
+                Log.e(TAG, "Error invoking isCommonCriteriaModeEnabled", e);
+            }
         }
     }
 
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 61a9467d..72dd00b9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -622,6 +622,8 @@
     <!-- Backup and Restore for Device Owner -->
     <string name="enable_backup_service">Enable backup service</string>
 
+    <string name="common_criteria_mode">Enable Common Criteria mode</string>
+
     <!-- Suspension of personal apps -->
     <string name="suspend_personal_apps">Suspend personal apps</string>
 
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index b3b80f50..0c7b43aa 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -723,6 +723,11 @@
             android:key="enable_backup_service"
             android:title="@string/enable_backup_service"
             testdpc:minSdkVersion="O" />
+        <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
+            android:key="common_criteria_mode"
+            android:title="@string/common_criteria_mode"
+            testdpc:admin="deviceOwner|orgOwnedProfileOwner"
+            testdpc:minSdkVersion="R" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="wipe_data"
             android:title="@string/wipe_data"

From 0c62812b14f6a12f083b4d3f0dde188ebf6871e2 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Fri, 13 Mar 2020 17:13:35 +0000
Subject: [PATCH 45/73] Rename auto time APIs in TestDPC

Bug: 150958098
Test: manual testing
Change-Id: Icfef3a01a86984a7638f1426ea092b01bc13e904
---
 .../testdpc/policy/PolicyManagementFragment.java | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

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 15ca095a..00e4a95b 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -2578,11 +2578,11 @@ private void reloadSetAutoTimeRequiredUi() {
     private void reloadSetAutoTimeUi() {
         try {
             boolean isAutoTime = (Boolean) ReflectionUtil.invoke(mDevicePolicyManager,
-                    "getAutoTime", new Class<?>[]{ComponentName.class},
+                    "getAutoTimeEnabled", new Class<?>[]{ComponentName.class},
                     mAdminComponentName);
             mSetAutoTimePreference.setChecked(isAutoTime);
         } catch (ReflectionIsTemporaryException e) {
-            Log.e(TAG, "Error invoking getAutoTime", e);
+            Log.e(TAG, "Error invoking getAutoTimeEnabled", e);
         }
     }
 
@@ -2590,11 +2590,11 @@ private void reloadSetAutoTimeUi() {
     private void reloadSetAutoTimeZoneUi() {
         try {
             boolean isAutoTimeZone = (Boolean) ReflectionUtil.invoke(mDevicePolicyManager,
-                    "getAutoTimeZone", new Class<?>[]{ComponentName.class},
+                    "getAutoTimeZoneEnabled", new Class<?>[]{ComponentName.class},
                     mAdminComponentName);
             mSetAutoTimeZonePreference.setChecked(isAutoTimeZone);
         } catch (ReflectionIsTemporaryException e) {
-            Log.e(TAG, "Error invoking getAutoTimeZone", e);
+            Log.e(TAG, "Error invoking getAutoTimeZoneEnabled", e);
         }
     }
 
@@ -4038,11 +4038,11 @@ abstract static class ManageLockTaskListCallback {
 
     private void setAutoTimeEnabled(boolean enabled) {
         try {
-            ReflectionUtil.invoke(mDevicePolicyManager, "setAutoTime",
+            ReflectionUtil.invoke(mDevicePolicyManager, "setAutoTimeEnabled",
                 new Class<?>[]{ComponentName.class, boolean.class},
                 mAdminComponentName, enabled);
         } catch (ReflectionIsTemporaryException e) {
-            Log.e(TAG, "Error invoking setAutoTime", e);
+            Log.e(TAG, "Error invoking setAutoTimeEnabled", e);
             // Prior to R, auto time is controlled by a global setting
             mDevicePolicyManager.setGlobalSetting(mAdminComponentName,
                 Settings.Global.AUTO_TIME, enabled ? "1" : "0");
@@ -4051,11 +4051,11 @@ private void setAutoTimeEnabled(boolean enabled) {
 
     private void setAutoTimeZoneEnabled(boolean enabled) {
         try {
-            ReflectionUtil.invoke(mDevicePolicyManager, "setAutoTimeZone",
+            ReflectionUtil.invoke(mDevicePolicyManager, "setAutoTimeZoneEnabled",
                 new Class<?>[]{ComponentName.class, boolean.class},
                 mAdminComponentName, enabled);
         } catch (ReflectionIsTemporaryException e) {
-            Log.e(TAG, "Error invoking setAutoTimeZone", e);
+            Log.e(TAG, "Error invoking setAutoTimeZoneEnabled", e);
             // Prior to R, auto timezone is controlled by a global setting
             mDevicePolicyManager.setGlobalSetting(mAdminComponentName,
                 Settings.Global.AUTO_TIME_ZONE, enabled ? "1" : "0");

From 3cca843d900bd76ec86ef49f1af49b974157443c Mon Sep 17 00:00:00 2001
From: Rubin Xu <rubinxu@google.com>
Date: Mon, 23 Mar 2020 15:23:30 +0000
Subject: [PATCH 46/73] Upgrade TestDPC SDK to R

Bug: 152203329
Test: ./gradlew assemble
Change-Id: I28fa76e3ff2d54e9daf739f2c4205a4c18bc4989
---
 app/build.gradle                                      |  2 +-
 .../testdpc/policy/PolicyManagementFragment.java      | 11 ++---------
 2 files changed, 3 insertions(+), 10 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index 9bc58b88..e050bc53 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -13,7 +13,7 @@ ext {
 }
 
 android {
-    compileSdkVersion 'android-29'
+    compileSdkVersion 'android-R'
     buildToolsVersion "28.0.0"
 
     defaultConfig {
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 78b02503..4ab2a5c8 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -2575,15 +2575,8 @@ private void reloadEnableBackupServiceUi() {
     //@TargetApi(VERSION_CODES.R)
     private void reloadCommonCriteriaModeUi() {
         if (mCommonCriteriaModePreference.isEnabled()) {
-            try {
-                mCommonCriteriaModePreference.setChecked(
-                    (Boolean) ReflectionUtil.invoke(mDevicePolicyManager,
-                    "isCommonCriteriaModeEnabled",
-                        new Class<?>[]{ComponentName.class},
-                    mAdminComponentName));
-            } catch (ReflectionIsTemporaryException e) {
-                Log.e(TAG, "Error invoking isCommonCriteriaModeEnabled", e);
-            }
+            mCommonCriteriaModePreference.setChecked(
+                mDevicePolicyManager.isCommonCriteriaModeEnabled(mAdminComponentName));
         }
     }
 

From cf793e652872a7c2ae4f9f9477274c7b7d55183f Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Tue, 24 Mar 2020 09:58:00 +0000
Subject: [PATCH 47/73] Add factory reset protection policy to TestDPC

* Added new Fragment to create, edit and clear
  the factory reset protection policy.

Bug: 152031262
Test: manual testing
Change-Id: Iebf6a248b1e3970825cf2c15993341e84e94b455
---
 .../FactoryResetProtectionPolicyFragment.java | 211 ++++++++++++++++++
 .../policy/PolicyManagementFragment.java      |   6 +
 .../factory_reset_protection_policy.xml       | 103 +++++++++
 ...actory_reset_protection_policy_account.xml |  41 ++++
 app/src/main/res/values/strings.xml           |  10 +
 app/src/main/res/xml/device_policy_header.xml |   5 +
 6 files changed, 376 insertions(+)
 create mode 100644 app/src/main/java/com/afwsamples/testdpc/policy/FactoryResetProtectionPolicyFragment.java
 create mode 100644 app/src/main/res/layout/factory_reset_protection_policy.xml
 create mode 100644 app/src/main/res/layout/factory_reset_protection_policy_account.xml

diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/FactoryResetProtectionPolicyFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/FactoryResetProtectionPolicyFragment.java
new file mode 100644
index 00000000..0a330acb
--- /dev/null
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/FactoryResetProtectionPolicyFragment.java
@@ -0,0 +1,211 @@
+/*
+ * 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.policy;
+
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.FactoryResetProtectionPolicy;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.RequiresApi;
+import androidx.annotation.StringRes;
+
+import com.afwsamples.testdpc.DeviceAdminReceiver;
+import com.afwsamples.testdpc.R;
+import com.afwsamples.testdpc.common.Util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FactoryResetProtectionPolicyFragment extends Fragment
+        implements AdapterView.OnItemSelectedListener, View.OnClickListener {
+
+    private static final int DISABLED = 0;
+    private static final int ENABLED = 1;
+
+    private DevicePolicyManager mDevicePolicyManager;
+    private ComponentName mAdminComponentName;
+
+    private List<String> mAccounts = new ArrayList<>();
+    private boolean mEnabled;
+
+    private FrpAccountsAdapter mAccountsAdapter;
+    private Spinner mFrpEnabledSpinner;
+
+    @RequiresApi(api = Util.R_VERSION_CODE)
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        mDevicePolicyManager = (DevicePolicyManager)
+                getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
+        mAdminComponentName = DeviceAdminReceiver.getComponentName(getActivity());
+        super.onCreate(savedInstanceState);
+        getActivity().getActionBar().setTitle(R.string.factory_reset_protection_policy);
+    }
+
+    @RequiresApi(api = Util.R_VERSION_CODE)
+    @Override
+    public View onCreateView(LayoutInflater inflater, final ViewGroup container,
+                             Bundle savedInstanceState) {
+        super.onCreateView(inflater, container, savedInstanceState);
+        View view = inflater.inflate(R.layout.factory_reset_protection_policy, container, false);
+
+        ListView frpAccounts = view.findViewById(R.id.frp_accounts);
+        mAccountsAdapter = new FrpAccountsAdapter();
+        frpAccounts.setAdapter(mAccountsAdapter);
+
+        Button addAccountButton = view.findViewById(R.id.add_account_button);
+        addAccountButton.setOnClickListener(this);
+        Button clearButton = view.findViewById(R.id.clear_frp_button);
+        clearButton.setOnClickListener(this);
+        Button saveButton = view.findViewById(R.id.save_frp_button);
+        saveButton.setOnClickListener(this);
+
+        mFrpEnabledSpinner = view.findViewById(R.id.frp_enabled);
+        mFrpEnabledSpinner.setOnItemSelectedListener(this);
+        ArrayAdapter<CharSequence> enabledAdapter = ArrayAdapter.createFromResource(getActivity(),
+                R.array.factory_reset_protection_policy_enabled_choices,
+                android.R.layout.simple_spinner_item);
+        enabledAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        mFrpEnabledSpinner.setAdapter(enabledAdapter);
+
+        refreshUi();
+        return view;
+    }
+
+    private void refreshUi() {
+        mAccountsAdapter.clear();
+        mFrpEnabledSpinner.setSelection(DISABLED);
+
+        FactoryResetProtectionPolicy mFrpPolicy = mDevicePolicyManager
+                .getFactoryResetProtectionPolicy(mAdminComponentName);
+        if (mFrpPolicy != null) {
+            mAccountsAdapter.addAll(mFrpPolicy.getFactoryResetProtectionAccounts());
+            mFrpEnabledSpinner.setSelection(
+                    mFrpPolicy.isFactoryResetProtectionEnabled() ? ENABLED : DISABLED);
+        }
+    }
+
+    private class FrpAccountsAdapter extends ArrayAdapter<String> {
+
+        public FrpAccountsAdapter() {
+            super(getActivity(), R.layout.factory_reset_protection_policy_account, mAccounts);
+        }
+
+        @Override
+        public View getView(int position, View view, ViewGroup parent) {
+            if (view == null) {
+                view = getActivity().getLayoutInflater().inflate(
+                        R.layout.factory_reset_protection_policy_account, parent, false);
+            }
+            TextView listItemText = view.findViewById(R.id.frp_account);
+            listItemText.setText(mAccounts.get(position));
+
+            Button removeAccountButton = view.findViewById(R.id.remove_account_button);
+            removeAccountButton.setOnClickListener(v -> {
+                mAccounts.remove(position);
+                notifyDataSetChanged();
+            });
+
+            return view;
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        switch (view.getId()) {
+            case R.id.add_account_button:
+                createAddAccountDialog();
+                break;
+            case R.id.clear_frp_button:
+                mDevicePolicyManager.setFactoryResetProtectionPolicy(mAdminComponentName, null);
+                refreshUi();
+                showToast(R.string.cleared_factory_reset_protection_policy);
+                break;
+            case R.id.save_frp_button:
+                mDevicePolicyManager.setFactoryResetProtectionPolicy(mAdminComponentName,
+                        new FactoryResetProtectionPolicy.Builder()
+                                .setFactoryResetProtectionAccounts(mAccounts)
+                                .setFactoryResetProtectionEnabled(mEnabled)
+                                .build());
+                showToast(R.string.saved_factory_reset_protection_policy);
+                break;
+        }
+    }
+
+    public void createAddAccountDialog() {
+        View view = LayoutInflater.from(getActivity()).inflate(R.layout.simple_edittext, null);
+        final EditText input = view.findViewById(R.id.input);
+
+        final AlertDialog dialog = new AlertDialog.Builder(getActivity())
+                .setTitle(R.string.add_account)
+                .setView(view)
+                .setPositiveButton(android.R.string.ok, null)
+                .setNegativeButton(android.R.string.cancel, null)
+                .create();
+        dialog.setOnShowListener(
+                dialogInterface -> dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(
+                        okButtonView -> {
+                            String item = input.getText().toString();
+                            if (TextUtils.isEmpty(item)) {
+                                showToast(R.string.fail_to_add_account);
+                                return;
+                            }
+                            mAccountsAdapter.add(item);
+                            dialog.dismiss();
+                        }));
+        dialog.show();
+    }
+
+    @Override
+    public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
+        if (parent.getId() == R.id.frp_enabled) {
+            switch (pos) {
+                case DISABLED:
+                    mEnabled = false;
+                    break;
+                case ENABLED:
+                    mEnabled = true;
+                    break;
+            }
+        }
+    }
+
+    @Override
+    public void onNothingSelected(AdapterView<?> adapterView) {
+        // do nothing
+    }
+
+    private void showToast(@StringRes int stringResId) {
+        Toast.makeText(getActivity(), stringResId, Toast.LENGTH_LONG).show();
+    }
+
+}
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 15ca095a..7ad2dcf5 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -396,6 +396,8 @@ public class PolicyManagementFragment extends BaseSearchablePolicyPreferenceFrag
     private static final String MANAGED_SYSTEM_UPDATES_KEY = "managed_system_updates";
     private static final String SET_PRIVATE_DNS_MODE_KEY = "set_private_dns_mode";
     private static final String FACTORY_RESET_ORG_OWNED_DEVICE = "factory_reset_org_owned_device";
+    private static final String SET_FACTORY_RESET_PROTECTION_POLICY_KEY =
+            "set_factory_reset_protection_policy";
     private static final String SET_LOCATION_ENABLED_KEY = "set_location_enabled";
     private static final String SET_LOCATION_MODE_KEY = "set_location_mode";
     private static final String SUSPEND_PERSONAL_APPS_KEY = "suspend_personal_apps";
@@ -719,6 +721,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
 
         findPreference(CROSS_PROFILE_CALENDAR_KEY).setOnPreferenceClickListener(this);
         findPreference(FACTORY_RESET_ORG_OWNED_DEVICE).setOnPreferenceClickListener(this);
+        findPreference(SET_FACTORY_RESET_PROTECTION_POLICY_KEY).setOnPreferenceClickListener(this);
 
         DpcPreference bindDeviceAdminPreference =
                 (DpcPreference) findPreference(BIND_DEVICE_ADMIN_POLICIES);
@@ -1287,6 +1290,9 @@ public void onPositiveButtonClicked(String[] lockTaskArray) {
             case FACTORY_RESET_ORG_OWNED_DEVICE:
                 factoryResetOrgOwnedDevice();
                 return true;
+            case SET_FACTORY_RESET_PROTECTION_POLICY_KEY:
+                showFragment(new FactoryResetProtectionPolicyFragment());
+                return true;
         }
         return false;
     }
diff --git a/app/src/main/res/layout/factory_reset_protection_policy.xml b/app/src/main/res/layout/factory_reset_protection_policy.xml
new file mode 100644
index 00000000..7a91b85c
--- /dev/null
+++ b/app/src/main/res/layout/factory_reset_protection_policy.xml
@@ -0,0 +1,103 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:orientation="vertical"
+        android:paddingStart="8dp"
+        android:paddingEnd="8dp"
+        android:layout_weight="1" >
+
+        <TextView
+            style="?android:attr/listSeparatorTextViewStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingStart="8dp"
+            android:paddingEnd="8dp"
+            android:text="@string/factory_reset_protection_policy_accounts"
+            android:textAlignment="viewStart"
+            android:textColor="?android:attr/colorAccent"
+            android:textSize="14sp" />
+
+        <ListView
+            android:id="@+id/frp_accounts"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingTop="4dp"/>
+
+        <Button
+            android:id="@+id/add_account_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="5dp"
+            android:text="@string/add_account" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingStart="8dp"
+            android:paddingEnd="8dp"
+            android:textSize="14sp"
+            android:textAlignment="viewStart"
+            android:textColor="?android:attr/colorAccent"
+            android:text="@string/factory_reset_protection_policy_enabled"
+            style="?android:attr/listSeparatorTextViewStyle" />
+
+        <Spinner android:id="@+id/frp_enabled"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        style="?android:attr/buttonBarStyle"
+        android:divider="?android:attr/dividerHorizontal"
+        android:showDividers="beginning">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:showDividers="middle">
+
+            <Button android:id="@+id/clear_frp_button"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/clear"
+                android:background="?android:attr/selectableItemBackground"
+                style="?android:attr/buttonBarButtonStyle" />
+
+            <Button android:id="@+id/save_frp_button"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/save"
+                android:background="?android:attr/selectableItemBackground"
+                style="?android:attr/buttonBarButtonStyle" />
+        </LinearLayout>
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/factory_reset_protection_policy_account.xml b/app/src/main/res/layout/factory_reset_protection_policy_account.xml
new file mode 100644
index 00000000..59c47830
--- /dev/null
+++ b/app/src/main/res/layout/factory_reset_protection_policy_account.xml
@@ -0,0 +1,41 @@
+<?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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <TextView
+        android:id="@+id/frp_account"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_alignParentStart="true"
+        android:paddingStart="8dp"
+        android:textSize="16sp"
+        android:textColor="?android:attr/colorPrimary"/>
+
+    <Button
+        android:id="@+id/remove_account_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_centerVertical="true"
+        android:layout_marginEnd="5dp"
+        android:text="@string/delete_button_label" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 61a9467d..e686792f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -146,6 +146,16 @@
     <string name="clear_persistent_device_owner_label">Clear</string>
     <!-- Corporate-owned work profile falls under device owner management, for convenience. -->
     <string name="factory_reset_org_owned">Factory reset Org-Owned Work Profile device</string>
+    <string name="set_factory_reset_protection_policy">Set factory reset protection policy</string>
+    <string name="factory_reset_protection_policy">Factory reset protection policy</string>
+    <string name="factory_reset_protection_policy_accounts">Accounts</string>
+    <string name="factory_reset_protection_policy_enabled">Enabled</string>
+    <string-array name="factory_reset_protection_policy_enabled_choices">
+        <item>Disabled</item>
+        <item>Enabled</item>
+    </string-array>
+    <string name="cleared_factory_reset_protection_policy">Cleared factory reset protection policy</string>
+    <string name="saved_factory_reset_protection_policy">Saved factory reset protection policy</string>
 
     <!-- Strings for device reboot -->
     <string name="reboot">Reboot device</string>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index b3b80f50..f454fbcf 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -742,6 +742,11 @@
             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"
+            testdpc:admin="deviceOwner|orgOwnedProfileOwner"
+            testdpc:minSdkVersion="R" />
         <com.afwsamples.testdpc.common.preference.DpcPreference
             android:key="factory_reset_org_owned_device"
             android:title="@string/factory_reset_org_owned"

From 8926f866f0af1864302650c2bdc045d53cd081d6 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Fri, 27 Mar 2020 11:52:17 +0000
Subject: [PATCH 48/73] Update user restrictions on parent in TestDPC

* Previously, TestDPC invoked UserManager
  hasUserRestriction() when displaying the
  restrictions set on the parent instance.
  This was incorrect, as it was showing the
  user restrictions applied to the managed
  profile rather than the primary profile.
* This is now updated to use DevicePolicyManager
  getUserRestrictions() on the parent instance
  to check if a user restriction is set on the
  primary profile.

Bug: 149743941
Test: Manual testing
Change-Id: Iaa9e765d69aea251d8b8aecfecbb90502eb89abf
---
 .../UserRestrictionsParentDisplayFragment.java       | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java
index a7cc4158..df4055f3 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestrictionsParentDisplayFragment.java
@@ -4,7 +4,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Bundle;
-import android.os.UserManager;
 import android.util.Log;
 import android.widget.Toast;
 
@@ -24,7 +23,6 @@ public class UserRestrictionsParentDisplayFragment extends BaseSearchablePolicyP
     private static final String TAG = "UserRestrictionsParent";
 
     private DevicePolicyManager mParentDevicePolicyManager;
-    private UserManager mUserManager;
     private ComponentName mAdminComponentName;
 
     @RequiresApi(api = Util.R_VERSION_CODE)
@@ -32,7 +30,6 @@ public class UserRestrictionsParentDisplayFragment extends BaseSearchablePolicyP
     public void onCreate(Bundle savedInstanceState) {
         DevicePolicyManager mDevicePolicyManager = (DevicePolicyManager)
                 getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
-        mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
         mAdminComponentName = DeviceAdminReceiver.getComponentName(getActivity());
         mParentDevicePolicyManager = mDevicePolicyManager
                 .getParentProfileInstance(mAdminComponentName);
@@ -40,6 +37,7 @@ public void onCreate(Bundle savedInstanceState) {
         getActivity().getActionBar().setTitle(R.string.user_restrictions_management_title);
     }
 
+    @RequiresApi(api = Util.R_VERSION_CODE)
     @Override
     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
@@ -59,6 +57,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         constrainPreferences();
     }
 
+    @RequiresApi(api = Util.R_VERSION_CODE)
     @Override
     public void onResume() {
         super.onResume();
@@ -70,6 +69,7 @@ public boolean isAvailable(Context context) {
         return true;
     }
 
+    @RequiresApi(api = Util.R_VERSION_CODE)
     @Override
     public boolean onPreferenceChange(Preference preference, Object newValue) {
         String restriction = preference.getKey();
@@ -89,16 +89,18 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
         }
     }
 
+    @RequiresApi(api = Util.R_VERSION_CODE)
     private void updateAllUserRestrictions() {
         for (UserRestriction restriction : UserRestriction.PROFILE_OWNER_ORG_DEVICE_RESTRICTIONS) {
             updateUserRestriction(restriction.key);
         }
     }
 
+    @RequiresApi(api = Util.R_VERSION_CODE)
     private void updateUserRestriction(String userRestriction) {
         DpcSwitchPreference preference = (DpcSwitchPreference) findPreference(userRestriction);
-        boolean disallowed = mUserManager.hasUserRestriction(userRestriction);
-        preference.setChecked(disallowed);
+        Bundle restrictions = mParentDevicePolicyManager.getUserRestrictions(mAdminComponentName);
+        preference.setChecked(restrictions.containsKey(userRestriction));
     }
 
     private void constrainPreferences() {

From 7ef6e6d0ad64da88b0c7f35aef510f09c43d1fa2 Mon Sep 17 00:00:00 2001
From: Eran Messeri <eranm@google.com>
Date: Mon, 30 Mar 2020 16:23:35 +0100
Subject: [PATCH 49/73] Is organization-owned: Use stable API

Switch from using reflection to using the official R method,
isOrganizationOwnedDeviceWithManagedProfile, now that TestDPC
compiles against R.

Test: Manual
Change-Id: I796e4fc8ee6f7bcb77f94abd5cc4225ed6503d25
---
 .../common/preference/DpcPreferenceHelper.java     |  8 +-------
 .../testdpc/policy/PolicyManagementFragment.java   | 14 ++------------
 2 files changed, 3 insertions(+), 19 deletions(-)

diff --git a/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java b/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
index 08900272..9805d898 100644
--- a/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
+++ b/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
@@ -257,13 +257,7 @@ private int getCurrentAdmin() {
         if (dpm.isDeviceOwnerApp(packageName)) {
             return ADMIN_DEVICE_OWNER;
         }
-        Boolean orgOwned;
-        try {
-            orgOwned = (Boolean) ReflectionUtil.invoke(dpm,
-                "isOrganizationOwnedDeviceWithManagedProfile");
-        } catch (ReflectionIsTemporaryException e) {
-            orgOwned = false;
-        }
+        Boolean orgOwned = dpm.isOrganizationOwnedDeviceWithManagedProfile();
         if (dpm.isProfileOwnerApp(packageName)) {
             if (orgOwned) {
                 return ADMIN_ORG_OWNED_PROFILE_OWNER;
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 0658203f..3329cc4a 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -2370,8 +2370,9 @@ private void loadAppFeedbackNotifications() {
 
     private void loadAppStatus() {
         final @StringRes int appStatusStringId;
+        boolean isOrgOwned = mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
         if (mDevicePolicyManager.isProfileOwnerApp(mPackageName)) {
-            if (isOrganizationOwnedDeviceWithManagedProfile()) {
+            if (mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) {
                 appStatusStringId = R.string.this_is_an_org_owned_profile_owner;
             } else {
                 appStatusStringId = R.string.this_is_a_profile_owner;
@@ -4054,17 +4055,6 @@ private int validateDeviceOwnerBeforeQ() {
         return NO_CUSTOM_CONSTRIANT;
     }
 
-    // TODO: nuke it when R sdk is available
-    private boolean isOrganizationOwnedDeviceWithManagedProfile() {
-        try {
-            return (Boolean) ReflectionUtil.invoke(
-                    mDevicePolicyManager, "isOrganizationOwnedDeviceWithManagedProfile");
-        } catch (ReflectionIsTemporaryException e) {
-            Log.e(TAG, "Error invoking isOrganizationOwnedDeviceWithManagedProfile", e);
-            return false;
-        }
-    }
-
     abstract static class ManageLockTaskListCallback {
         public abstract void onPositiveButtonClicked(String[] lockTaskArray);
     }

From 3f69b93a8b0afe1a4dfb850328b0adebb8c7f708 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Mon, 30 Mar 2020 20:46:25 +0000
Subject: [PATCH 50/73] Remove reflection for time-related DPM APIs in TestDPC

Bug: 152754849
Test: manual testing
Change-Id: I50dd2211649a4a5620e11f53e2269a78b29bb2a3
---
 .../policy/PolicyManagementFragment.java      | 61 +++----------------
 1 file changed, 10 insertions(+), 51 deletions(-)

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 3329cc4a..05bd00bf 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -1496,23 +1496,11 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
                 reloadSetAutoTimeRequiredUi();
                 return true;
             case SET_AUTO_TIME_KEY:
-                try {
-                    ReflectionUtil.invoke(mDevicePolicyManager, "setAutoTime",
-                            new Class<?>[]{ComponentName.class, boolean.class},
-                            mAdminComponentName, newValue.equals(true));
-                } catch (ReflectionIsTemporaryException e) {
-                    Log.e(TAG, "Error invoking setAutoTime", e);
-                }
+                setAutoTimeEnabled(newValue.equals(true));
                 reloadSetAutoTimeUi();
                 return true;
             case SET_AUTO_TIME_ZONE_KEY:
-                try {
-                    ReflectionUtil.invoke(mDevicePolicyManager, "setAutoTimeZone",
-                            new Class<?>[]{ComponentName.class, boolean.class},
-                            mAdminComponentName, newValue.equals(true));
-                } catch (ReflectionIsTemporaryException e) {
-                    Log.e(TAG, "Error invoking setAutoTimeZone", e);
-                }
+                setAutoTimeZoneEnabled(newValue.equals(true));
                 reloadSetAutoTimeZoneUi();
                 return true;
             case SET_DEVICE_ORGANIZATION_NAME_KEY:
@@ -2610,26 +2598,14 @@ private void reloadSetAutoTimeRequiredUi() {
 
     @TargetApi(Util.R_VERSION_CODE)
     private void reloadSetAutoTimeUi() {
-        try {
-            boolean isAutoTime = (Boolean) ReflectionUtil.invoke(mDevicePolicyManager,
-                    "getAutoTimeEnabled", new Class<?>[]{ComponentName.class},
-                    mAdminComponentName);
-            mSetAutoTimePreference.setChecked(isAutoTime);
-        } catch (ReflectionIsTemporaryException e) {
-            Log.e(TAG, "Error invoking getAutoTimeEnabled", e);
-        }
+        boolean isAutoTime = mDevicePolicyManager.getAutoTimeEnabled(mAdminComponentName);
+        mSetAutoTimePreference.setChecked(isAutoTime);
     }
 
     @TargetApi(Util.R_VERSION_CODE)
     private void reloadSetAutoTimeZoneUi() {
-        try {
-            boolean isAutoTimeZone = (Boolean) ReflectionUtil.invoke(mDevicePolicyManager,
-                    "getAutoTimeZoneEnabled", new Class<?>[]{ComponentName.class},
-                    mAdminComponentName);
-            mSetAutoTimeZonePreference.setChecked(isAutoTimeZone);
-        } catch (ReflectionIsTemporaryException e) {
-            Log.e(TAG, "Error invoking getAutoTimeZoneEnabled", e);
-        }
+        boolean isAutoTimeZone = mDevicePolicyManager.getAutoTimeZoneEnabled(mAdminComponentName);
+        mSetAutoTimeZonePreference.setChecked(isAutoTimeZone);
     }
 
     @TargetApi(VERSION_CODES.LOLLIPOP)
@@ -4059,30 +4035,13 @@ abstract static class ManageLockTaskListCallback {
         public abstract void onPositiveButtonClicked(String[] lockTaskArray);
     }
 
+    @RequiresApi(Util.R_VERSION_CODE)
     private void setAutoTimeEnabled(boolean enabled) {
-        try {
-            ReflectionUtil.invoke(mDevicePolicyManager, "setAutoTimeEnabled",
-                new Class<?>[]{ComponentName.class, boolean.class},
-                mAdminComponentName, enabled);
-        } catch (ReflectionIsTemporaryException e) {
-            Log.e(TAG, "Error invoking setAutoTimeEnabled", e);
-            // Prior to R, auto time is controlled by a global setting
-            mDevicePolicyManager.setGlobalSetting(mAdminComponentName,
-                Settings.Global.AUTO_TIME, enabled ? "1" : "0");
-        }
+        mDevicePolicyManager.setAutoTimeEnabled(mAdminComponentName, enabled);
     }
 
+    @RequiresApi(Util.R_VERSION_CODE)
     private void setAutoTimeZoneEnabled(boolean enabled) {
-        try {
-            ReflectionUtil.invoke(mDevicePolicyManager, "setAutoTimeZoneEnabled",
-                new Class<?>[]{ComponentName.class, boolean.class},
-                mAdminComponentName, enabled);
-        } catch (ReflectionIsTemporaryException e) {
-            Log.e(TAG, "Error invoking setAutoTimeZoneEnabled", e);
-            // Prior to R, auto timezone is controlled by a global setting
-            mDevicePolicyManager.setGlobalSetting(mAdminComponentName,
-                Settings.Global.AUTO_TIME_ZONE, enabled ? "1" : "0");
-        }
-
+        mDevicePolicyManager.setAutoTimeZoneEnabled(mAdminComponentName, enabled);
     }
 }

From 2c93dee1bdd41338349c6d2d3f373e970f648f55 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Wed, 1 Apr 2020 12:25:25 +0000
Subject: [PATCH 51/73] Fix reloadSetAutoTimeUi in TestDPC

* Add check to make sure active admin is
  either a  device owner or profile owner
  of an organization-owned device before
  calling the API getAutoTimeEnabled.

Manual Testing Steps
* Set up a device with a device owner and
  verified that preference was working as
  expected.
* Set up a profile owner of organization
  -owned device and verified that preference
  was working as expected.
* Set up a profile owner (managed profile)
  and verified no exception was thrown and
  that preference is disabled.

Bug: 152939119
Test: Manual testing
Change-Id: Iae2a3999ed4df044af030793989b7cd12d9c9d7f
---
 .../policy/PolicyManagementFragment.java        | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

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 05bd00bf..a9a4b536 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -2598,14 +2598,23 @@ private void reloadSetAutoTimeRequiredUi() {
 
     @TargetApi(Util.R_VERSION_CODE)
     private void reloadSetAutoTimeUi() {
-        boolean isAutoTime = mDevicePolicyManager.getAutoTimeEnabled(mAdminComponentName);
-        mSetAutoTimePreference.setChecked(isAutoTime);
+        if (mDevicePolicyManager.isDeviceOwnerApp(mPackageName)
+                || (mDevicePolicyManager.isProfileOwnerApp(mPackageName)
+                && mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile())) {
+            boolean isAutoTime = mDevicePolicyManager.getAutoTimeEnabled(mAdminComponentName);
+            mSetAutoTimePreference.setChecked(isAutoTime);
+        }
     }
 
     @TargetApi(Util.R_VERSION_CODE)
     private void reloadSetAutoTimeZoneUi() {
-        boolean isAutoTimeZone = mDevicePolicyManager.getAutoTimeZoneEnabled(mAdminComponentName);
-        mSetAutoTimeZonePreference.setChecked(isAutoTimeZone);
+        if (mDevicePolicyManager.isDeviceOwnerApp(mPackageName)
+                || (mDevicePolicyManager.isProfileOwnerApp(mPackageName)
+                && mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile())) {
+            boolean isAutoTimeZone = mDevicePolicyManager
+                    .getAutoTimeZoneEnabled(mAdminComponentName);
+            mSetAutoTimeZonePreference.setChecked(isAutoTimeZone);
+        }
     }
 
     @TargetApi(VERSION_CODES.LOLLIPOP)

From a2cece0be3db658df34c2dc8bcf9d5b2a3844993 Mon Sep 17 00:00:00 2001
From: Eran Messeri <eranm@google.com>
Date: Mon, 20 Apr 2020 14:18:22 +0100
Subject: [PATCH 52/73] Gate check of organization-owned on SDK level

Do not call
DevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile
unless running on an R device or newer.

Test: Manual, flash a device with Android 10, install TestDPC and create a managed profile.
Bug: 153322206
Change-Id: Ide7e16174943358a09ba29674e9abdc4b4756e20
---
 .../common/preference/DpcPreferenceHelper.java    |  3 ++-
 .../testdpc/policy/PolicyManagementFragment.java  | 15 +++++++++++----
 2 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java b/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
index 9805d898..bffd48c4 100644
--- a/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
+++ b/app/src/main/java/com/afwsamples/testdpc/common/preference/DpcPreferenceHelper.java
@@ -257,8 +257,9 @@ private int getCurrentAdmin() {
         if (dpm.isDeviceOwnerApp(packageName)) {
             return ADMIN_DEVICE_OWNER;
         }
-        Boolean orgOwned = dpm.isOrganizationOwnedDeviceWithManagedProfile();
         if (dpm.isProfileOwnerApp(packageName)) {
+            Boolean orgOwned = Util.SDK_INT >= VERSION_CODES.R &&
+                    dpm.isOrganizationOwnedDeviceWithManagedProfile();
             if (orgOwned) {
                 return ADMIN_ORG_OWNED_PROFILE_OWNER;
             } else {
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 a9a4b536..2015d20d 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -2358,9 +2358,10 @@ private void loadAppFeedbackNotifications() {
 
     private void loadAppStatus() {
         final @StringRes int appStatusStringId;
-        boolean isOrgOwned = mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
+        boolean isOrgOwned = Util.SDK_INT >= VERSION_CODES.R &&
+                mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
         if (mDevicePolicyManager.isProfileOwnerApp(mPackageName)) {
-            if (mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) {
+            if (isOrgOwned) {
                 appStatusStringId = R.string.this_is_an_org_owned_profile_owner;
             } else {
                 appStatusStringId = R.string.this_is_a_profile_owner;
@@ -2598,9 +2599,12 @@ private void reloadSetAutoTimeRequiredUi() {
 
     @TargetApi(Util.R_VERSION_CODE)
     private void reloadSetAutoTimeUi() {
+        boolean isOrgOwned = Util.SDK_INT >= VERSION_CODES.R &&
+                mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
+
         if (mDevicePolicyManager.isDeviceOwnerApp(mPackageName)
                 || (mDevicePolicyManager.isProfileOwnerApp(mPackageName)
-                && mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile())) {
+                && isOrgOwned)) {
             boolean isAutoTime = mDevicePolicyManager.getAutoTimeEnabled(mAdminComponentName);
             mSetAutoTimePreference.setChecked(isAutoTime);
         }
@@ -2608,9 +2612,12 @@ private void reloadSetAutoTimeUi() {
 
     @TargetApi(Util.R_VERSION_CODE)
     private void reloadSetAutoTimeZoneUi() {
+        boolean isOrgOwned = Util.SDK_INT >= VERSION_CODES.R &&
+                mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
+
         if (mDevicePolicyManager.isDeviceOwnerApp(mPackageName)
                 || (mDevicePolicyManager.isProfileOwnerApp(mPackageName)
-                && mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile())) {
+                && isOrgOwned)) {
             boolean isAutoTimeZone = mDevicePolicyManager
                     .getAutoTimeZoneEnabled(mAdminComponentName);
             mSetAutoTimeZonePreference.setChecked(isAutoTimeZone);

From ad53ab5c7550504cca359cd5731bfa8b6da7e863 Mon Sep 17 00:00:00 2001
From: Rubin Xu <rubinxu@google.com>
Date: Wed, 22 Apr 2020 18:32:47 +0100
Subject: [PATCH 53/73] Default to not add account post PO provisioning

Most functionalities in TestDPC does not require an account,
so make this the default to save us one tap.

Bug: 154726355
Test: manual
Change-Id: Ic3e2ab4a40024a65dd897eda5379ce356168255c
---
 app/src/main/res/layout/activity_add_account.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/src/main/res/layout/activity_add_account.xml b/app/src/main/res/layout/activity_add_account.xml
index 217e9148..5f6b22d6 100644
--- a/app/src/main/res/layout/activity_add_account.xml
+++ b/app/src/main/res/layout/activity_add_account.xml
@@ -46,7 +46,6 @@
                 android:id="@+id/add_account"
                 android:layout_width="wrap_content"
                 android:layout_height="@dimen/add_account_option_height"
-                android:checked="true"
                 android:text="@string/add_account" />
 
             <RadioButton
@@ -59,6 +58,7 @@
                 android:id="@+id/add_account_skip"
                 android:layout_width="match_parent"
                 android:layout_height="@dimen/add_account_option_height"
+                android:checked="true"
                 android:text="@string/add_account_skip" />
         </RadioGroup>
     </LinearLayout>

From d35fdfb09ce5efdae4ba8a20eec4d6539e358fcc Mon Sep 17 00:00:00 2001
From: Eran Messeri <eranm@google.com>
Date: Tue, 5 May 2020 11:09:31 +0100
Subject: [PATCH 54/73] Apps suspension: Use stable API.

Now that the Android R SDK is stable, switch to using the official
version of setPersonalAppsSuspended / getPersonalAppsSuspendedReasons
rather than using reflection.

Test: That it compiles
Change-Id: I7f04e0614b1c049175a9291b54e6f6d4b7862d49
---
 .../policy/PolicyManagementFragment.java      | 31 +++----------------
 1 file changed, 4 insertions(+), 27 deletions(-)

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 2015d20d..c641ef6b 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -795,37 +795,13 @@ private void maybeUpdateProfileMaxTimeOff() {
 
     @TargetApi(Util.R_VERSION_CODE)
     private void reloadPersonalAppsSuspendedUi() {
-        // TODO: nuke it when R sdk is available
-        final int PERSONAL_APPS_NOT_SUSPENDED = 0;
         if (mSuspendPersonalApps.isEnabled()) {
-            int suspendReasons = getPersonalAppsSuspensionReasons();
+            int suspendReasons =
+                    mDevicePolicyManager.getPersonalAppsSuspendedReasons(mAdminComponentName);
             mSuspendPersonalApps.setChecked(suspendReasons != 0);
         }
     }
 
-    // TODO: nuke it when R sdk is available.
-    int getPersonalAppsSuspensionReasons() {
-        try {
-            return (Integer) ReflectionUtil.invoke(mDevicePolicyManager,
-                    "getPersonalAppsSuspendedReasons", new Class<?>[]{ComponentName.class},
-                    mAdminComponentName);
-        } catch (ReflectionIsTemporaryException e) {
-            logAndShowToast("Error invoking getPersonalAppsSuspendedReasons", e);
-            return 0;
-        }
-    }
-
-    // TODO: nuke it when R sdk is available.
-    void setPersonalAppsSuspended(boolean suspended) {
-        try {
-            ReflectionUtil.invoke(mDevicePolicyManager, "setPersonalAppsSuspended",
-                    new Class<?>[]{ComponentName.class, boolean.class},
-                    mAdminComponentName, suspended);
-        } catch (ReflectionIsTemporaryException e) {
-            logAndShowToast("Error invoking setPersonalAppsSuspended", e);
-        }
-    }
-
     //TODO: nuke it when R sdk is available.
     public long getManagedProfileMaximumTimeOff() {
         try {
@@ -1555,7 +1531,8 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
                 reloadLocationModeUi();
                 return true;
             case SUSPEND_PERSONAL_APPS_KEY:
-                setPersonalAppsSuspended((Boolean) newValue);
+                mDevicePolicyManager.setPersonalAppsSuspended(
+                        mAdminComponentName, (Boolean) newValue);
                 reloadPersonalAppsSuspendedUi();
                 return true;
             case PROFILE_MAX_TIME_OFF_KEY:

From 0bb21b2e3ef39a26666257151cac547507e78393 Mon Sep 17 00:00:00 2001
From: Rubin Xu <rubinxu@google.com>
Date: Tue, 5 May 2020 12:24:04 +0100
Subject: [PATCH 55/73] Fix Common Criteria toggle

Prefernce changed listener was not set up, causing the policy
not being set correctly.

Bug: 155725222
Test: manual
Change-Id: Ic888f6e4b2179b0986beee62fd26040f05f7dd23
---
 .../testdpc/policy/PolicyManagementFragment.java   | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

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 2015d20d..1f88bbef 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -614,6 +614,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         mEnableBackupServicePreference.setCustomConstraint(this::validateDeviceOwnerBeforeQ);
         mCommonCriteriaModePreference = (DpcSwitchPreference) findPreference(
             COMMON_CRITERIA_MODE_KEY);
+        mCommonCriteriaModePreference.setOnPreferenceChangeListener(this);
         findPreference(REQUEST_BUGREPORT_KEY).setOnPreferenceClickListener(this);
         mEnableSecurityLoggingPreference =
             (SwitchPreference) findPreference(ENABLE_SECURITY_LOGGING);
@@ -774,6 +775,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         reloadScreenCaptureDisableUi();
         reloadMuteAudioUi();
         reloadEnableBackupServiceUi();
+        reloadCommonCriteriaModeUi();
         reloadEnableSecurityLoggingUi();
         reloadEnableNetworkLoggingUi();
         reloadSetAutoTimeRequiredUi();
@@ -1444,6 +1446,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
             case COMMON_CRITERIA_MODE_KEY:
                 setCommonCriteriaModeEnabled((Boolean) newValue);
                 reloadCommonCriteriaModeUi();
+                return true;
             case ENABLE_SECURITY_LOGGING:
                 setSecurityLoggingEnabled((Boolean) newValue);
                 reloadEnableSecurityLoggingUi();
@@ -1589,16 +1592,9 @@ private void setBackupServiceEnabled(boolean enabled) {
         mDevicePolicyManager.setBackupServiceEnabled(mAdminComponentName, enabled);
     }
 
-    //@TargetApi(VERSION_CODES.R)
+    @TargetApi(VERSION_CODES.R)
     private void setCommonCriteriaModeEnabled(boolean enabled) {
-        try {
-            ReflectionUtil.invoke(mDevicePolicyManager,
-                "setCommonCriteriaModeEnabled",
-                new Class<?>[]{ComponentName.class, boolean.class},
-                mAdminComponentName, enabled);
-        } catch (ReflectionIsTemporaryException e) {
-            Log.e(TAG, "Error invoking setCommonCriteriaModeEnabled", e);
-        }
+        mDevicePolicyManager.setCommonCriteriaModeEnabled(mAdminComponentName, enabled);
     }
 
     @TargetApi(VERSION_CODES.M)

From 1fe620f062e6389b47dd52d84ef53aa965f9d973 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Thu, 30 Apr 2020 14:24:49 +0000
Subject: [PATCH 56/73] Restrict creation of secondary users TestDPC

Background
* Secondary users should be disabled
  when the device is an organization-owned
  managed profile device.
* This is because supporting secondary
  users would complicate the semantics of
  user restrictions.

Changes
* Remove 'Disallow add user' from list of
  user restrictions the profile owner of
  an organization-owned managed profile
  device can set on the parent profile.

Manual Testing Steps
* Provision an organization-owned managed
  profile device.
* Check WP TestDPC 'Set user restrictions
  on parent' and verify 'Disallow add user'
  is not present.

Bug: 155281701
Test: Manual testing
Change-Id: I4706cda0aa2b72c786daf08a03502406b3819cf8
---
 .../java/com/afwsamples/testdpc/policy/UserRestriction.java     | 2 --
 1 file changed, 2 deletions(-)

diff --git a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java
index 42ade733..d29c7328 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/UserRestriction.java
@@ -221,8 +221,6 @@ public UserRestriction(String key, int titleResId) {
                     R.string.disallow_data_roaming),
             new UserRestriction(DISALLOW_DEBUGGING_FEATURES,
                     R.string.disallow_debugging_features),
-            new UserRestriction(DISALLOW_ADD_USER,
-                    R.string.disallow_add_user),
             new UserRestriction(DISALLOW_BLUETOOTH,
                     R.string.disallow_bluetooth),
             new UserRestriction(DISALLOW_BLUETOOTH_SHARING,

From a6b44115090c2fd8cb96d627a3dcb40d25d5c7b8 Mon Sep 17 00:00:00 2001
From: Eran Messeri <eranm@google.com>
Date: Fri, 15 May 2020 14:13:41 +0100
Subject: [PATCH 57/73] Remove use of reflection

Now that R SDK is finalized, there's no need to use reflection anymore.

Test: That it compiles.
Change-Id: I7ad212ce729b263facf3073f349d220c93d1fe70
---
 .../policy/PolicyManagementFragment.java      | 60 ++++---------------
 1 file changed, 10 insertions(+), 50 deletions(-)

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 a39f6f51..01b31584 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -102,8 +102,6 @@
 import com.afwsamples.testdpc.common.CertificateUtil;
 import com.afwsamples.testdpc.common.MediaDisplayFragment;
 import com.afwsamples.testdpc.common.PackageInstallationUtils;
-import com.afwsamples.testdpc.common.ReflectionUtil;
-import com.afwsamples.testdpc.common.ReflectionUtil.ReflectionIsTemporaryException;
 import com.afwsamples.testdpc.common.UserArrayAdapter;
 import com.afwsamples.testdpc.common.Util;
 import com.afwsamples.testdpc.common.preference.CustomConstraint;
@@ -789,7 +787,8 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
     private void maybeUpdateProfileMaxTimeOff() {
         if (mProfileMaxTimeOff.isEnabled()) {
             final String currentValueAsString = Long.toString(
-                    TimeUnit.MILLISECONDS.toSeconds(getManagedProfileMaximumTimeOff()));
+                    TimeUnit.MILLISECONDS.toSeconds(
+                        mDevicePolicyManager.getManagedProfileMaximumTimeOff(mAdminComponentName)));
             mProfileMaxTimeOff.setText(currentValueAsString);
             mProfileMaxTimeOff.setSummary(currentValueAsString);
         }
@@ -804,29 +803,6 @@ private void reloadPersonalAppsSuspendedUi() {
         }
     }
 
-    //TODO: nuke it when R sdk is available.
-    public long getManagedProfileMaximumTimeOff() {
-        try {
-            return (Long) ReflectionUtil.invoke(mDevicePolicyManager,
-                    "getManagedProfileMaximumTimeOff", new Class<?>[]{ComponentName.class},
-                    mAdminComponentName);
-        } catch (ReflectionIsTemporaryException e) {
-            logAndShowToast("Error invoking getManagedProfileMaximumTimeOff", e);
-            return 0;
-        }
-    }
-
-    //TODO: nuke it when R sdk is available.
-    public void setManagedProfileMaximumTimeOff(long timeoutMs) {
-        try {
-            ReflectionUtil.invoke(mDevicePolicyManager, "setManagedProfileMaximumTimeOff",
-                    new Class<?>[]{ComponentName.class, long.class},
-                    mAdminComponentName, timeoutMs);
-        } catch (ReflectionIsTemporaryException e) {
-            logAndShowToast("Error invoking setManagedProfileMaximumTimeOff", e);
-        }
-    }
-
     private void logAndShowToast(String message, Exception e) {
         Log.e(TAG, message, e);
         showToast(message + ": " + e.getMessage());
@@ -1453,14 +1429,8 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
                 updateStayOnWhilePluggedInPreference();
                 return true;
             case WIFI_CONFIG_LOCKDOWN_ENABLE_KEY:
-                try {
-                    ReflectionUtil.invoke(mDevicePolicyManager,
-                            "setConfiguredNetworksLockdownState",
-                            new Class<?>[]{ComponentName.class, boolean.class},
-                            mAdminComponentName, newValue.equals(true));
-                } catch (ReflectionIsTemporaryException e) {
-                    Log.e(TAG, "Error invoking setConfiguredNetworksLockdownState", e);
-                }
+                mDevicePolicyManager.setConfiguredNetworksLockdownState(
+                        mAdminComponentName, newValue.equals(true));
                 reloadLockdownAdminConfiguredNetworksUi();
                 return true;
             case INSTALL_NONMARKET_APPS_KEY:
@@ -1511,13 +1481,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
                 editor.commit();
                 return true;
             case SET_LOCATION_ENABLED_KEY:
-                try {
-                    ReflectionUtil.invoke(mDevicePolicyManager, "setLocationEnabled",
-                        new Class<?>[]{ComponentName.class, boolean.class},
-                        mAdminComponentName, newValue.equals(true));
-                } catch (ReflectionIsTemporaryException e) {
-                    Log.e(TAG, "Error invoking setLocationEnabled", e);
-                }
+                mDevicePolicyManager.setLocationEnabled(mAdminComponentName, newValue.equals(true));
                 reloadLocationEnabledUi();
                 reloadLocationModeUi();
                 return true;
@@ -1540,7 +1504,8 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
                 return true;
             case PROFILE_MAX_TIME_OFF_KEY:
                 final long timeoutSec = Long.parseLong((String) newValue);
-                setManagedProfileMaximumTimeOff(TimeUnit.SECONDS.toMillis(timeoutSec));
+                mDevicePolicyManager.setManagedProfileMaximumTimeOff(
+                        mAdminComponentName, TimeUnit.SECONDS.toMillis(timeoutSec));
                 maybeUpdateProfileMaxTimeOff();
                 return true;
         }
@@ -2457,14 +2422,9 @@ private void reloadLocationEnabledUi() {
 
     @TargetApi(Util.R_VERSION_CODE)
     private void reloadLockdownAdminConfiguredNetworksUi() {
-        try {
-            boolean lockdown = (Boolean) ReflectionUtil.invoke(mDevicePolicyManager,
-                    "hasLockdownAdminConfiguredNetworks",
-                    new Class<?>[]{ComponentName.class}, mAdminComponentName);
-            mLockdownAdminConfiguredNetworksPreference.setChecked(lockdown);
-        } catch (ReflectionIsTemporaryException e) {
-            Log.e(TAG, "Error invoking hasLockdownAdminConfiguredNetworks", e);
-        }
+        boolean lockdown = mDevicePolicyManager.hasLockdownAdminConfiguredNetworks(
+                mAdminComponentName);
+        mLockdownAdminConfiguredNetworksPreference.setChecked(lockdown);
     }
 
     static private int parseInt(String str, int defaultValue) {

From 43599b8c690fd02ab5ecbdf10a72505ce2cddba1 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Wed, 20 May 2020 10:32:01 +0000
Subject: [PATCH 58/73] TestDPC SetAutoTimeRequired disabled in managed profile

* Set auto time required preference should be disabled
  if the TestDpc instance is in managed profile.

Bug: 156620695
Test: Manual, verify TestDPC preference is disabled on a
      managed profile.
Change-Id: I6ff599f84bdff3b269ef18afd65beed5577760ca
---
 app/src/main/res/xml/device_policy_header.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index 5e3a2e9d..963bc04c 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -672,6 +672,7 @@
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="set_auto_time_required"
             android:title="@string/set_auto_time_required"
+            testdpc:user="notManagedProfile"
             testdpc:minSdkVersion="L" />
         <com.afwsamples.testdpc.common.preference.DpcSwitchPreference
             android:key="set_auto_time"

From d8a56a849ad2f860879cb87ac56b1c63f9cff24e Mon Sep 17 00:00:00 2001
From: Jonathan Scott <scottjonathan@google.com>
Date: Fri, 12 Jun 2020 13:58:18 +0100
Subject: [PATCH 59/73] Remove deprecated gradle features.

Test: ./gradlew assemble
Bug: 158763485
Change-Id: I3834bca3f47cdf853dfdb419cf811cd2ca0b0471
---
 app/build.gradle | 24 ++++++++++++++++--------
 1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index e050bc53..18fb3927 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -82,20 +82,28 @@ android {
         }
     }
 
-    task stripTestOnlyNormalDebug << {
-        stripTestOnlyForBuild("normal", "debug")
+    task stripTestOnlyNormalDebug {
+        doLast {
+            stripTestOnlyForBuild("normal", "debug")
+        }
     }
 
-    task stripTestOnlyNormalRelease << {
-        stripTestOnlyForBuild("normal", "release")
+    task stripTestOnlyNormalRelease {
+        doLast {
+            stripTestOnlyForBuild("normal", "release")
+        }
     }
 
-    task stripTestOnlyReplicaDebug << {
-        stripTestOnlyForBuild("replica", "debug")
+    task stripTestOnlyReplicaDebug {
+        doLast {
+            stripTestOnlyForBuild("replica", "debug")
+        }
     }
 
-    task stripTestOnlyReplicaRelease << {
-        stripTestOnlyForBuild("replica", "release")
+    task stripTestOnlyReplicaRelease {
+        doLast {
+            stripTestOnlyForBuild("replica", "release")
+        }
     }
 
     tasks.whenTaskAdded { task ->

From 0d95eeefd62f4834ce2e347bdcff05a0beb43a92 Mon Sep 17 00:00:00 2001
From: Jonathan Scott <scottjonathan@google.com>
Date: Tue, 16 Jun 2020 09:59:05 +0100
Subject: [PATCH 60/73] Remove CosuApp.

CosuApp has not been updated in 5 years and was never released
publicly.

Test: ./gradlew assemble
Change-Id: Ibc770fea3bf6225c9f41329559d7fe31d412edf2
---
 settings.gradle | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/settings.gradle b/settings.gradle
index 1c8d119a..b377dd35 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,4 +1,2 @@
 include ':TestDPC'
-project(':TestDPC').projectDir = new File(rootDir, "vendor/unbundled_google/packages/TestDPC/app")
-include ':CosuApp'
-project(':CosuApp').projectDir = new File(rootDir, "vendor/unbundled_google/packages/TestDPC/cosuapp")
+project(':TestDPC').projectDir = new File(rootDir, "vendor/unbundled_google/packages/TestDPC/app")
\ No newline at end of file

From d0833cd226e6172b06deac7333eb214c17406ce6 Mon Sep 17 00:00:00 2001
From: Eran Messeri <eranm@google.com>
Date: Tue, 16 Jun 2020 19:06:27 +0100
Subject: [PATCH 61/73] Support individual attestation in TestDPC

Enhance TestDPC for supporting individual attestation:
* Separate section for StrongBox options, including a new checkbox for
  use of individual attestation certificate.
* Refactor key generation parameters into their own data container class.
* Log the generated attestation record into logcat.

Bug: 158668149
Test: Manual
Change-Id: I707224ba91b2414be78b0017b07fb38d0b0aa8c3
---
 .../policy/PolicyManagementFragment.java      | 34 +++++-----
 .../GenerateKeyAndCertificateTask.java        | 29 ++++----
 .../KeyGenerationParameters.java              | 66 +++++++++++++++++++
 .../main/res/layout/key_generation_prompt.xml | 11 ++++
 app/src/main/res/values/strings.xml           |  3 +
 5 files changed, 116 insertions(+), 27 deletions(-)
 create mode 100644 app/src/main/java/com/afwsamples/testdpc/policy/keymanagement/KeyGenerationParameters.java

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 01b31584..b28a4f69 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -116,6 +116,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;
@@ -1616,14 +1617,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();
     }
 
     /**
@@ -2772,6 +2767,9 @@ 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);
 
         new AlertDialog.Builder(getActivity())
                 .setTitle(getString(R.string.certificate_alias_prompt_title))
@@ -2779,12 +2777,13 @@ 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};
+                            paramsBuilder.setAttestationChallenge(new byte[] {0x61, 0x62, 0x63});
                         }
 
                         int idAttestationFlags = 0;
@@ -2800,10 +2799,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)
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..da15873e 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 =
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/res/layout/key_generation_prompt.xml b/app/src/main/res/layout/key_generation_prompt.xml
index 8fdb8b1f..46fff759 100644
--- a/app/src/main/res/layout/key_generation_prompt.xml
+++ b/app/src/main/res/layout/key_generation_prompt.xml
@@ -93,10 +93,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/values/strings.xml b/app/src/main/res/values/strings.xml
index 51e06173..9067b40a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -565,7 +565,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>

From e83e6d0042c4466d0be5dd0f78e2921bba8dda75 Mon Sep 17 00:00:00 2001
From: Jonathan Scott <scottjonathan@google.com>
Date: Fri, 19 Jun 2020 16:15:44 +0100
Subject: [PATCH 62/73] Bump version.

Test: manual
Bug: 158586464
Change-Id: I59737a96ee891fe0c29962ff70976c9306e8c5f9
---
 app/build.gradle | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index 18fb3927..14f1208f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -5,9 +5,9 @@ apply from: '../dist.gradle'
 ext {
     /* Version code for *next* release, bump *after* a release is created. */
     // 1 or more digits
-    versionMajor = 6
+    versionMajor = 7
     // exactly 1 digit
-    versionMinor = 2
+    versionMinor = 0
     // exactly 2 digits
     versionBuild = 00
 }

From c6a9df997f1a8a41f3fd53bf26cb30ac60aeb30b Mon Sep 17 00:00:00 2001
From: Jonathan Scott <scottjonathan@google.com>
Date: Wed, 24 Jun 2020 14:45:14 +0100
Subject: [PATCH 63/73] Fix bug which crashes prior to R.

This was caused by a faulty version check.

Fixes: 159761407
Change-Id: Id7f235b67a9d325e867a7575ef6c3e5b13c4fc63
---
 .../testdpc/policy/PolicyManagementFragment.java   | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

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 b28a4f69..ed3df37a 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -2527,8 +2527,11 @@ private void reloadSetAutoTimeRequiredUi() {
 
     @TargetApi(Util.R_VERSION_CODE)
     private void reloadSetAutoTimeUi() {
-        boolean isOrgOwned = Util.SDK_INT >= VERSION_CODES.R &&
-                mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
+        if (Util.SDK_INT < VERSION_CODES.R) {
+            return;
+        }
+        boolean isOrgOwned =
+            mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
 
         if (mDevicePolicyManager.isDeviceOwnerApp(mPackageName)
                 || (mDevicePolicyManager.isProfileOwnerApp(mPackageName)
@@ -2540,8 +2543,11 @@ private void reloadSetAutoTimeUi() {
 
     @TargetApi(Util.R_VERSION_CODE)
     private void reloadSetAutoTimeZoneUi() {
-        boolean isOrgOwned = Util.SDK_INT >= VERSION_CODES.R &&
-                mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
+        if (Util.SDK_INT < VERSION_CODES.R) {
+            return;
+        }
+        boolean isOrgOwned =
+            mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
 
         if (mDevicePolicyManager.isDeviceOwnerApp(mPackageName)
                 || (mDevicePolicyManager.isProfileOwnerApp(mPackageName)

From 478afc78b5b7b183d9d232a7f9f2b54cf9a92622 Mon Sep 17 00:00:00 2001
From: Jonathan Scott <scottjonathan@google.com>
Date: Wed, 24 Jun 2020 15:59:00 +0100
Subject: [PATCH 64/73] Bump version.

Change-Id: If8cc5c859583eac76fd8ff087d9e8d83f7965237
---
 app/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/build.gradle b/app/build.gradle
index 14f1208f..3e541f95 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,7 +9,7 @@ ext {
     // exactly 1 digit
     versionMinor = 0
     // exactly 2 digits
-    versionBuild = 00
+    versionBuild = 01
 }
 
 android {

From 2ac75d320bc7cf154744f6e6fc7562d843dcf242 Mon Sep 17 00:00:00 2001
From: Elis Elliott <eliselliott@google.com>
Date: Wed, 1 Jul 2020 08:54:20 +0000
Subject: [PATCH 65/73] Add option to use a custom attestation challenge

When generating a private/public key pair a text box now appears
asking for a custom challenge (in Base64) under the checkbox
"Include key attestation challenge". The default challenge is 'abc'
('YWJj' in Base64).

Bug: 159901371
Change-Id: I0b275205eb53f92cd58792892d861a3666947b0a
---
 .../testdpc/policy/PolicyManagementFragment.java     | 11 ++++++++++-
 app/src/main/res/layout/key_generation_prompt.xml    | 12 ++++++++++++
 2 files changed, 22 insertions(+), 1 deletion(-)

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 ed3df37a..6461e65e 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -64,6 +64,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;
@@ -2777,6 +2778,10 @@ private void showPromptForGeneratedKeyAlias(String alias) {
                 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))
                 .setView(aliasNamingView)
@@ -2789,7 +2794,11 @@ public void onClick(DialogInterface dialog, int which) {
                         paramsBuilder.setIsUserSelectable(userSelectableCheckbox.isChecked());
 
                         if (includeAttestationChallengeCheckbox.isChecked()) {
-                            paramsBuilder.setAttestationChallenge(new byte[] {0x61, 0x62, 0x63});
+                            String customChallenge = customChallengeInput.getText().toString()
+                                .trim();
+                            byte[] decodedChallenge = Base64.decode(customChallenge,
+                                Base64.DEFAULT);
+                            paramsBuilder.setAttestationChallenge(decodedChallenge);
                         }
 
                         int idAttestationFlags = 0;
diff --git a/app/src/main/res/layout/key_generation_prompt.xml b/app/src/main/res/layout/key_generation_prompt.xml
index 46fff759..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"

From 756073848c2ba1bda514f2afc70b7f78d88995c4 Mon Sep 17 00:00:00 2001
From: Elis Elliott <eliselliott@google.com>
Date: Fri, 3 Jul 2020 13:03:19 +0000
Subject: [PATCH 66/73] Add DEVICE_UNIQUE_ATTESTION tag & parse its value.

Make TestDPC aware of the DEVICE_UNIQUE_ATTESTATION tag
and parse its value for use in the dialog produced after
the attestation record is parsed.

Bug: 159706341
Change-Id: I2e7489a8f698ff38ae21a1897699962d7f2ea4de
---
 .../GenerateKeyAndCertificateTask.java              |  3 +++
 .../testdpc/policy/utils/AuthorizationList.java     | 13 +++++++++++++
 2 files changed, 16 insertions(+)

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 da15873e..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
@@ -204,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/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);
         }

From b6e4c433c0298ae765ef79143f6e67ebbf9a67d0 Mon Sep 17 00:00:00 2001
From: Kholoud Mohamed <kholoudm@google.com>
Date: Mon, 6 Jul 2020 18:25:24 +0100
Subject: [PATCH 67/73] Add cross profile apps whitelisting support

Bug: https://b.corp.google.com/issues/160294472
Test: manual testing
Change-Id: I8ba199c020ba1499afabc9bd55c14c45dfae73ff
---
 .../CrossProfileAppsWhitelistFragment.java    | 111 ++++++++++++++++++
 .../policy/PolicyManagementFragment.java      |   6 +
 .../layout/cross_profile_apps_whitelist.xml   |  78 ++++++++++++
 app/src/main/res/values/strings.xml           |   8 ++
 app/src/main/res/xml/device_policy_header.xml |   6 +
 5 files changed, 209 insertions(+)
 create mode 100644 app/src/main/java/com/afwsamples/testdpc/CrossProfileAppsWhitelistFragment.java
 create mode 100644 app/src/main/res/layout/cross_profile_apps_whitelist.xml

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/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
index 6461e65e..b8bf700e 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -94,6 +94,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;
@@ -385,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";
@@ -710,6 +712,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);
@@ -1217,6 +1220,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;
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/values/strings.xml b/app/src/main/res/values/strings.xml
index 9067b40a..a503b412 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1106,6 +1106,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..dd7ff65c 100644
--- a/app/src/main/res/xml/device_policy_header.xml
+++ b/app/src/main/res/xml/device_policy_header.xml
@@ -786,5 +786,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>

From d7e605f24775754872ca3b1ebf43042aed5eac14 Mon Sep 17 00:00:00 2001
From: Gabriel-Codrin Cojocaru <codrinc@google.com>
Date: Tue, 14 Jul 2020 15:30:37 +0000
Subject: [PATCH 68/73] Android TV launcher and lock task fixes

Add a launcher intent filter for Android TV.
Use the LEANBACK_LAUNCHER category instead of the LAUNCHER category when creating launcher intents on Android TV.
When starting apps from the kiosk mode activity use getLeanbackLaunchIntentForPackage instead of getLaunchIntentForPackage when running on Android TV.

Test: Manual testing
Bug: 161220655
Change-Id: Id5cf498bfd7aa9967e57abe2451e85181740612c
---
 app/src/main/AndroidManifest.xml               |  6 ++++++
 .../com/afwsamples/testdpc/common/Util.java    | 18 ++++++++++++++++++
 .../policy/PolicyManagementFragment.java       |  6 ++----
 .../policy/locktask/KioskModeActivity.java     | 10 +++++++++-
 4 files changed, 35 insertions(+), 5 deletions(-)

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/common/Util.java b/app/src/main/java/com/afwsamples/testdpc/common/Util.java
index 5e315c5a..82fa0f80 100644
--- a/app/src/main/java/com/afwsamples/testdpc/common/Util.java
+++ b/app/src/main/java/com/afwsamples/testdpc/common/Util.java
@@ -18,12 +18,14 @@
 
 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;
@@ -255,6 +257,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);
     }
@@ -266,4 +279,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/policy/PolicyManagementFragment.java b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
index b8bf700e..a76c2023 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -1666,8 +1666,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);
@@ -3396,8 +3395,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/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);
         }
     }
 }

From f83bd52b7203bf3b0e41dbf947ab003f965694b0 Mon Sep 17 00:00:00 2001
From: bernardchau <bernardchau@google.com>
Date: Thu, 6 Aug 2020 16:46:15 +0100
Subject: [PATCH 69/73] Revert "Add UI for testing forced re-enrollment."

This reverts commit fe471ff4db4339ceb2b492a7cab9268cbbb0b09e.

Reason for revert: Forced re-enrollment (FRE) is deprecated and support
has been removed from GmsCore. Cleaning up related code in TestDpc.

Change-Id: I60e1e0ce1ebc7ef18e75a2bd40871b94e4d09d9c
---
 .../com/afwsamples/testdpc/common/Util.java   | 42 ----------
 .../policy/PersistentDeviceOwnerFragment.java | 77 -------------------
 .../policy/PolicyManagementFragment.java      |  5 --
 .../provision/PostProvisioningTask.java       | 15 ----
 .../persistent_device_owner_fragment.xml      | 46 -----------
 app/src/main/res/values/strings.xml           |  4 -
 app/src/main/res/xml/device_policy_header.xml |  5 --
 7 files changed, 194 deletions(-)
 delete mode 100644 app/src/main/java/com/afwsamples/testdpc/policy/PersistentDeviceOwnerFragment.java
 delete mode 100644 app/src/main/res/layout/persistent_device_owner_fragment.xml

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 82fa0f80..79997bbd 100644
--- a/app/src/main/java/com/afwsamples/testdpc/common/Util.java
+++ b/app/src/main/java/com/afwsamples/testdpc/common/Util.java
@@ -30,7 +30,6 @@
 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;
@@ -54,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 +199,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);
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 a76c2023..88c6f615 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -371,7 +371,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";
@@ -608,7 +607,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);
@@ -937,9 +935,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;
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/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 a503b412..f1b2c3f6 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>
@@ -141,9 +140,6 @@
     <string name="remove_device_owner_confirmation">Policies and restrictions will continue to be
         active and may require a factory reset to clear.</string>
     <string name="device_owner_removed">This app is no longer a device owner.</string>
-    <string name="persistent_device_owner_state_hint">Test state to be restored alongside the device owner after factory reset.</string>
-    <string name="set_persistent_device_owner_label">Set</string>
-    <string name="clear_persistent_device_owner_label">Clear</string>
     <!-- Corporate-owned work profile falls under device owner management, for convenience. -->
     <string name="factory_reset_org_owned">Factory reset Org-Owned Work Profile device</string>
     <string name="set_factory_reset_protection_policy">Set factory reset protection policy</string>
diff --git a/app/src/main/res/xml/device_policy_header.xml b/app/src/main/res/xml/device_policy_header.xml
index dd7ff65c..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"

From 5f004a3d68f847a90e819fe40298beb823230815 Mon Sep 17 00:00:00 2001
From: Alex Johnston <acjohnston@google.com>
Date: Wed, 26 Aug 2020 11:44:35 +0100
Subject: [PATCH 70/73] Do not call time APIs introduced in R if device is
 pre-R

* If the device is pre-R, do not call setAutoTimeEnabled
  and setAutoTimeZoneEnabled.
* Call setGlobalSettings instead.

Manual testing steps
* Install TestDPC
* Set device in device owner mode
* Select setTime and setTimeZone
* Verify TestDPC does not crash

Bug: 165592867
Test: manual testing with TestDPC
Change-Id: Ife22d85f0415b978b34c89e5950242df5815e8e5
---
 .../testdpc/policy/PolicyManagementFragment.java   | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

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 88c6f615..b4fce5ff 100644
--- a/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/policy/PolicyManagementFragment.java
@@ -1229,11 +1229,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:

From 2ed654de8c43fa7dff9fb4754debd39b03ad187c Mon Sep 17 00:00:00 2001
From: Jonathan Scott <scottjonathan@google.com>
Date: Mon, 14 Sep 2020 10:46:44 +0100
Subject: [PATCH 71/73] Merge pull request #115.

Test: build
Change-Id: If12233afb4ca47dae08daea44556e84188163c51
---
 .../KeyValueBundleArrayFragment.java          | 23 +++++++++++++++++++
 1 file changed, 23 insertions(+)

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 3121b145..a6329b5e 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
@@ -33,6 +33,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 
 import static com.afwsamples.testdpc.common.EditDeleteArrayAdapter.OnDeleteButtonClickListener;
 import static com.afwsamples.testdpc.common.EditDeleteArrayAdapter.OnEditButtonClickListener;
@@ -130,10 +131,32 @@ protected void saveConfig() {
     @Override
     protected void addNewRow() {
         Bundle bundle = new Bundle();
+
+        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() {}
 

From c4ce52fdd8469f47766fafd7e198eae476a66d59 Mon Sep 17 00:00:00 2001
From: Jonathan Scott <scottjonathan@google.com>
Date: Mon, 14 Sep 2020 11:17:07 +0100
Subject: [PATCH 72/73] Merge #100

Change-Id: I8d37f31c183ad6300bfb4f0970c7d9ce36e86ff0
---
 .../com/afwsamples/testdpc/common/ProfileOrParentFragment.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/src/main/java/com/afwsamples/testdpc/common/ProfileOrParentFragment.java b/app/src/main/java/com/afwsamples/testdpc/common/ProfileOrParentFragment.java
index 4cdf1081..2a4b1170 100644
--- a/app/src/main/java/com/afwsamples/testdpc/common/ProfileOrParentFragment.java
+++ b/app/src/main/java/com/afwsamples/testdpc/common/ProfileOrParentFragment.java
@@ -61,7 +61,7 @@ public void onCreate(Bundle savedInstanceState) {
         public View onCreateView(
                 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
             FragmentTabHost tabHost = new FragmentTabHost(getActivity());
-            tabHost.setup(getActivity(), getChildFragmentManager(), View.generateViewId());
+            tabHost.setup(getActivity(), getChildFragmentManager(), container.getId());
 
             final boolean showDualTabs =
                 Util.isManagedProfileOwner(getActivity())

From cb954fb61f53be2c5b0eb9e9c2d96cc5c2454c21 Mon Sep 17 00:00:00 2001
From: Jonathan Scott <scottjonathan@google.com>
Date: Mon, 14 Sep 2020 11:42:11 +0100
Subject: [PATCH 73/73] Bump version.

Change-Id: Icb2654c9c8dfabc554bc61d7763e6df289645a1c
---
 app/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/build.gradle b/app/build.gradle
index 3e541f95..e4e61fa8 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,7 +9,7 @@ ext {
     // exactly 1 digit
     versionMinor = 0
     // exactly 2 digits
-    versionBuild = 01
+    versionBuild = 02
 }
 
 android {