/*
* 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.
*/
package com.afwsamples.testdpc.provision;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
import static com.afwsamples.testdpc.DeviceAdminReceiver.getComponentName;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.TargetApi;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.os.Build;
import android.os.PersistableBundle;
import android.support.v4.os.BuildCompat;
import android.util.Log;
import com.afwsamples.testdpc.AddAccountActivity;
import com.afwsamples.testdpc.EnableDeviceOwnerActivity;
import com.afwsamples.testdpc.EnableProfileActivity;
import com.afwsamples.testdpc.FirstAccountReadyBroadcastReceiver;
import com.afwsamples.testdpc.common.LaunchIntentUtil;
import com.afwsamples.testdpc.common.Util;
import com.afwsamples.testdpc.cosu.EnableCosuActivity;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Task executed after provisioning is done indicated by either the
* {@link DevicePolicyManager#ACTION_PROVISIONING_SUCCESSFUL} activity intent or the
* {@link android.app.admin.DeviceAdminReceiver#onProfileProvisioningComplete(Context, Intent)}
* broadcast.
*
* <p>Operations performed:
* <ul>
* <li>self-grant all run-time permissions</li>
* <li>enable the launcher activity</li>
* <li>start waiting for first account ready broadcast</li>
* </ul>
*/
public class PostProvisioningTask {
private static final String TAG = "PostProvisioningTask";
private static final String SETUP_MANAGEMENT_LAUNCH_ACTIVITY =
"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 final Context mContext;
private final DevicePolicyManager mDevicePolicyManager;
private final SharedPreferences mSharedPrefs;
public PostProvisioningTask(Context context) {
mContext = context;
mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
mSharedPrefs = context.getSharedPreferences(POST_PROV_PREFS, Context.MODE_PRIVATE);
}
public boolean performPostProvisioningOperations(Intent intent) {
if (isPostProvisioningDone()) {
return false;
}
markPostProvisioningDone();
// From M onwards, permissions are not auto-granted, so we need to manually grant
// permissions for TestDPC.
if (Util.isAtLeastM()) {
autoGrantRequestedPermissionsToSelf();
}
// Retreive the admin extras bundle, which we can use to determine the original context for
// TestDPCs launch.
PersistableBundle extras = intent.getParcelableExtra(
EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE);
if (BuildCompat.isAtLeastO()) {
maybeSetAffiliationIds(extras);
}
// Hide the setup launcher when this app is the admin
mContext.getPackageManager().setComponentEnabledSetting(
new ComponentName(mContext, SETUP_MANAGEMENT_LAUNCH_ACTIVITY),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
// Enable first account ready receiver for PO flow. On pre-N devices, the only supported
// PO flow is managed profile. On N+ devices we need to check whether we're running in a
// managed profile.
if (mDevicePolicyManager.isProfileOwnerApp(mContext.getPackageName())
&& (!BuildCompat.isAtLeastN() || Util.isManagedProfile(mContext))) {
FirstAccountReadyBroadcastReceiver.setEnabled(mContext, true);
}
return true;
}
public Intent getPostProvisioningLaunchIntent(Intent intent) {
// Enable the profile after provisioning is complete.
Intent launch;
// Retreive the admin extras bundle, which we can use to determine the original context for
// TestDPCs launch.
PersistableBundle extras = intent.getParcelableExtra(
EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE);
String packageName = mContext.getPackageName();
boolean synchronousAuthLaunch = LaunchIntentUtil.isSynchronousAuthLaunch(extras);
boolean cosuLaunch = LaunchIntentUtil.isCosuLaunch(extras);
boolean isProfileOwner = mDevicePolicyManager.isProfileOwnerApp(packageName);
boolean isDeviceOwner = mDevicePolicyManager.isDeviceOwnerApp(packageName);
// Drop out quickly if we're neither profile or device owner.
if (!isProfileOwner && !isDeviceOwner) {
return null;
}
if (isProfileOwner) {
launch = new Intent(mContext, EnableProfileActivity.class);
} else if (cosuLaunch) {
launch = new Intent(mContext, EnableCosuActivity.class);
launch.putExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE, extras);
} else {
launch = new Intent(mContext, EnableDeviceOwnerActivity.class);
}
if (synchronousAuthLaunch) {
String accountName = LaunchIntentUtil.getAddedAccountName(extras);
if (accountName != null) {
launch.putExtra(LaunchIntentUtil.EXTRA_ACCOUNT_NAME, accountName);
}
}
// For synchronous auth cases, we can assume accounts are already setup (or will be shortly,
// as account migration for Profile Owner is asynchronous). For COSU we don't want to show
// the account option to the user, as no accounts should be added for now.
// In other cases, offer to add an account to the newly configured device/profile.
if (!synchronousAuthLaunch && !cosuLaunch) {
AccountManager accountManager = AccountManager.get(mContext);
Account[] accounts = accountManager.getAccounts();
if (accounts != null && accounts.length == 0) {
// Add account after provisioning is complete.
Intent addAccountIntent = new Intent(mContext, AddAccountActivity.class);
addAccountIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
addAccountIntent.putExtra(AddAccountActivity.EXTRA_NEXT_ACTIVITY_INTENT, launch);
return addAccountIntent;
}
}
launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return launch;
}
private void markPostProvisioningDone() {
mSharedPrefs.edit().putBoolean(KEY_POST_PROV_DONE, true).commit();
}
private boolean isPostProvisioningDone() {
return mSharedPrefs.getBoolean(KEY_POST_PROV_DONE, false);
}
@TargetApi(Build.VERSION_CODES.O)
private void maybeSetAffiliationIds(PersistableBundle extras) {
if (extras == null) {
return;
}
String affiliationId = extras.getString(LaunchIntentUtil.EXTRA_AFFILIATION_ID);
if (affiliationId != null) {
mDevicePolicyManager.setAffiliationIds(getComponentName(mContext),
Arrays.asList(affiliationId));
}
}
@TargetApi(Build.VERSION_CODES.M)
private void autoGrantRequestedPermissionsToSelf() {
String packageName = mContext.getPackageName();
ComponentName adminComponentName = getComponentName(mContext);
List<String> permissions = getRuntimePermissions(mContext.getPackageManager(), packageName);
for (String permission : permissions) {
boolean success = mDevicePolicyManager.setPermissionGrantState(adminComponentName,
packageName, permission, PERMISSION_GRANT_STATE_GRANTED);
Log.d(TAG, "Auto-granting " + permission + ", success: " + success);
if (!success) {
Log.e(TAG, "Failed to auto grant permission to self: " + permission);
}
}
}
private List<String> getRuntimePermissions(PackageManager packageManager, String packageName) {
List<String> permissions = new ArrayList<>();
PackageInfo packageInfo;
try {
packageInfo =
packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Could not retrieve info about the package: " + packageName, e);
return permissions;
}
if (packageInfo != null && packageInfo.requestedPermissions != null) {
for (String requestedPerm : packageInfo.requestedPermissions) {
if (isRuntimePermission(packageManager, requestedPerm)) {
permissions.add(requestedPerm);
}
}
}
return permissions;
}
private boolean isRuntimePermission(PackageManager packageManager, String permission) {
try {
PermissionInfo pInfo = packageManager.getPermissionInfo(permission, 0);
if (pInfo != null) {
if ((pInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
== PermissionInfo.PROTECTION_DANGEROUS) {
return true;
}
}
} catch (PackageManager.NameNotFoundException e) {
Log.i(TAG, "Could not retrieve info about the permission: " + permission);
}
return false;
}
}