• 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_FINANCED_DEVICE;
20 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
21 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
22 import static android.app.admin.DevicePolicyManager.FLAG_SUPPORTED_MODES_DEVICE_OWNER;
23 import static android.app.admin.DevicePolicyManager.FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED;
24 import static android.content.pm.PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS;
25 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
26 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
27 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
28 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
29 
30 import android.annotation.NonNull;
31 import android.annotation.Nullable;
32 import android.annotation.StringRes;
33 import android.app.admin.DevicePolicyManager;
34 import android.content.ComponentName;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.pm.ActivityInfo;
38 import android.content.pm.ApplicationInfo;
39 import android.content.pm.IPackageManager;
40 import android.content.pm.PackageInfo;
41 import android.content.pm.PackageManager;
42 import android.content.pm.PackageManager.NameNotFoundException;
43 import android.content.pm.ResolveInfo;
44 import android.content.pm.UserInfo;
45 import android.content.res.TypedArray;
46 import android.net.ConnectivityManager;
47 import android.net.NetworkCapabilities;
48 import android.net.NetworkInfo;
49 import android.net.wifi.WifiManager;
50 import android.os.Build;
51 import android.os.RemoteException;
52 import android.os.ServiceManager;
53 import android.os.SystemProperties;
54 import android.os.UserHandle;
55 import android.os.UserManager;
56 import android.os.storage.StorageManager;
57 import android.text.SpannableString;
58 import android.text.Spanned;
59 import android.text.TextUtils;
60 import android.text.method.LinkMovementMethod;
61 import android.text.style.ClickableSpan;
62 import android.view.View;
63 import android.view.View.OnClickListener;
64 import android.view.ViewTreeObserver;
65 import android.widget.TextView;
66 
67 import com.android.internal.annotations.VisibleForTesting;
68 import com.android.managedprovisioning.R;
69 import com.android.managedprovisioning.model.CustomizationParams;
70 import com.android.managedprovisioning.model.PackageDownloadInfo;
71 import com.android.managedprovisioning.model.ProvisioningParams;
72 import com.android.managedprovisioning.preprovisioning.WebActivity;
73 
74 import com.google.android.setupcompat.template.FooterBarMixin;
75 import com.google.android.setupcompat.template.FooterButton;
76 import com.google.android.setupcompat.template.FooterButton.ButtonType;
77 import com.google.android.setupdesign.GlifLayout;
78 
79 import java.io.FileInputStream;
80 import java.io.IOException;
81 import java.io.InputStream;
82 import java.security.MessageDigest;
83 import java.security.NoSuchAlgorithmException;
84 import java.util.Arrays;
85 import java.util.HashSet;
86 import java.util.List;
87 import java.util.Objects;
88 import java.util.Set;
89 import java.util.function.Consumer;
90 
91 /**
92  * Class containing various auxiliary methods.
93  */
94 public class Utils {
95     public static final String SHA256_TYPE = "SHA-256";
96 
97     // value chosen to match UX designs; when updating check status bar icon colors
98     private static final int THRESHOLD_BRIGHT_COLOR = 190;
99 
Utils()100     public Utils() {}
101 
102     /**
103      * Returns the system apps currently available to a given user.
104      *
105      * <p>Calls the {@link IPackageManager} to retrieve all system apps available to a user and
106      * returns their package names.
107      *
108      * @param ipm an {@link IPackageManager} object
109      * @param userId the id of the user to check the apps for
110      */
getCurrentSystemApps(IPackageManager ipm, int userId)111     public Set<String> getCurrentSystemApps(IPackageManager ipm, int userId) {
112         Set<String> apps = new HashSet<>();
113         List<ApplicationInfo> aInfos = null;
114         try {
115             aInfos = ipm.getInstalledApplications(
116                     MATCH_UNINSTALLED_PACKAGES | MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, userId)
117                     .getList();
118         } catch (RemoteException neverThrown) {
119             ProvisionLogger.loge("This should not happen.", neverThrown);
120         }
121         for (ApplicationInfo aInfo : aInfos) {
122             if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
123                 apps.add(aInfo.packageName);
124             }
125         }
126         return apps;
127     }
128 
129     /**
130      * Disables a given component in a given user.
131      *
132      * @param toDisable the component that should be disabled
133      * @param userId the id of the user where the component should be disabled.
134      */
disableComponent(ComponentName toDisable, int userId)135     public void disableComponent(ComponentName toDisable, int userId) {
136         setComponentEnabledSetting(
137                 IPackageManager.Stub.asInterface(ServiceManager.getService("package")),
138                 toDisable,
139                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
140                 userId);
141     }
142 
143     /**
144      * Enables a given component in a given user.
145      *
146      * @param toEnable the component that should be enabled
147      * @param userId the id of the user where the component should be disabled.
148      */
enableComponent(ComponentName toEnable, int userId)149     public void enableComponent(ComponentName toEnable, int userId) {
150         setComponentEnabledSetting(
151                 IPackageManager.Stub.asInterface(ServiceManager.getService("package")),
152                 toEnable,
153                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
154                 userId);
155     }
156 
157     /**
158      * Disables a given component in a given user.
159      *
160      * @param ipm an {@link IPackageManager} object
161      * @param toDisable the component that should be disabled
162      * @param userId the id of the user where the component should be disabled.
163      */
164     @VisibleForTesting
setComponentEnabledSetting(IPackageManager ipm, ComponentName toDisable, int enabledSetting, int userId)165     void setComponentEnabledSetting(IPackageManager ipm, ComponentName toDisable,
166             int enabledSetting, int userId) {
167         try {
168             ipm.setComponentEnabledSetting(toDisable,
169                     enabledSetting, PackageManager.DONT_KILL_APP,
170                     userId);
171         } catch (RemoteException neverThrown) {
172             ProvisionLogger.loge("This should not happen.", neverThrown);
173         } catch (Exception e) {
174             ProvisionLogger.logw("Component not found, not changing enabled setting: "
175                 + toDisable.toShortString());
176         }
177     }
178 
179     /**
180      * Check the validity of the admin component name supplied, or try to infer this componentName
181      * from the package.
182      *
183      * We are supporting lookup by package name for legacy reasons.
184      *
185      * If dpcComponentName is supplied (not null): dpcPackageName is ignored.
186      * Check that the package of dpcComponentName is installed, that dpcComponentName is a
187      * receiver in this package, and return it. The receiver can be in disabled state.
188      *
189      * Otherwise: dpcPackageName must be supplied (not null).
190      * Check that this package is installed, try to infer a potential device admin in this package,
191      * and return it.
192      */
193     @NonNull
findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName, Context context, int userId)194     public ComponentName findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName,
195             Context context, int userId) throws IllegalProvisioningArgumentException {
196         if (dpcComponentName != null) {
197             dpcPackageName = dpcComponentName.getPackageName();
198         }
199         if (dpcPackageName == null) {
200             throw new IllegalProvisioningArgumentException("Neither the package name nor the"
201                     + " component name of the admin are supplied");
202         }
203         PackageInfo pi;
204         try {
205             pi = context.getPackageManager().getPackageInfoAsUser(dpcPackageName,
206                     PackageManager.GET_RECEIVERS | PackageManager.MATCH_DISABLED_COMPONENTS,
207                     userId);
208         } catch (NameNotFoundException e) {
209             throw new IllegalProvisioningArgumentException("Dpc " + dpcPackageName
210                     + " is not installed. ", e);
211         }
212 
213         final ComponentName componentName = findDeviceAdminInPackageInfo(dpcPackageName,
214                 dpcComponentName, pi);
215         if (componentName == null) {
216             throw new IllegalProvisioningArgumentException("Cannot find any admin receiver in "
217                     + "package " + dpcPackageName + " with component " + dpcComponentName);
218         }
219         return componentName;
220     }
221 
222     /**
223      * If dpcComponentName is not null: dpcPackageName is ignored.
224      * Check that the package of dpcComponentName is installed, that dpcComponentName is a
225      * receiver in this package, and return it. The receiver can be in disabled state.
226      *
227      * Otherwise, try to infer a potential device admin component in this package info.
228      *
229      * @return infered device admin component in package info. Otherwise, null
230      */
231     @Nullable
findDeviceAdminInPackageInfo(@onNull String dpcPackageName, @Nullable ComponentName dpcComponentName, @NonNull PackageInfo pi)232     public ComponentName findDeviceAdminInPackageInfo(@NonNull String dpcPackageName,
233             @Nullable ComponentName dpcComponentName, @NonNull PackageInfo pi) {
234         if (dpcComponentName != null) {
235             if (!isComponentInPackageInfo(dpcComponentName, pi)) {
236                 ProvisionLogger.logw("The component " + dpcComponentName + " isn't registered in "
237                         + "the apk");
238                 return null;
239             }
240             return dpcComponentName;
241         } else {
242             return findDeviceAdminInPackage(dpcPackageName, pi);
243         }
244     }
245 
246     /**
247      * Finds a device admin in a given {@link PackageInfo} object.
248      *
249      * <p>This function returns {@code null} if no or multiple admin receivers were found, and if
250      * the package name does not match dpcPackageName.</p>
251      * @param packageName packge name that should match the {@link PackageInfo} object.
252      * @param packageInfo package info to be examined.
253      * @return admin receiver or null in case of error.
254      */
255     @Nullable
findDeviceAdminInPackage(String packageName, PackageInfo packageInfo)256     private ComponentName findDeviceAdminInPackage(String packageName, PackageInfo packageInfo) {
257         if (packageInfo == null || !TextUtils.equals(packageInfo.packageName, packageName)) {
258             return null;
259         }
260 
261         ComponentName mdmComponentName = null;
262         for (ActivityInfo ai : packageInfo.receivers) {
263             if (TextUtils.equals(ai.permission, android.Manifest.permission.BIND_DEVICE_ADMIN)) {
264                 if (mdmComponentName != null) {
265                     ProvisionLogger.logw("more than 1 device admin component are found");
266                     return null;
267                 } else {
268                     mdmComponentName = new ComponentName(packageName, ai.name);
269                 }
270             }
271         }
272         return mdmComponentName;
273     }
274 
isComponentInPackageInfo(ComponentName dpcComponentName, PackageInfo pi)275     private boolean isComponentInPackageInfo(ComponentName dpcComponentName,
276             PackageInfo pi) {
277         for (ActivityInfo ai : pi.receivers) {
278             if (dpcComponentName.getClassName().equals(ai.name)) {
279                 return true;
280             }
281         }
282         return false;
283     }
284 
285     /**
286      * Return if a given package has testOnly="true", in which case we'll relax certain rules
287      * for CTS.
288      *
289      * The system allows this flag to be changed when an app is updated. But
290      * {@link DevicePolicyManager} uses the persisted version to do actual checks for relevant
291      * dpm command.
292      *
293      * @see DevicePolicyManagerService#isPackageTestOnly for more info
294      */
isPackageTestOnly(PackageManager pm, String packageName, int userHandle)295     public static boolean isPackageTestOnly(PackageManager pm, String packageName, int userHandle) {
296         if (TextUtils.isEmpty(packageName)) {
297             return false;
298         }
299 
300         try {
301             final ApplicationInfo ai = pm.getApplicationInfoAsUser(packageName,
302                     PackageManager.MATCH_DIRECT_BOOT_AWARE
303                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
304             return ai != null && (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0;
305         } catch (PackageManager.NameNotFoundException e) {
306             return false;
307         }
308 
309     }
310 
311     /**
312      * Returns whether the current user is the system user.
313      */
isCurrentUserSystem()314     public boolean isCurrentUserSystem() {
315         return UserHandle.myUserId() == UserHandle.USER_SYSTEM;
316     }
317 
318     /**
319      * Returns whether the device is currently managed.
320      */
isDeviceManaged(Context context)321     public boolean isDeviceManaged(Context context) {
322         DevicePolicyManager dpm =
323                 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
324         return dpm.isDeviceManaged();
325     }
326 
327     /**
328      * Returns true if the given package requires an update.
329      *
330      * <p>There are two cases where an update is required:
331      * 1. The package is not currently present on the device.
332      * 2. The package is present, but the version is below the minimum supported version.
333      *
334      * @param packageName the package to be checked for updates
335      * @param minSupportedVersion the minimum supported version
336      * @param context a {@link Context} object
337      */
packageRequiresUpdate(String packageName, int minSupportedVersion, Context context)338     public boolean packageRequiresUpdate(String packageName, int minSupportedVersion,
339             Context context) {
340         try {
341             PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
342             // Always download packages if no minimum version given.
343             if (minSupportedVersion != PackageDownloadInfo.DEFAULT_MINIMUM_VERSION
344                     && packageInfo.versionCode >= minSupportedVersion) {
345                 return false;
346             }
347         } catch (NameNotFoundException e) {
348             // Package not on device.
349         }
350 
351         return true;
352     }
353 
354     /**
355      * Returns the first existing managed profile if any present, null otherwise.
356      *
357      * <p>Note that we currently only support one managed profile per device.
358      */
359     // TODO: Add unit tests
getManagedProfile(Context context)360     public UserHandle getManagedProfile(Context context) {
361         UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
362         int currentUserId = userManager.getUserHandle();
363         List<UserInfo> userProfiles = userManager.getProfiles(currentUserId);
364         for (UserInfo profile : userProfiles) {
365             if (profile.isManagedProfile()) {
366                 return new UserHandle(profile.id);
367             }
368         }
369         return null;
370     }
371 
372     /**
373      * Returns whether FRP is supported on the device.
374      */
isFrpSupported(Context context)375     public boolean isFrpSupported(Context context) {
376         Object pdbManager = context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
377         return pdbManager != null;
378     }
379 
380 
381     /**
382      * Returns {@code true} if the admin-integrated flow should be performed.
383      *
384      * <p>This method must not be called before the admin app has been installed. If it has not
385      * yet been installed, consider using {@link
386      * #checkAdminIntegratedFlowPreconditions(ProvisioningParams)}.
387      *
388      * <p>To perform the admin-integrated flow, all of the following criteria must be fulfilled:
389      * <ul>
390      *     <li>All of the preconditions in {@link
391      *     #checkAdminIntegratedFlowPreconditions(ProvisioningParams)}</li>
392      *     <li>The DPC has an activity with intent filter with action {@link
393      *     DevicePolicyManager#ACTION_GET_PROVISIONING_MODE}</li>
394      *     <li>The DPC has an activity with intent filter with action {@link
395      *     DevicePolicyManager#ACTION_ADMIN_POLICY_COMPLIANCE}</li>
396      * </ul>
397      */
canPerformAdminIntegratedFlow(Context context, ProvisioningParams params, PolicyComplianceUtils policyComplianceUtils, GetProvisioningModeUtils provisioningModeUtils)398     public boolean canPerformAdminIntegratedFlow(Context context, ProvisioningParams params,
399             PolicyComplianceUtils policyComplianceUtils,
400             GetProvisioningModeUtils provisioningModeUtils) {
401         if (!checkAdminIntegratedFlowPreconditions(params)) {
402             return false;
403         }
404         boolean isPolicyComplianceScreenAvailable =
405                 policyComplianceUtils.isPolicyComplianceActivityResolvableForUser(context, params,
406                         this, UserHandle.SYSTEM);
407         if (!isPolicyComplianceScreenAvailable) {
408             ProvisionLogger.logi("Policy compliance DPC screen not available.");
409             return false;
410         }
411         boolean isGetProvisioningModeScreenAvailable =
412                 provisioningModeUtils.isGetProvisioningModeActivityResolvable(context, params);
413         if (!isGetProvisioningModeScreenAvailable) {
414             ProvisionLogger.logi("Get provisioning mode DPC screen not available.");
415             return false;
416         }
417         return true;
418     }
419 
420     /**
421      * Returns {@code true} if the admin-integrated flow preconditions are met.
422      *
423      * <p>This method can be called before the admin app has been installed. Returning {@code true}
424      * does not mean the admin-integrated flow should be performed (for that, use {@link
425      * #canPerformAdminIntegratedFlow(Context, ProvisioningParams, PolicyComplianceUtils,
426      * GetProvisioningModeUtils)}), but returning {@code false} can be used as an early indication
427      * that it should <i>not</i> be performed.
428      *
429      * <p>The preconditions are:
430      * <ul>
431      *     <li>Provisioning was started using {@link
432      *     DevicePolicyManager#ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}</li>
433      *     <li>The provisioning is not triggered by NFC</li>
434      *     <li>This is not a financed device provisioning</li>
435      * </ul>
436      */
checkAdminIntegratedFlowPreconditions(ProvisioningParams params)437     public boolean checkAdminIntegratedFlowPreconditions(ProvisioningParams params) {
438         if (params.isNfc) {
439             ProvisionLogger.logi("NFC provisioning");
440             return false;
441         }
442         if (isFinancedDeviceAction(params.provisioningAction)) {
443             ProvisionLogger.logi("Financed device provisioning");
444             return false;
445         }
446         if (!params.startedByTrustedSource) {
447             ProvisionLogger.logi("Provisioning not started by a trusted source");
448             return false;
449         }
450         return true;
451     }
452 
453     /**
454      * Factory resets the device.
455      */
factoryReset(Context context, String reason)456     public void factoryReset(Context context, String reason) {
457         context.getSystemService(DevicePolicyManager.class).wipeData(/* flags=*/ 0, reason);
458     }
459 
460     /**
461      * Returns whether the given provisioning action is a profile owner action.
462      */
463     // TODO: Move the list of device owner actions into a Globals class.
isProfileOwnerAction(String action)464     public final boolean isProfileOwnerAction(String action) {
465         return ACTION_PROVISION_MANAGED_PROFILE.equals(action);
466     }
467 
468     /**
469      * Returns whether the given provisioning action is a device owner action.
470      */
471     // TODO: Move the list of device owner actions into a Globals class.
isDeviceOwnerAction(String action)472     public final boolean isDeviceOwnerAction(String action) {
473         return ACTION_PROVISION_MANAGED_DEVICE.equals(action);
474     }
475 
476     /**
477      * Returns whether the given provisioning action is a financed device action.
478      */
isFinancedDeviceAction(String action)479     public final boolean isFinancedDeviceAction(String action) {
480         return ACTION_PROVISION_FINANCED_DEVICE.equals(action);
481     }
482 
483     /**
484      * Returns whether the device currently has connectivity.
485      */
isConnectedToNetwork(Context context)486     public boolean isConnectedToNetwork(Context context) {
487         NetworkInfo info = getActiveNetworkInfo(context);
488         return info != null && info.isConnected();
489     }
490 
isMobileNetworkConnectedToInternet(Context context)491     public boolean isMobileNetworkConnectedToInternet(Context context) {
492         final ConnectivityManager connectivityManager =
493                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
494         return Arrays.stream(connectivityManager.getAllNetworks())
495                 .filter(network -> {
496                     return Objects.nonNull(connectivityManager.getNetworkCapabilities(network));
497                 })
498                 .map(connectivityManager::getNetworkCapabilities)
499                 .filter(this::isCellularNetwork)
500                 .anyMatch(this::isConnectedToInternet);
501     }
502 
isConnectedToInternet(NetworkCapabilities capabilities)503     private boolean isConnectedToInternet(NetworkCapabilities capabilities) {
504         return capabilities.hasCapability(NET_CAPABILITY_INTERNET)
505                 && capabilities.hasCapability(NET_CAPABILITY_VALIDATED);
506     }
507 
isCellularNetwork(NetworkCapabilities capabilities)508     private boolean isCellularNetwork(NetworkCapabilities capabilities) {
509         return capabilities.hasTransport(TRANSPORT_CELLULAR);
510     }
511 
512     /**
513      * Returns whether the device is currently connected to specific network type, such as {@link
514      * ConnectivityManager.TYPE_WIFI} or {@link ConnectivityManager.TYPE_ETHERNET}
515      *
516      * {@see ConnectivityManager}
517      */
isNetworkTypeConnected(Context context, int... types)518     public boolean isNetworkTypeConnected(Context context, int... types) {
519         final NetworkInfo networkInfo = getActiveNetworkInfo(context);
520         if (networkInfo != null && networkInfo.isConnected()) {
521             final int activeNetworkType = networkInfo.getType();
522             for (int type : types) {
523                 if (activeNetworkType == type) {
524                     return true;
525                 }
526             }
527         }
528         return false;
529     }
530 
531     /**
532      * Returns the active network info of the device.
533      */
getActiveNetworkInfo(Context context)534     public NetworkInfo getActiveNetworkInfo(Context context) {
535         ConnectivityManager cm =
536                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
537         return cm.getActiveNetworkInfo();
538     }
539 
540     /**
541      * Returns whether encryption is required on this device.
542      *
543      * <p>Encryption is required if the device is not currently encrypted and the persistent
544      * system flag {@code persist.sys.no_req_encrypt} is not set.
545      */
isEncryptionRequired()546     public boolean isEncryptionRequired() {
547         return !isPhysicalDeviceEncrypted()
548                 && !SystemProperties.getBoolean("persist.sys.no_req_encrypt", false);
549     }
550 
551     /**
552      * Returns whether the device is currently encrypted.
553      */
isPhysicalDeviceEncrypted()554     public boolean isPhysicalDeviceEncrypted() {
555         return StorageManager.isEncrypted();
556     }
557 
558     /**
559      * Returns the wifi pick intent.
560      */
561     // TODO: Move this intent into a Globals class.
getWifiPickIntent()562     public Intent getWifiPickIntent() {
563         Intent wifiIntent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK);
564         wifiIntent.putExtra("extra_prefs_show_button_bar", true);
565         wifiIntent.putExtra("wifi_enable_next_on_connect", true);
566         return wifiIntent;
567     }
568 
569     /**
570      * Returns whether the device is in headless system user mode.
571      */
isHeadlessSystemUserMode()572     public boolean isHeadlessSystemUserMode() {
573         return UserManager.isHeadlessSystemUserMode();
574     }
575 
576     /**
577      * Returns whether the currently chosen launcher supports managed profiles.
578      *
579      * <p>A launcher is deemed to support managed profiles when its target API version is at least
580      * {@link Build.VERSION_CODES#LOLLIPOP}.
581      */
currentLauncherSupportsManagedProfiles(Context context)582     public boolean currentLauncherSupportsManagedProfiles(Context context) {
583         Intent intent = new Intent(Intent.ACTION_MAIN);
584         intent.addCategory(Intent.CATEGORY_HOME);
585 
586         PackageManager pm = context.getPackageManager();
587         ResolveInfo launcherResolveInfo = pm.resolveActivity(intent,
588                 PackageManager.MATCH_DEFAULT_ONLY);
589         if (launcherResolveInfo == null) {
590             return false;
591         }
592         try {
593             // If the user has not chosen a default launcher, then launcherResolveInfo will be
594             // referring to the resolver activity. It is fine to create a managed profile in
595             // this case since there will always be at least one launcher on the device that
596             // supports managed profile feature.
597             ApplicationInfo launcherAppInfo = pm.getApplicationInfo(
598                     launcherResolveInfo.activityInfo.packageName, 0 /* default flags */);
599             return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion);
600         } catch (PackageManager.NameNotFoundException e) {
601             return false;
602         }
603     }
604 
605     /**
606      * Returns whether the given version number is at least lollipop.
607      *
608      * @param versionNumber the version number to be verified.
609      */
versionNumberAtLeastL(int versionNumber)610     private boolean versionNumberAtLeastL(int versionNumber) {
611         return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
612     }
613 
614     /**
615      * Computes the sha 256 hash of a byte array.
616      */
617     @Nullable
computeHashOfByteArray(byte[] bytes)618     public byte[] computeHashOfByteArray(byte[] bytes) {
619         try {
620             MessageDigest md = MessageDigest.getInstance(SHA256_TYPE);
621             md.update(bytes);
622             return md.digest();
623         } catch (NoSuchAlgorithmException e) {
624             ProvisionLogger.loge("Hashing algorithm " + SHA256_TYPE + " not supported.", e);
625             return null;
626         }
627     }
628 
629     /**
630      * Computes a hash of a file with a spcific hash algorithm.
631      */
632     // TODO: Add unit tests
633     @Nullable
computeHashOfFile(String fileLocation, String hashType)634     public byte[] computeHashOfFile(String fileLocation, String hashType) {
635         InputStream fis = null;
636         MessageDigest md;
637         byte hash[] = null;
638         try {
639             md = MessageDigest.getInstance(hashType);
640         } catch (NoSuchAlgorithmException e) {
641             ProvisionLogger.loge("Hashing algorithm " + hashType + " not supported.", e);
642             return null;
643         }
644         try {
645             fis = new FileInputStream(fileLocation);
646 
647             byte[] buffer = new byte[256];
648             int n = 0;
649             while (n != -1) {
650                 n = fis.read(buffer);
651                 if (n > 0) {
652                     md.update(buffer, 0, n);
653                 }
654             }
655             hash = md.digest();
656         } catch (IOException e) {
657             ProvisionLogger.loge("IO error.", e);
658         } finally {
659             // Close input stream quietly.
660             try {
661                 if (fis != null) {
662                     fis.close();
663                 }
664             } catch (IOException e) {
665                 // Ignore.
666             }
667         }
668         return hash;
669     }
670 
671     /**
672      * Returns whether given intent can be resolved for the user.
673      */
canResolveIntentAsUser(Context context, Intent intent, int userId)674     public boolean canResolveIntentAsUser(Context context, Intent intent, int userId) {
675         return intent != null
676                 && context.getPackageManager().resolveActivityAsUser(intent, 0, userId) != null;
677     }
678 
isPackageDeviceOwner(DevicePolicyManager dpm, String packageName)679     public boolean isPackageDeviceOwner(DevicePolicyManager dpm, String packageName) {
680         final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnCallingUser();
681         return deviceOwner != null && deviceOwner.getPackageName().equals(packageName);
682     }
683 
getAccentColor(Context context)684     public int getAccentColor(Context context) {
685         return getAttrColor(context, android.R.attr.colorAccent);
686     }
687 
688     /**
689      * Returns the theme's background color.
690      */
getBackgroundColor(Context context)691     public int getBackgroundColor(Context context) {
692         return getAttrColor(context, android.R.attr.colorBackground);
693     }
694 
695     /**
696      * Returns the theme's text primary color.
697      */
getTextPrimaryColor(Context context)698     public int getTextPrimaryColor(Context context) {
699         return getAttrColor(context, android.R.attr.textColorPrimary);
700     }
701 
702     /**
703      * Returns the theme's text secondary color.
704      */
getTextSecondaryColor(Context context)705     public int getTextSecondaryColor(Context context) {
706         return getAttrColor(context, android.R.attr.textColorSecondary);
707     }
708 
getAttrColor(Context context, int attr)709     private int getAttrColor(Context context, int attr) {
710         TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
711         int attrColor = ta.getColor(0, 0);
712         ta.recycle();
713         return attrColor;
714     }
715 
handleSupportUrl(Context context, CustomizationParams customizationParams, AccessibilityContextMenuMaker contextMenuMaker, TextView textView, String deviceProvider, String contactDeviceProvider, Consumer<Intent> clickHandler)716     public void handleSupportUrl(Context context, CustomizationParams customizationParams,
717             AccessibilityContextMenuMaker contextMenuMaker, TextView textView,
718             String deviceProvider, String contactDeviceProvider,
719             Consumer<Intent> clickHandler) {
720         if (customizationParams.supportUrl == null) {
721             textView.setText(contactDeviceProvider);
722             return;
723         }
724         final Intent intent = WebActivity.createIntent(
725                 context, customizationParams.supportUrl);
726 
727         final ClickableSpanFactory spanFactory =
728                 new ClickableSpanFactory(getAccentColor(context), clickHandler);
729         handlePartialClickableTextView(
730                 textView, contactDeviceProvider, deviceProvider, intent, spanFactory);
731 
732         contextMenuMaker.registerWithActivity(textView);
733     }
734 
735     /**
736      * Utility function to make a TextView partial clickable. It also associates the TextView with
737      * an Intent. The intent will be triggered when the clickable part is clicked.
738      *
739      * @param textView The TextView which hosts the clickable string.
740      * @param content The content of the TextView.
741      * @param clickableString The substring which is clickable.
742      * @param intent The Intent that will be launched.
743      * @param clickableSpanFactory The factory which is used to create ClickableSpan to decorate
744      *                             clickable string.
745      */
handlePartialClickableTextView(TextView textView, String content, String clickableString, Intent intent, ClickableSpanFactory clickableSpanFactory)746     public void handlePartialClickableTextView(TextView textView, String content,
747             String clickableString, Intent intent, ClickableSpanFactory clickableSpanFactory) {
748         final SpannableString spannableString = new SpannableString(content);
749         if (intent != null) {
750             final ClickableSpan span = clickableSpanFactory.create(intent);
751             final int startIdx = content.indexOf(clickableString);
752             final int endIdx = startIdx + clickableString.length();
753 
754             spannableString.setSpan(span, startIdx, endIdx, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
755             textView.setMovementMethod(LinkMovementMethod.getInstance());
756         }
757 
758         textView.setText(spannableString);
759     }
760 
761     /**
762      * Gets the device's current device owner admin component.
763      */
764     @Nullable
getCurrentDeviceOwnerComponentName(DevicePolicyManager dpm)765     public ComponentName getCurrentDeviceOwnerComponentName(DevicePolicyManager dpm) {
766         return isHeadlessSystemUserMode()
767                 ? dpm.getDeviceOwnerComponentOnAnyUser()
768                 : dpm.getDeviceOwnerComponentOnCallingUser();
769     }
770 
addNextButton(GlifLayout layout, @NonNull OnClickListener listener)771     public static FooterButton addNextButton(GlifLayout layout, @NonNull OnClickListener listener) {
772         return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.next);
773     }
774 
775     /**
776      * Adds an encryption primary button mixin to a {@link GlifLayout} screen.
777      */
addEncryptButton( GlifLayout layout, @NonNull OnClickListener listener)778     public static FooterButton addEncryptButton(
779             GlifLayout layout, @NonNull OnClickListener listener) {
780         return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.encrypt);
781     }
782 
addAcceptAndContinueButton(GlifLayout layout, @NonNull OnClickListener listener)783     public static FooterButton addAcceptAndContinueButton(GlifLayout layout,
784         @NonNull OnClickListener listener) {
785         return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.accept_and_continue);
786     }
787 
788     /** Adds a primary "Cancel setup" button */
addResetButton(GlifLayout layout, @NonNull OnClickListener listener)789     public static FooterButton addResetButton(GlifLayout layout,
790             @NonNull OnClickListener listener) {
791         return setPrimaryButton(layout, listener, ButtonType.CANCEL,
792                 R.string.fully_managed_device_reset_and_return_button);
793     }
794 
setPrimaryButton(GlifLayout layout, OnClickListener listener, @ButtonType int buttonType, @StringRes int label)795     private static FooterButton setPrimaryButton(GlifLayout layout, OnClickListener listener,
796         @ButtonType int buttonType, @StringRes int label) {
797         final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
798         final FooterButton primaryButton = new FooterButton.Builder(layout.getContext())
799             .setText(label)
800             .setListener(listener)
801             .setButtonType(buttonType)
802             .setTheme(R.style.SudGlifButton_Primary)
803             .build();
804         mixin.setPrimaryButton(primaryButton);
805         return primaryButton;
806     }
807 
808     /** Adds a secondary "abort & reset" button. */
addAbortAndResetButton(GlifLayout layout, @NonNull OnClickListener listener)809     public static FooterButton addAbortAndResetButton(GlifLayout layout,
810             @NonNull OnClickListener listener) {
811         final int buttonType = ButtonType.CANCEL;
812         final int buttonLabel = R.string.fully_managed_device_cancel_setup_button;
813 
814         return addSecondaryButton(layout, listener, buttonType, buttonLabel);
815     }
816 
addSecondaryButton(GlifLayout layout, @NonNull OnClickListener listener, @ButtonType int buttonType, @StringRes int buttonLabel)817     private static FooterButton addSecondaryButton(GlifLayout layout,
818             @NonNull OnClickListener listener,
819             @ButtonType int buttonType, @StringRes int buttonLabel) {
820         final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
821         final FooterButton secondaryButton = new FooterButton.Builder(layout.getContext())
822                 .setText(buttonLabel)
823                 .setListener(listener)
824                 .setButtonType(buttonType)
825                 .setTheme(R.style.SudGlifButton_Secondary)
826                 .build();
827         mixin.setSecondaryButton(secondaryButton);
828         return secondaryButton;
829     }
830 
createCancelProvisioningResetDialogBuilder()831     public SimpleDialog.Builder createCancelProvisioningResetDialogBuilder() {
832         final int positiveResId = R.string.reset;
833         final int negativeResId = R.string.device_owner_cancel_cancel;
834         final int dialogMsgResId = R.string.this_will_reset_take_back_first_screen;
835         return getBaseDialogBuilder(positiveResId, negativeResId, dialogMsgResId)
836                 .setTitle(R.string.stop_setup_reset_device_question);
837     }
838 
createCancelProvisioningDialogBuilder()839     public SimpleDialog.Builder createCancelProvisioningDialogBuilder() {
840         final int positiveResId = R.string.profile_owner_cancel_ok;
841         final int negativeResId = R.string.profile_owner_cancel_cancel;
842         final int dialogMsgResId = R.string.profile_owner_cancel_message;
843         return getBaseDialogBuilder(positiveResId, negativeResId, dialogMsgResId);
844     }
845 
shouldShowOwnershipDisclaimerScreen(ProvisioningParams params)846     public boolean shouldShowOwnershipDisclaimerScreen(ProvisioningParams params) {
847         return !params.skipOwnershipDisclaimer;
848     }
849 
isOrganizationOwnedAllowed(ProvisioningParams params)850     public boolean isOrganizationOwnedAllowed(ProvisioningParams params) {
851         int provisioningModes = params.initiatorRequestedProvisioningModes;
852         return containsBinaryFlags(provisioningModes, FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED)
853                 || containsBinaryFlags(provisioningModes, FLAG_SUPPORTED_MODES_DEVICE_OWNER);
854     }
855 
isManagedProfileProvisioningStartedByDpc( Context context, ProvisioningParams params, SettingsFacade settingsFacade)856     public boolean isManagedProfileProvisioningStartedByDpc(
857             Context context,
858             ProvisioningParams params,
859             SettingsFacade settingsFacade) {
860         if (!ACTION_PROVISION_MANAGED_PROFILE.equals(params.provisioningAction)) {
861             return false;
862         }
863         if (params.startedByTrustedSource) {
864             return false;
865         }
866         return settingsFacade.isUserSetupCompleted(context);
867     }
868 
869     /**
870      * Returns {@code true} if {@code packageName} is installed on the primary user.
871      */
isPackageInstalled(String packageName, PackageManager packageManager)872     public boolean isPackageInstalled(String packageName, PackageManager packageManager) {
873         try {
874             final ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
875                     PackageManager.MATCH_DIRECT_BOOT_AWARE
876                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
877             return ai != null;
878         } catch (PackageManager.NameNotFoundException e) {
879             return false;
880         }
881     }
882 
getBaseDialogBuilder( int positiveResId, int negativeResId, int dialogMsgResId)883     private SimpleDialog.Builder getBaseDialogBuilder(
884             int positiveResId, int negativeResId, int dialogMsgResId) {
885         return new SimpleDialog.Builder()
886                 .setCancelable(false)
887                 .setMessage(dialogMsgResId)
888                 .setNegativeButtonMessage(negativeResId)
889                 .setPositiveButtonMessage(positiveResId);
890     }
891 
892     /**
893      * Returns {@code true} if {@code value} contains the {@code flags} binary flags.
894      */
containsBinaryFlags(int value, int flags)895     public boolean containsBinaryFlags(int value, int flags) {
896         return (value & flags) == flags;
897     }
898 
899     /**
900      * Calls {@code callback} when {@code view} has been measured.
901      */
onViewMeasured(View view, Consumer<View> callback)902     public void onViewMeasured(View view, Consumer<View> callback) {
903         view.getViewTreeObserver().addOnGlobalLayoutListener(
904                 new ViewTreeObserver.OnGlobalLayoutListener() {
905                 @Override
906                 public void onGlobalLayout() {
907                     view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
908                     callback.accept(view);
909                 }
910             });
911     }
912 }
913