• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.managedprovisioning.common;
18 
19 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
20 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE;
21 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
22 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE;
23 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
24 import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
25 import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_CLOUD_ENROLLMENT;
26 import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_QR_CODE;
27 import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_UNSPECIFIED;
28 import static android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED;
29 
30 import static com.android.managedprovisioning.common.Globals.ACTION_PROVISION_MANAGED_DEVICE_SILENTLY;
31 import static com.android.managedprovisioning.model.ProvisioningParams.PROVISIONING_MODE_FULLY_MANAGED_DEVICE;
32 import static com.android.managedprovisioning.model.ProvisioningParams.PROVISIONING_MODE_MANAGED_PROFILE;
33 import static com.android.managedprovisioning.model.ProvisioningParams.PROVISIONING_MODE_MANAGED_PROFILE_ON_FULLY_NAMAGED_DEVICE;
34 
35 import android.annotation.WorkerThread;
36 import android.os.Handler;
37 import android.os.Looper;
38 import com.android.managedprovisioning.R;
39 
40 import android.accounts.Account;
41 import android.accounts.AccountManager;
42 import android.accounts.AccountManagerFuture;
43 import android.accounts.AuthenticatorException;
44 import android.accounts.OperationCanceledException;
45 import android.annotation.NonNull;
46 import android.annotation.Nullable;
47 import android.annotation.StringRes;
48 import android.app.admin.DevicePolicyManager;
49 import android.content.ComponentName;
50 import android.content.Context;
51 import android.content.Intent;
52 import android.content.pm.ActivityInfo;
53 import android.content.pm.ApplicationInfo;
54 import android.content.pm.IPackageManager;
55 import android.content.pm.PackageInfo;
56 import android.content.pm.PackageManager;
57 import android.content.pm.PackageManager.NameNotFoundException;
58 import android.content.pm.ResolveInfo;
59 import android.content.pm.UserInfo;
60 import android.content.res.TypedArray;
61 import android.graphics.Color;
62 import android.net.ConnectivityManager;
63 import android.net.NetworkInfo;
64 import android.net.wifi.WifiManager;
65 import android.os.Build;
66 import android.os.Bundle;
67 import android.os.RemoteException;
68 import android.os.ServiceManager;
69 import android.os.SystemProperties;
70 import android.os.UserHandle;
71 import android.os.UserManager;
72 import android.os.storage.StorageManager;
73 import android.text.SpannableString;
74 import android.text.Spanned;
75 import android.text.TextUtils;
76 import android.text.method.LinkMovementMethod;
77 import android.text.style.ClickableSpan;
78 import android.view.View.OnClickListener;
79 import android.widget.TextView;
80 
81 import com.android.internal.annotations.VisibleForTesting;
82 import com.android.managedprovisioning.TrampolineActivity;
83 import com.android.managedprovisioning.model.CustomizationParams;
84 import com.android.managedprovisioning.model.PackageDownloadInfo;
85 import com.android.managedprovisioning.model.ProvisioningParams;
86 import com.android.managedprovisioning.preprovisioning.WebActivity;
87 
88 import java.io.FileInputStream;
89 import java.io.IOException;
90 import java.io.InputStream;
91 import java.security.MessageDigest;
92 import java.security.NoSuchAlgorithmException;
93 import java.util.HashSet;
94 import java.util.List;
95 import java.util.Set;
96 
97 import com.google.android.setupdesign.GlifLayout;
98 import com.google.android.setupcompat.template.FooterBarMixin;
99 import com.google.android.setupcompat.template.FooterButton;
100 import com.google.android.setupcompat.template.FooterButton.ButtonType;
101 
102 /**
103  * Class containing various auxiliary methods.
104  */
105 public class Utils {
106     public static final String SHA256_TYPE = "SHA-256";
107 
108     // value chosen to match UX designs; when updating check status bar icon colors
109     private static final int THRESHOLD_BRIGHT_COLOR = 190;
110 
Utils()111     public Utils() {}
112 
113     /**
114      * Returns the system apps currently available to a given user.
115      *
116      * <p>Calls the {@link IPackageManager} to retrieve all system apps available to a user and
117      * returns their package names.
118      *
119      * @param ipm an {@link IPackageManager} object
120      * @param userId the id of the user to check the apps for
121      */
getCurrentSystemApps(IPackageManager ipm, int userId)122     public Set<String> getCurrentSystemApps(IPackageManager ipm, int userId) {
123         Set<String> apps = new HashSet<>();
124         List<ApplicationInfo> aInfos = null;
125         try {
126             aInfos = ipm.getInstalledApplications(
127                     PackageManager.MATCH_UNINSTALLED_PACKAGES, userId).getList();
128         } catch (RemoteException neverThrown) {
129             ProvisionLogger.loge("This should not happen.", neverThrown);
130         }
131         for (ApplicationInfo aInfo : aInfos) {
132             if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
133                 apps.add(aInfo.packageName);
134             }
135         }
136         return apps;
137     }
138 
139     /**
140      * Disables a given component in a given user.
141      *
142      * @param toDisable the component that should be disabled
143      * @param userId the id of the user where the component should be disabled.
144      */
disableComponent(ComponentName toDisable, int userId)145     public void disableComponent(ComponentName toDisable, int userId) {
146         setComponentEnabledSetting(
147                 IPackageManager.Stub.asInterface(ServiceManager.getService("package")),
148                 toDisable,
149                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
150                 userId);
151     }
152 
153     /**
154      * Enables a given component in a given user.
155      *
156      * @param toEnable the component that should be enabled
157      * @param userId the id of the user where the component should be disabled.
158      */
enableComponent(ComponentName toEnable, int userId)159     public void enableComponent(ComponentName toEnable, int userId) {
160         setComponentEnabledSetting(
161                 IPackageManager.Stub.asInterface(ServiceManager.getService("package")),
162                 toEnable,
163                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
164                 userId);
165     }
166 
167     /**
168      * Disables a given component in a given user.
169      *
170      * @param ipm an {@link IPackageManager} object
171      * @param toDisable the component that should be disabled
172      * @param userId the id of the user where the component should be disabled.
173      */
174     @VisibleForTesting
setComponentEnabledSetting(IPackageManager ipm, ComponentName toDisable, int enabledSetting, int userId)175     void setComponentEnabledSetting(IPackageManager ipm, ComponentName toDisable,
176             int enabledSetting, int userId) {
177         try {
178             ipm.setComponentEnabledSetting(toDisable,
179                     enabledSetting, PackageManager.DONT_KILL_APP,
180                     userId);
181         } catch (RemoteException neverThrown) {
182             ProvisionLogger.loge("This should not happen.", neverThrown);
183         } catch (Exception e) {
184             ProvisionLogger.logw("Component not found, not changing enabled setting: "
185                 + toDisable.toShortString());
186         }
187     }
188 
189     /**
190      * Check the validity of the admin component name supplied, or try to infer this componentName
191      * from the package.
192      *
193      * We are supporting lookup by package name for legacy reasons.
194      *
195      * If dpcComponentName is supplied (not null): dpcPackageName is ignored.
196      * Check that the package of dpcComponentName is installed, that dpcComponentName is a
197      * receiver in this package, and return it. The receiver can be in disabled state.
198      *
199      * Otherwise: dpcPackageName must be supplied (not null).
200      * Check that this package is installed, try to infer a potential device admin in this package,
201      * and return it.
202      */
203     @NonNull
findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName, Context context, int userId)204     public ComponentName findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName,
205             Context context, int userId) throws IllegalProvisioningArgumentException {
206         if (dpcComponentName != null) {
207             dpcPackageName = dpcComponentName.getPackageName();
208         }
209         if (dpcPackageName == null) {
210             throw new IllegalProvisioningArgumentException("Neither the package name nor the"
211                     + " component name of the admin are supplied");
212         }
213         PackageInfo pi;
214         try {
215             pi = context.getPackageManager().getPackageInfoAsUser(dpcPackageName,
216                     PackageManager.GET_RECEIVERS | PackageManager.MATCH_DISABLED_COMPONENTS,
217                     userId);
218         } catch (NameNotFoundException e) {
219             throw new IllegalProvisioningArgumentException("Dpc " + dpcPackageName
220                     + " is not installed. ", e);
221         }
222 
223         final ComponentName componentName = findDeviceAdminInPackageInfo(dpcPackageName,
224                 dpcComponentName, pi);
225         if (componentName == null) {
226             throw new IllegalProvisioningArgumentException("Cannot find any admin receiver in "
227                     + "package " + dpcPackageName + " with component " + dpcComponentName);
228         }
229         return componentName;
230     }
231 
232     /**
233      * If dpcComponentName is not null: dpcPackageName is ignored.
234      * Check that the package of dpcComponentName is installed, that dpcComponentName is a
235      * receiver in this package, and return it. The receiver can be in disabled state.
236      *
237      * Otherwise, try to infer a potential device admin component in this package info.
238      *
239      * @return infered device admin component in package info. Otherwise, null
240      */
241     @Nullable
findDeviceAdminInPackageInfo(@onNull String dpcPackageName, @Nullable ComponentName dpcComponentName, @NonNull PackageInfo pi)242     public ComponentName findDeviceAdminInPackageInfo(@NonNull String dpcPackageName,
243             @Nullable ComponentName dpcComponentName, @NonNull PackageInfo pi) {
244         if (dpcComponentName != null) {
245             if (!isComponentInPackageInfo(dpcComponentName, pi)) {
246                 ProvisionLogger.logw("The component " + dpcComponentName + " isn't registered in "
247                         + "the apk");
248                 return null;
249             }
250             return dpcComponentName;
251         } else {
252             return findDeviceAdminInPackage(dpcPackageName, pi);
253         }
254     }
255 
256     /**
257      * Finds a device admin in a given {@link PackageInfo} object.
258      *
259      * <p>This function returns {@code null} if no or multiple admin receivers were found, and if
260      * the package name does not match dpcPackageName.</p>
261      * @param packageName packge name that should match the {@link PackageInfo} object.
262      * @param packageInfo package info to be examined.
263      * @return admin receiver or null in case of error.
264      */
265     @Nullable
findDeviceAdminInPackage(String packageName, PackageInfo packageInfo)266     private ComponentName findDeviceAdminInPackage(String packageName, PackageInfo packageInfo) {
267         if (packageInfo == null || !TextUtils.equals(packageInfo.packageName, packageName)) {
268             return null;
269         }
270 
271         ComponentName mdmComponentName = null;
272         for (ActivityInfo ai : packageInfo.receivers) {
273             if (TextUtils.equals(ai.permission, android.Manifest.permission.BIND_DEVICE_ADMIN)) {
274                 if (mdmComponentName != null) {
275                     ProvisionLogger.logw("more than 1 device admin component are found");
276                     return null;
277                 } else {
278                     mdmComponentName = new ComponentName(packageName, ai.name);
279                 }
280             }
281         }
282         return mdmComponentName;
283     }
284 
isComponentInPackageInfo(ComponentName dpcComponentName, PackageInfo pi)285     private boolean isComponentInPackageInfo(ComponentName dpcComponentName,
286             PackageInfo pi) {
287         for (ActivityInfo ai : pi.receivers) {
288             if (dpcComponentName.getClassName().equals(ai.name)) {
289                 return true;
290             }
291         }
292         return false;
293     }
294 
295     /**
296      * Return if a given package has testOnly="true", in which case we'll relax certain rules
297      * for CTS.
298      *
299      * The system allows this flag to be changed when an app is updated. But
300      * {@link DevicePolicyManager} uses the persisted version to do actual checks for relevant
301      * dpm command.
302      *
303      * @see DevicePolicyManagerService#isPackageTestOnly for more info
304      */
isPackageTestOnly(PackageManager pm, String packageName, int userHandle)305     public static boolean isPackageTestOnly(PackageManager pm, String packageName, int userHandle) {
306         if (TextUtils.isEmpty(packageName)) {
307             return false;
308         }
309 
310         try {
311             final ApplicationInfo ai = pm.getApplicationInfoAsUser(packageName,
312                     PackageManager.MATCH_DIRECT_BOOT_AWARE
313                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
314             return ai != null && (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0;
315         } catch (PackageManager.NameNotFoundException e) {
316             return false;
317         }
318 
319     }
320 
321     /**
322      * Returns whether the current user is the system user.
323      */
isCurrentUserSystem()324     public boolean isCurrentUserSystem() {
325         return UserHandle.myUserId() == UserHandle.USER_SYSTEM;
326     }
327 
328     /**
329      * Returns whether the device is currently managed.
330      */
isDeviceManaged(Context context)331     public boolean isDeviceManaged(Context context) {
332         DevicePolicyManager dpm =
333                 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
334         return dpm.isDeviceManaged();
335     }
336 
337     /**
338      * Returns true if the given package requires an update.
339      *
340      * <p>There are two cases where an update is required:
341      * 1. The package is not currently present on the device.
342      * 2. The package is present, but the version is below the minimum supported version.
343      *
344      * @param packageName the package to be checked for updates
345      * @param minSupportedVersion the minimum supported version
346      * @param context a {@link Context} object
347      */
packageRequiresUpdate(String packageName, int minSupportedVersion, Context context)348     public boolean packageRequiresUpdate(String packageName, int minSupportedVersion,
349             Context context) {
350         try {
351             PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
352             // Always download packages if no minimum version given.
353             if (minSupportedVersion != PackageDownloadInfo.DEFAULT_MINIMUM_VERSION
354                     && packageInfo.versionCode >= minSupportedVersion) {
355                 return false;
356             }
357         } catch (NameNotFoundException e) {
358             // Package not on device.
359         }
360 
361         return true;
362     }
363 
364     /**
365      * Returns the first existing managed profile if any present, null otherwise.
366      *
367      * <p>Note that we currently only support one managed profile per device.
368      */
369     // TODO: Add unit tests
getManagedProfile(Context context)370     public UserHandle getManagedProfile(Context context) {
371         UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
372         int currentUserId = userManager.getUserHandle();
373         List<UserInfo> userProfiles = userManager.getProfiles(currentUserId);
374         for (UserInfo profile : userProfiles) {
375             if (profile.isManagedProfile()) {
376                 return new UserHandle(profile.id);
377             }
378         }
379         return null;
380     }
381 
382     /**
383      * Returns the user id of an already existing managed profile or -1 if none exists.
384      */
385     // TODO: Add unit tests
alreadyHasManagedProfile(Context context)386     public int alreadyHasManagedProfile(Context context) {
387         UserHandle managedUser = getManagedProfile(context);
388         if (managedUser != null) {
389             return managedUser.getIdentifier();
390         } else {
391             return -1;
392         }
393     }
394 
395     /**
396      * Removes an account asynchronously.
397      *
398      * @see #removeAccount(Context, Account)
399      */
removeAccountAsync(Context context, Account accountToRemove, RemoveAccountListener callback)400     public void removeAccountAsync(Context context, Account accountToRemove,
401             RemoveAccountListener callback) {
402         new RemoveAccountAsyncTask(context, accountToRemove, this, callback).execute();
403     }
404 
405     /**
406      * Removes an account synchronously.
407      *
408      * This method is blocking and must never be called from the main thread.
409      *
410      * <p>This removes the given account from the calling user's list of accounts.
411      *
412      * @param context a {@link Context} object
413      * @param account the account to be removed
414      */
415     // TODO: Add unit tests
416     @WorkerThread
removeAccount(Context context, Account account)417     void removeAccount(Context context, Account account) {
418         final AccountManager accountManager =
419                 (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
420         final AccountManagerFuture<Bundle> bundle = accountManager.removeAccount(account,
421                 null, null /* callback */, null /* handler */);
422         // Block to get the result of the removeAccount operation
423         try {
424             final Bundle result = bundle.getResult();
425             if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, /* default */ false)) {
426                 ProvisionLogger.logw("Account removed from the primary user.");
427             } else {
428                 final Intent removeIntent = result.getParcelable(AccountManager.KEY_INTENT);
429                 if (removeIntent != null) {
430                     ProvisionLogger.logi("Starting activity to remove account");
431                     new Handler(Looper.getMainLooper()).post(() -> {
432                         TrampolineActivity.startActivity(context, removeIntent);
433                     });
434                 } else {
435                     ProvisionLogger.logw("Could not remove account from the primary user.");
436                 }
437             }
438         } catch (OperationCanceledException | AuthenticatorException | IOException e) {
439             ProvisionLogger.logw("Exception removing account from the primary user.", e);
440         }
441     }
442 
443     /**
444      * Returns whether FRP is supported on the device.
445      */
isFrpSupported(Context context)446     public boolean isFrpSupported(Context context) {
447         Object pdbManager = context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
448         return pdbManager != null;
449     }
450 
451     /**
452      * Translates a given managed provisioning intent to its corresponding provisioning flow, using
453      * the action from the intent.
454      *
455      * <p/>This is necessary because, unlike other provisioning actions which has 1:1 mapping, there
456      * are multiple actions that can trigger the device owner provisioning flow. This includes
457      * {@link ACTION_PROVISION_MANAGED_DEVICE}, {@link ACTION_NDEF_DISCOVERED} and
458      * {@link ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}. These 3 actions are equivalent
459      * excepts they are sent from a different source.
460      *
461      * @return the appropriate DevicePolicyManager declared action for the given incoming intent.
462      * @throws IllegalProvisioningArgumentException if intent is malformed
463      */
464     // TODO: Add unit tests
mapIntentToDpmAction(Intent intent)465     public String mapIntentToDpmAction(Intent intent)
466             throws IllegalProvisioningArgumentException {
467         if (intent == null || intent.getAction() == null) {
468             throw new IllegalProvisioningArgumentException("Null intent action.");
469         }
470 
471         // Map the incoming intent to a DevicePolicyManager.ACTION_*, as there is a N:1 mapping in
472         // some cases.
473         String dpmProvisioningAction;
474         switch (intent.getAction()) {
475             // Trivial cases.
476             case ACTION_PROVISION_MANAGED_DEVICE:
477             case ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE:
478             case ACTION_PROVISION_MANAGED_USER:
479             case ACTION_PROVISION_MANAGED_PROFILE:
480                 dpmProvisioningAction = intent.getAction();
481                 break;
482 
483             // Silent device owner is same as device owner.
484             case ACTION_PROVISION_MANAGED_DEVICE_SILENTLY:
485                 dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE;
486                 break;
487 
488             // NFC cases which need to take mime-type into account.
489             case ACTION_NDEF_DISCOVERED:
490                 String mimeType = intent.getType();
491                 if (mimeType == null) {
492                     throw new IllegalProvisioningArgumentException(
493                             "Unknown NFC bump mime-type: " + mimeType);
494                 }
495                 switch (mimeType) {
496                     case MIME_TYPE_PROVISIONING_NFC:
497                         dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE;
498                         break;
499 
500                     default:
501                         throw new IllegalProvisioningArgumentException(
502                                 "Unknown NFC bump mime-type: " + mimeType);
503                 }
504                 break;
505 
506             // Device owner provisioning from a trusted app.
507             // TODO (b/27217042): review for new management modes in split system-user model
508             case ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE:
509                 dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE;
510                 break;
511 
512             default:
513                 throw new IllegalProvisioningArgumentException("Unknown intent action "
514                         + intent.getAction());
515         }
516         return dpmProvisioningAction;
517     }
518 
isCloudEnrollment(Intent intent)519     public boolean isCloudEnrollment(Intent intent) {
520         return PROVISIONING_TRIGGER_CLOUD_ENROLLMENT ==
521                 intent.getIntExtra(
522                         DevicePolicyManager.EXTRA_PROVISIONING_TRIGGER,
523                         /* defValue= */ PROVISIONING_TRIGGER_UNSPECIFIED);
524     }
525 
526     /**
527      * Returns if the given intent for a organization owned provisioning.
528      * Only QR, cloud enrollment and NFC are owned by organization.
529      */
isOrganizationOwnedProvisioning(Intent intent)530     public boolean isOrganizationOwnedProvisioning(Intent intent) {
531         if (ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
532             return true;
533         }
534         if (!ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE.equals(intent.getAction())) {
535             return false;
536         }
537         //  Do additional check under ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE
538         // in order to exclude force DO.
539         switch (intent.getIntExtra(DevicePolicyManager.EXTRA_PROVISIONING_TRIGGER,
540                 PROVISIONING_TRIGGER_UNSPECIFIED)) {
541             case PROVISIONING_TRIGGER_CLOUD_ENROLLMENT:
542             case PROVISIONING_TRIGGER_QR_CODE:
543                 return true;
544             default:
545                 return false;
546         }
547     }
548 
549     /**
550      * Returns if the given parameter is for provisioning the admin integrated flow.
551      */
isAdminIntegratedFlow(ProvisioningParams params)552     public boolean isAdminIntegratedFlow(ProvisioningParams params) {
553         if (!params.isOrganizationOwnedProvisioning) {
554             return false;
555         }
556         return params.provisioningMode == PROVISIONING_MODE_FULLY_MANAGED_DEVICE
557                 || params.provisioningMode == PROVISIONING_MODE_MANAGED_PROFILE
558                 || params.provisioningMode
559                     == PROVISIONING_MODE_MANAGED_PROFILE_ON_FULLY_NAMAGED_DEVICE;
560     }
561 
562     /**
563      * Sends an intent to trigger a factory reset.
564      */
565     // TODO: Move the FR intent into a Globals class.
sendFactoryResetBroadcast(Context context, String reason)566     public void sendFactoryResetBroadcast(Context context, String reason) {
567         Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
568         // Send explicit broadcast due to Broadcast Limitations
569         intent.setPackage("android");
570         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
571         intent.putExtra(Intent.EXTRA_REASON, reason);
572         context.sendBroadcast(intent);
573     }
574 
575     /**
576      * Returns whether the given provisioning action is a profile owner action.
577      */
578     // TODO: Move the list of device owner actions into a Globals class.
isProfileOwnerAction(String action)579     public final boolean isProfileOwnerAction(String action) {
580         return ACTION_PROVISION_MANAGED_PROFILE.equals(action)
581                 || ACTION_PROVISION_MANAGED_USER.equals(action);
582     }
583 
584     /**
585      * Returns whether the given provisioning action is a device owner action.
586      */
587     // TODO: Move the list of device owner actions into a Globals class.
isDeviceOwnerAction(String action)588     public final boolean isDeviceOwnerAction(String action) {
589         return ACTION_PROVISION_MANAGED_DEVICE.equals(action)
590                 || ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE.equals(action);
591     }
592 
593     /**
594      * Returns whether the device currently has connectivity.
595      */
isConnectedToNetwork(Context context)596     public boolean isConnectedToNetwork(Context context) {
597         NetworkInfo info = getActiveNetworkInfo(context);
598         return info != null && info.isConnected();
599     }
600 
601     /**
602      * Returns whether the device is currently connected to a wifi.
603      */
isConnectedToWifi(Context context)604     public boolean isConnectedToWifi(Context context) {
605         NetworkInfo info = getActiveNetworkInfo(context);
606         return info != null
607                 && info.isConnected()
608                 && info.getType() == ConnectivityManager.TYPE_WIFI;
609     }
610 
611     /**
612      * Returns the active network info of the device.
613      */
getActiveNetworkInfo(Context context)614     public NetworkInfo getActiveNetworkInfo(Context context) {
615         ConnectivityManager cm =
616                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
617         return cm.getActiveNetworkInfo();
618     }
619 
620     /**
621      * Returns whether encryption is required on this device.
622      *
623      * <p>Encryption is required if the device is not currently encrypted and the persistent
624      * system flag {@code persist.sys.no_req_encrypt} is not set.
625      */
isEncryptionRequired()626     public boolean isEncryptionRequired() {
627         return !isPhysicalDeviceEncrypted()
628                 && !SystemProperties.getBoolean("persist.sys.no_req_encrypt", false);
629     }
630 
631     /**
632      * Returns whether the device is currently encrypted.
633      */
isPhysicalDeviceEncrypted()634     public boolean isPhysicalDeviceEncrypted() {
635         return StorageManager.isEncrypted();
636     }
637 
638     /**
639      * Returns the wifi pick intent.
640      */
641     // TODO: Move this intent into a Globals class.
getWifiPickIntent()642     public Intent getWifiPickIntent() {
643         Intent wifiIntent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK);
644         wifiIntent.putExtra("extra_prefs_show_button_bar", true);
645         wifiIntent.putExtra("wifi_enable_next_on_connect", true);
646         return wifiIntent;
647     }
648 
649     /**
650      * Returns whether the device has a split system user.
651      *
652      * <p>Split system user means that user 0 is system only and all meat users are separate from
653      * the system user.
654      */
isSplitSystemUser()655     public boolean isSplitSystemUser() {
656         return UserManager.isSplitSystemUser();
657     }
658 
659     /**
660      * Returns whether the currently chosen launcher supports managed profiles.
661      *
662      * <p>A launcher is deemed to support managed profiles when its target API version is at least
663      * {@link Build.VERSION_CODES#LOLLIPOP}.
664      */
currentLauncherSupportsManagedProfiles(Context context)665     public boolean currentLauncherSupportsManagedProfiles(Context context) {
666         Intent intent = new Intent(Intent.ACTION_MAIN);
667         intent.addCategory(Intent.CATEGORY_HOME);
668 
669         PackageManager pm = context.getPackageManager();
670         ResolveInfo launcherResolveInfo = pm.resolveActivity(intent,
671                 PackageManager.MATCH_DEFAULT_ONLY);
672         if (launcherResolveInfo == null) {
673             return false;
674         }
675         try {
676             // If the user has not chosen a default launcher, then launcherResolveInfo will be
677             // referring to the resolver activity. It is fine to create a managed profile in
678             // this case since there will always be at least one launcher on the device that
679             // supports managed profile feature.
680             ApplicationInfo launcherAppInfo = pm.getApplicationInfo(
681                     launcherResolveInfo.activityInfo.packageName, 0 /* default flags */);
682             return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion);
683         } catch (PackageManager.NameNotFoundException e) {
684             return false;
685         }
686     }
687 
688     /**
689      * Returns whether the given version number is at least lollipop.
690      *
691      * @param versionNumber the version number to be verified.
692      */
versionNumberAtLeastL(int versionNumber)693     private boolean versionNumberAtLeastL(int versionNumber) {
694         return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
695     }
696 
697     /**
698      * Computes the sha 256 hash of a byte array.
699      */
700     @Nullable
computeHashOfByteArray(byte[] bytes)701     public byte[] computeHashOfByteArray(byte[] bytes) {
702         try {
703             MessageDigest md = MessageDigest.getInstance(SHA256_TYPE);
704             md.update(bytes);
705             return md.digest();
706         } catch (NoSuchAlgorithmException e) {
707             ProvisionLogger.loge("Hashing algorithm " + SHA256_TYPE + " not supported.", e);
708             return null;
709         }
710     }
711 
712     /**
713      * Computes a hash of a file with a spcific hash algorithm.
714      */
715     // TODO: Add unit tests
716     @Nullable
computeHashOfFile(String fileLocation, String hashType)717     public byte[] computeHashOfFile(String fileLocation, String hashType) {
718         InputStream fis = null;
719         MessageDigest md;
720         byte hash[] = null;
721         try {
722             md = MessageDigest.getInstance(hashType);
723         } catch (NoSuchAlgorithmException e) {
724             ProvisionLogger.loge("Hashing algorithm " + hashType + " not supported.", e);
725             return null;
726         }
727         try {
728             fis = new FileInputStream(fileLocation);
729 
730             byte[] buffer = new byte[256];
731             int n = 0;
732             while (n != -1) {
733                 n = fis.read(buffer);
734                 if (n > 0) {
735                     md.update(buffer, 0, n);
736                 }
737             }
738             hash = md.digest();
739         } catch (IOException e) {
740             ProvisionLogger.loge("IO error.", e);
741         } finally {
742             // Close input stream quietly.
743             try {
744                 if (fis != null) {
745                     fis.close();
746                 }
747             } catch (IOException e) {
748                 // Ignore.
749             }
750         }
751         return hash;
752     }
753 
isBrightColor(int color)754     public boolean isBrightColor(int color) {
755         // This comes from the YIQ transformation. We're using the formula:
756         // Y = .299 * R + .587 * G + .114 * B
757         return Color.red(color) * 299 + Color.green(color) * 587 + Color.blue(color) * 114
758                 >= 1000 * THRESHOLD_BRIGHT_COLOR;
759     }
760 
761     /**
762      * Returns whether given intent can be resolved for the user.
763      */
canResolveIntentAsUser(Context context, Intent intent, int userId)764     public boolean canResolveIntentAsUser(Context context, Intent intent, int userId) {
765         return intent != null
766                 && context.getPackageManager().resolveActivityAsUser(intent, 0, userId) != null;
767     }
768 
isPackageDeviceOwner(DevicePolicyManager dpm, String packageName)769     public boolean isPackageDeviceOwner(DevicePolicyManager dpm, String packageName) {
770         final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnCallingUser();
771         return deviceOwner != null && deviceOwner.getPackageName().equals(packageName);
772     }
773 
getAccentColor(Context context)774     public int getAccentColor(Context context) {
775         return getAttrColor(context, android.R.attr.colorAccent);
776     }
777 
getAttrColor(Context context, int attr)778     private int getAttrColor(Context context, int attr) {
779         TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
780         int attrColor = ta.getColor(0, 0);
781         ta.recycle();
782         return attrColor;
783     }
784 
handleSupportUrl(Context context, CustomizationParams customizationParams, ClickableSpanFactory clickableSpanFactory, AccessibilityContextMenuMaker contextMenuMaker, TextView textView, String deviceProvider, String contactDeviceProvider)785     public void handleSupportUrl(Context context, CustomizationParams customizationParams,
786                 ClickableSpanFactory clickableSpanFactory,
787                 AccessibilityContextMenuMaker contextMenuMaker, TextView textView,
788                 String deviceProvider, String contactDeviceProvider) {
789         if (customizationParams.supportUrl == null) {
790             textView.setText(contactDeviceProvider);
791             return;
792         }
793         final SpannableString spannableString = new SpannableString(contactDeviceProvider);
794         final Intent intent = WebActivity.createIntent(
795                 context, customizationParams.supportUrl, customizationParams.statusBarColor);
796         if (intent != null) {
797             final ClickableSpan span = clickableSpanFactory.create(intent);
798             final int startIx = contactDeviceProvider.indexOf(deviceProvider);
799             final int endIx = startIx + deviceProvider.length();
800             spannableString.setSpan(span, startIx, endIx, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
801             textView.setMovementMethod(LinkMovementMethod.getInstance()); // make clicks work
802         }
803 
804         textView.setText(spannableString);
805         contextMenuMaker.registerWithActivity(textView);
806     }
807 
isSilentProvisioningForTestingDeviceOwner( Context context, ProvisioningParams params)808     public static boolean isSilentProvisioningForTestingDeviceOwner(
809                 Context context, ProvisioningParams params) {
810         final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
811         final ComponentName currentDeviceOwner =
812                 dpm.getDeviceOwnerComponentOnCallingUser();
813         final ComponentName targetDeviceAdmin = params.deviceAdminComponentName;
814 
815         switch (params.provisioningAction) {
816             case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
817                 return isPackageTestOnly(context, params)
818                         && currentDeviceOwner != null
819                         && targetDeviceAdmin != null
820                         && currentDeviceOwner.equals(targetDeviceAdmin);
821             default:
822                 return false;
823         }
824     }
825 
isSilentProvisioningForTestingManagedProfile( Context context, ProvisioningParams params)826     private static boolean isSilentProvisioningForTestingManagedProfile(
827         Context context, ProvisioningParams params) {
828         return DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE.equals(
829                 params.provisioningAction) && isPackageTestOnly(context, params);
830     }
831 
isSilentProvisioning(Context context, ProvisioningParams params)832     public static boolean isSilentProvisioning(Context context, ProvisioningParams params) {
833         return isSilentProvisioningForTestingManagedProfile(context, params)
834                 || isSilentProvisioningForTestingDeviceOwner(context, params);
835     }
836 
isPackageTestOnly(Context context, ProvisioningParams params)837     private static boolean isPackageTestOnly(Context context, ProvisioningParams params) {
838         final UserManager userManager = context.getSystemService(UserManager.class);
839         return isPackageTestOnly(context.getPackageManager(),
840                 params.inferDeviceAdminPackageName(), userManager.getUserHandle());
841     }
842 
addNextButton(GlifLayout layout, @NonNull OnClickListener listener)843     public static FooterButton addNextButton(GlifLayout layout, @NonNull OnClickListener listener) {
844         return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.next);
845     }
846 
addDoneButton(GlifLayout layout, @NonNull OnClickListener listener)847     public static FooterButton addDoneButton(GlifLayout layout, @NonNull OnClickListener listener) {
848         return setPrimaryButton(layout, listener, ButtonType.DONE, R.string.done);
849     }
850 
addAcceptAndContinueButton(GlifLayout layout, @NonNull OnClickListener listener)851     public static FooterButton addAcceptAndContinueButton(GlifLayout layout,
852         @NonNull OnClickListener listener) {
853         return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.accept_and_continue);
854     }
855 
setPrimaryButton(GlifLayout layout, OnClickListener listener, @ButtonType int buttonType, @StringRes int label)856     private static FooterButton setPrimaryButton(GlifLayout layout, OnClickListener listener,
857         @ButtonType int buttonType, @StringRes int label) {
858         final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
859         final FooterButton primaryButton = new FooterButton.Builder(layout.getContext())
860             .setText(label)
861             .setListener(listener)
862             .setButtonType(buttonType)
863             .setTheme(R.style.SudGlifButton_Primary)
864             .build();
865         mixin.setPrimaryButton(primaryButton);
866         return primaryButton;
867     }
868 
createCancelProvisioningResetDialogBuilder()869     public SimpleDialog.Builder createCancelProvisioningResetDialogBuilder() {
870         final int positiveResId = R.string.reset;
871         final int negativeResId = R.string.device_owner_cancel_cancel;
872         final int dialogMsgResId = R.string.this_will_reset_take_back_first_screen;
873         return getBaseDialogBuilder(positiveResId, negativeResId, dialogMsgResId)
874                 .setTitle(R.string.stop_setup_reset_device_question);
875     }
876 
createCancelProvisioningDialogBuilder()877     public SimpleDialog.Builder createCancelProvisioningDialogBuilder() {
878         final int positiveResId = R.string.profile_owner_cancel_ok;
879         final int negativeResId = R.string.profile_owner_cancel_cancel;
880         final int dialogMsgResId = R.string.profile_owner_cancel_message;
881         return getBaseDialogBuilder(positiveResId, negativeResId, dialogMsgResId);
882     }
883 
getBaseDialogBuilder( int positiveResId, int negativeResId, int dialogMsgResId)884     private SimpleDialog.Builder getBaseDialogBuilder(
885             int positiveResId, int negativeResId, int dialogMsgResId) {
886         return new SimpleDialog.Builder()
887                 .setCancelable(false)
888                 .setMessage(dialogMsgResId)
889                 .setNegativeButtonMessage(negativeResId)
890                 .setPositiveButtonMessage(positiveResId);
891     }
892 }
893