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 
27 import static java.util.Objects.requireNonNull;
28 
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.annotation.StringRes;
32 import android.app.admin.DevicePolicyManager;
33 import android.content.ComponentName;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.pm.ActivityInfo;
37 import android.content.pm.ApplicationInfo;
38 import android.content.pm.IPackageManager;
39 import android.content.pm.PackageInfo;
40 import android.content.pm.PackageManager;
41 import android.content.pm.PackageManager.NameNotFoundException;
42 import android.content.pm.ResolveInfo;
43 import android.content.res.TypedArray;
44 import android.net.ConnectivityManager;
45 import android.net.NetworkCapabilities;
46 import android.net.NetworkInfo;
47 import android.net.wifi.WifiManager;
48 import android.os.Build;
49 import android.os.RemoteException;
50 import android.os.ServiceManager;
51 import android.os.SystemProperties;
52 import android.os.UserHandle;
53 import android.os.UserManager;
54 import android.os.storage.StorageManager;
55 import android.text.SpannableString;
56 import android.text.Spanned;
57 import android.text.TextUtils;
58 import android.text.method.LinkMovementMethod;
59 import android.text.style.ClickableSpan;
60 import android.view.View;
61 import android.view.View.OnClickListener;
62 import android.view.ViewTreeObserver;
63 import android.widget.TextView;
64 
65 import com.android.internal.annotations.VisibleForTesting;
66 import com.android.managedprovisioning.R;
67 import com.android.managedprovisioning.model.CustomizationParams;
68 import com.android.managedprovisioning.model.PackageDownloadInfo;
69 import com.android.managedprovisioning.model.ProvisioningParams;
70 import com.android.managedprovisioning.preprovisioning.WebActivity;
71 import com.android.managedprovisioning.util.LazyStringResource;
72 
73 import com.google.android.setupcompat.template.FooterBarMixin;
74 import com.google.android.setupcompat.template.FooterButton;
75 import com.google.android.setupcompat.template.FooterButton.ButtonType;
76 import com.google.android.setupdesign.GlifLayout;
77 import com.google.android.setupdesign.util.DeviceHelper;
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, "managedprovisioning");
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
194     @VisibleForTesting
findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName, Context context, int userId)195     public ComponentName findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName,
196             Context context, int userId) throws IllegalProvisioningArgumentException {
197         if (dpcComponentName != null) {
198             dpcPackageName = dpcComponentName.getPackageName();
199         }
200         if (dpcPackageName == null) {
201             throw new IllegalProvisioningArgumentException("Neither the package name nor the"
202                     + " component name of the admin are supplied");
203         }
204         PackageInfo pi;
205         try {
206             pi = context.getPackageManager().getPackageInfoAsUser(dpcPackageName,
207                     PackageManager.GET_RECEIVERS | PackageManager.MATCH_DISABLED_COMPONENTS,
208                     userId);
209         } catch (NameNotFoundException e) {
210             throw new IllegalProvisioningArgumentException("Dpc " + dpcPackageName
211                     + " is not installed. ", e);
212         }
213 
214         final ComponentName componentName = findDeviceAdminInPackageInfo(dpcPackageName,
215                 dpcComponentName, pi);
216         if (componentName == null) {
217             throw new IllegalProvisioningArgumentException("Cannot find any admin receiver in "
218                     + "package " + dpcPackageName + " with component " + dpcComponentName);
219         }
220         return componentName;
221     }
222 
223     /**
224      * If dpcComponentName is not null: dpcPackageName is ignored.
225      * Check that the package of dpcComponentName is installed, that dpcComponentName is a
226      * receiver in this package, and return it. The receiver can be in disabled state.
227      *
228      * Otherwise, try to infer a potential device admin component in this package info.
229      *
230      * @return infered device admin component in package info. Otherwise, null
231      */
232     @Nullable
findDeviceAdminInPackageInfo(@onNull String dpcPackageName, @Nullable ComponentName dpcComponentName, @NonNull PackageInfo pi)233     public ComponentName findDeviceAdminInPackageInfo(@NonNull String dpcPackageName,
234             @Nullable ComponentName dpcComponentName, @NonNull PackageInfo pi) {
235         if (dpcComponentName != null) {
236             if (!isComponentInPackageInfo(dpcComponentName, pi)) {
237                 ProvisionLogger.logw("The component " + dpcComponentName + " isn't registered in "
238                         + "the apk");
239                 return null;
240             }
241             return dpcComponentName;
242         } else {
243             return findDeviceAdminInPackage(dpcPackageName, pi);
244         }
245     }
246 
247     /**
248      * Finds a device admin in a given {@link PackageInfo} object.
249      *
250      * <p>This function returns {@code null} if no or multiple admin receivers were found, and if
251      * the package name does not match dpcPackageName.</p>
252      * @param packageName packge name that should match the {@link PackageInfo} object.
253      * @param packageInfo package info to be examined.
254      * @return admin receiver or null in case of error.
255      */
256     @Nullable
findDeviceAdminInPackage(String packageName, PackageInfo packageInfo)257     private ComponentName findDeviceAdminInPackage(String packageName, PackageInfo packageInfo) {
258         if (packageInfo == null || !TextUtils.equals(packageInfo.packageName, packageName)) {
259             return null;
260         }
261 
262         ComponentName mdmComponentName = null;
263         for (ActivityInfo ai : packageInfo.receivers) {
264             if (TextUtils.equals(ai.permission, android.Manifest.permission.BIND_DEVICE_ADMIN)) {
265                 if (mdmComponentName != null) {
266                     ProvisionLogger.logw("more than 1 device admin component are found");
267                     return null;
268                 } else {
269                     mdmComponentName = new ComponentName(packageName, ai.name);
270                 }
271             }
272         }
273         return mdmComponentName;
274     }
275 
isComponentInPackageInfo(ComponentName dpcComponentName, PackageInfo pi)276     private boolean isComponentInPackageInfo(ComponentName dpcComponentName,
277             PackageInfo pi) {
278         for (ActivityInfo ai : pi.receivers) {
279             if (dpcComponentName.getClassName().equals(ai.name)) {
280                 return true;
281             }
282         }
283         return false;
284     }
285 
286     /**
287      * Return if a given package has testOnly="true", in which case we'll relax certain rules
288      * for CTS.
289      *
290      * The system allows this flag to be changed when an app is updated. But
291      * {@link DevicePolicyManager} uses the persisted version to do actual checks for relevant
292      * dpm command.
293      *
294      * @see DevicePolicyManagerService#isPackageTestOnly for more info
295      */
isPackageTestOnly(PackageManager pm, String packageName, int userHandle)296     public static boolean isPackageTestOnly(PackageManager pm, String packageName, int userHandle) {
297         if (TextUtils.isEmpty(packageName)) {
298             return false;
299         }
300 
301         try {
302             final ApplicationInfo ai = pm.getApplicationInfoAsUser(packageName,
303                     PackageManager.MATCH_DIRECT_BOOT_AWARE
304                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
305             return ai != null && (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0;
306         } catch (PackageManager.NameNotFoundException e) {
307             return false;
308         }
309 
310     }
311 
312     /**
313      * Returns whether the current user is the system user.
314      */
isCurrentUserSystem()315     public boolean isCurrentUserSystem() {
316         return UserHandle.myUserId() == UserHandle.USER_SYSTEM;
317     }
318 
319     /**
320      * Returns whether the device is currently managed.
321      */
isDeviceManaged(Context context)322     public boolean isDeviceManaged(Context context) {
323         DevicePolicyManager dpm =
324                 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
325         return dpm.isDeviceManaged();
326     }
327 
328     /**
329      * Returns true if the given package requires an update.
330      *
331      * <p>There are two cases where an update is required:
332      * 1. The package is not currently present on the device.
333      * 2. The package is present, but the version is below the minimum supported version.
334      *
335      * @param packageName the package to be checked for updates
336      * @param minSupportedVersion the minimum supported version
337      * @param context a {@link Context} object
338      */
packageRequiresUpdate(String packageName, int minSupportedVersion, Context context)339     public boolean packageRequiresUpdate(String packageName, int minSupportedVersion,
340             Context context) {
341         try {
342             PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
343             // Always download packages if no minimum version given.
344             if (minSupportedVersion != PackageDownloadInfo.DEFAULT_MINIMUM_VERSION
345                     && packageInfo.versionCode >= minSupportedVersion) {
346                 return false;
347             }
348         } catch (NameNotFoundException e) {
349             // Package not on device.
350         }
351 
352         return true;
353     }
354 
355     /**
356      * Returns the first existing managed profile if any present, null otherwise.
357      *
358      * <p>Note that we currently only support one managed profile per device.
359      */
360     // TODO: Add unit tests
361     @Nullable
getManagedProfile(Context context)362     public UserHandle getManagedProfile(Context context) {
363         DevicePolicyManager devicePolicyManager =
364                 requireNonNull(
365                         /* obj= */ context.getSystemService(DevicePolicyManager.class),
366                         /* message= */ "Unable to obtain DevicePolicyManager");
367         int currentUserId = UserHandle.myUserId();
368         List<UserHandle> managedProfiles =
369                 devicePolicyManager.getPolicyManagedProfiles(UserHandle.of(currentUserId));
370         if (managedProfiles.isEmpty()) {
371             return null;
372         }
373         return managedProfiles.get(0);
374     }
375 
376     /**
377      * Returns whether FRP is supported on the device.
378      */
isFrpSupported(Context context)379     public boolean isFrpSupported(Context context) {
380         Object pdbManager = context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
381         return pdbManager != null;
382     }
383 
384 
385     /**
386      * Returns {@code true} if the admin-integrated flow should be performed.
387      *
388      * <p>This method must not be called before the admin app has been installed. If it has not
389      * yet been installed, consider using {@link
390      * #checkAdminIntegratedFlowPreconditions(ProvisioningParams)}.
391      *
392      * <p>To perform the admin-integrated flow, all of the following criteria must be fulfilled:
393      * <ul>
394      *     <li>All of the preconditions in {@link
395      *     #checkAdminIntegratedFlowPreconditions(ProvisioningParams)}</li>
396      *     <li>The DPC has an activity with intent filter with action {@link
397      *     DevicePolicyManager#ACTION_GET_PROVISIONING_MODE}</li>
398      *     <li>The DPC has an activity with intent filter with action {@link
399      *     DevicePolicyManager#ACTION_ADMIN_POLICY_COMPLIANCE}</li>
400      * </ul>
401      */
canPerformAdminIntegratedFlow(Context context, ProvisioningParams params, PolicyComplianceUtils policyComplianceUtils, GetProvisioningModeUtils provisioningModeUtils)402     public boolean canPerformAdminIntegratedFlow(Context context, ProvisioningParams params,
403             PolicyComplianceUtils policyComplianceUtils,
404             GetProvisioningModeUtils provisioningModeUtils) {
405         if (!checkAdminIntegratedFlowPreconditions(params)) {
406             return false;
407         }
408         boolean isPolicyComplianceScreenAvailable =
409                 policyComplianceUtils.isPolicyComplianceActivityResolvableForUser(context, params,
410                         this, UserHandle.SYSTEM);
411         if (!isPolicyComplianceScreenAvailable) {
412             ProvisionLogger.logi("Policy compliance DPC screen not available.");
413             return false;
414         }
415         boolean isGetProvisioningModeScreenAvailable =
416                 provisioningModeUtils.isGetProvisioningModeActivityResolvable(context, params);
417         if (!isGetProvisioningModeScreenAvailable) {
418             ProvisionLogger.logi("Get provisioning mode DPC screen not available.");
419             return false;
420         }
421         return true;
422     }
423 
424     /**
425      * Returns {@code true} if the admin-integrated flow preconditions are met.
426      *
427      * <p>This method can be called before the admin app has been installed. Returning {@code true}
428      * does not mean the admin-integrated flow should be performed (for that, use {@link
429      * #canPerformAdminIntegratedFlow(Context, ProvisioningParams, PolicyComplianceUtils,
430      * GetProvisioningModeUtils)}), but returning {@code false} can be used as an early indication
431      * that it should <i>not</i> be performed.
432      *
433      * <p>The preconditions are:
434      * <ul>
435      *     <li>Provisioning was started using {@link
436      *     DevicePolicyManager#ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}</li>
437      *     <li>The provisioning is not triggered by NFC</li>
438      *     <li>This is not a financed device provisioning</li>
439      * </ul>
440      */
checkAdminIntegratedFlowPreconditions(ProvisioningParams params)441     public boolean checkAdminIntegratedFlowPreconditions(ProvisioningParams params) {
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).wipeDevice(/* flags=*/ 0);
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                 .map(connectivityManager::getNetworkCapabilities)
496                 .filter(Objects::nonNull)
497                 .anyMatch(this::isNetworkConnectedToInternetViaCellular);
498     }
499 
500     /**
501      * Returns whether the device is currently connected to specific network type, such as
502      * {@link ConnectivityManager#TYPE_WIFI} or {@link ConnectivityManager#TYPE_ETHERNET}
503      *
504      * {@see ConnectivityManager}
505      *
506      * @deprecated use one of
507      * {@link #isNetworkConnectedToInternetViaEthernet(NetworkCapabilities)},
508      * {@link #isNetworkConnectedToInternetViaWiFi(NetworkCapabilities)}
509      * {@link #isNetworkConnectedToInternetViaCellular(NetworkCapabilities)}
510      */
511     @Deprecated
isNetworkTypeConnected(Context context, int... types)512     public boolean isNetworkTypeConnected(Context context, int... types) {
513         final NetworkInfo networkInfo = getActiveNetworkInfo(context);
514         if (networkInfo != null && networkInfo.isConnected()) {
515             final int activeNetworkType = networkInfo.getType();
516             for (int type : types) {
517                 if (activeNetworkType == type) {
518                     return true;
519                 }
520             }
521         }
522         return false;
523     }
524 
525     /**
526      * Checks if the network is active (can receive and send data)
527      */
isNetworkActive(NetworkCapabilities network)528     public boolean isNetworkActive(NetworkCapabilities network) {
529         return network.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
530                 && network.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
531     }
532 
533     /**
534      * Checks if the network has transport {@link NetworkCapabilities#TRANSPORT_ETHERNET} and
535      * {@link #isNetworkActive}
536      */
isNetworkConnectedToInternetViaEthernet(NetworkCapabilities network)537     public boolean isNetworkConnectedToInternetViaEthernet(NetworkCapabilities network) {
538         return network.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
539                 && isNetworkActive(network);
540     }
541 
542     /**
543      * Checks if the network has transport {@link NetworkCapabilities#TRANSPORT_WIFI} and
544      * {@link #isNetworkActive}
545      */
isNetworkConnectedToInternetViaWiFi(NetworkCapabilities network)546     public boolean isNetworkConnectedToInternetViaWiFi(NetworkCapabilities network) {
547         return network.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
548                 && isNetworkActive(network);
549     }
550 
551     /**
552      * Checks if the network has transport {@link NetworkCapabilities#TRANSPORT_CELLULAR} and
553      * {@link #isNetworkActive}
554      */
isNetworkConnectedToInternetViaCellular(NetworkCapabilities network)555     public boolean isNetworkConnectedToInternetViaCellular(NetworkCapabilities network) {
556         return network.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
557                 && isNetworkActive(network);
558     }
559 
560     /**
561      * Returns the active network info of the device.
562      *
563      * @deprecated use {@link #getActiveNetworkCapabilities(Context)}
564      */
565     @Deprecated
getActiveNetworkInfo(Context context)566     public NetworkInfo getActiveNetworkInfo(Context context) {
567         ConnectivityManager cm =
568                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
569         return cm.getActiveNetworkInfo();
570     }
571 
572     /**
573      * Retrieves {@link NetworkCapabilities} of the currently active network
574      * or `null` if there is no active network
575      */
576     @Nullable
getActiveNetworkCapabilities(Context context)577     public NetworkCapabilities getActiveNetworkCapabilities(Context context) {
578         var cn = requireNonNull(context.getSystemService(ConnectivityManager.class),
579                 "Unable to obtain ConnectivityManager");
580         return cn.getNetworkCapabilities(cn.getActiveNetwork());
581     }
582 
583     /**
584      * Returns whether encryption is required on this device.
585      *
586      * <p>Encryption is required if the device is not currently encrypted and the persistent
587      * system flag {@code persist.sys.no_req_encrypt} is not set.
588      */
isEncryptionRequired()589     public boolean isEncryptionRequired() {
590         return !isPhysicalDeviceEncrypted()
591                 && !SystemProperties.getBoolean("persist.sys.no_req_encrypt", false);
592     }
593 
594     /**
595      * Returns whether the device is currently encrypted.
596      */
isPhysicalDeviceEncrypted()597     public boolean isPhysicalDeviceEncrypted() {
598         return StorageManager.isEncrypted();
599     }
600 
601     /**
602      * Returns the wifi pick intent.
603      */
604     // TODO: Move this intent into a Globals class.
getWifiPickIntent()605     public Intent getWifiPickIntent() {
606         Intent wifiIntent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK);
607         wifiIntent.putExtra("extra_prefs_show_button_bar", true);
608         wifiIntent.putExtra("wifi_enable_next_on_connect", true);
609         return wifiIntent;
610     }
611 
612     /**
613      * Returns whether the device is in headless system user mode.
614      */
isHeadlessSystemUserMode()615     public boolean isHeadlessSystemUserMode() {
616         return UserManager.isHeadlessSystemUserMode();
617     }
618 
619     /**
620      * Returns whether the currently chosen launcher supports managed profiles.
621      *
622      * <p>A launcher is deemed to support managed profiles when its target API version is at least
623      * {@link Build.VERSION_CODES#LOLLIPOP}.
624      */
currentLauncherSupportsManagedProfiles(Context context)625     public boolean currentLauncherSupportsManagedProfiles(Context context) {
626         Intent intent = new Intent(Intent.ACTION_MAIN);
627         intent.addCategory(Intent.CATEGORY_HOME);
628 
629         PackageManager pm = context.getPackageManager();
630         ResolveInfo launcherResolveInfo = pm.resolveActivity(intent,
631                 PackageManager.MATCH_DEFAULT_ONLY);
632         if (launcherResolveInfo == null) {
633             return false;
634         }
635         try {
636             // If the user has not chosen a default launcher, then launcherResolveInfo will be
637             // referring to the resolver activity. It is fine to create a managed profile in
638             // this case since there will always be at least one launcher on the device that
639             // supports managed profile feature.
640             ApplicationInfo launcherAppInfo = pm.getApplicationInfo(
641                     launcherResolveInfo.activityInfo.packageName, 0 /* default flags */);
642             return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion);
643         } catch (PackageManager.NameNotFoundException e) {
644             return false;
645         }
646     }
647 
648     /**
649      * Returns whether the given version number is at least lollipop.
650      *
651      * @param versionNumber the version number to be verified.
652      */
versionNumberAtLeastL(int versionNumber)653     private boolean versionNumberAtLeastL(int versionNumber) {
654         return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
655     }
656 
657     /**
658      * Computes the sha 256 hash of a byte array.
659      */
660     @Nullable
computeHashOfByteArray(byte[] bytes)661     public byte[] computeHashOfByteArray(byte[] bytes) {
662         try {
663             MessageDigest md = MessageDigest.getInstance(SHA256_TYPE);
664             md.update(bytes);
665             return md.digest();
666         } catch (NoSuchAlgorithmException e) {
667             ProvisionLogger.loge("Hashing algorithm " + SHA256_TYPE + " not supported.", e);
668             return null;
669         }
670     }
671 
672     /**
673      * Computes a hash of a file with a spcific hash algorithm.
674      */
675     // TODO: Add unit tests
676     @Nullable
computeHashOfFile(String fileLocation, String hashType)677     public byte[] computeHashOfFile(String fileLocation, String hashType) {
678         InputStream fis = null;
679         MessageDigest md;
680         byte[] hash = null;
681         try {
682             md = MessageDigest.getInstance(hashType);
683         } catch (NoSuchAlgorithmException e) {
684             ProvisionLogger.loge("Hashing algorithm " + hashType + " not supported.", e);
685             return null;
686         }
687         try {
688             fis = new FileInputStream(fileLocation);
689 
690             byte[] buffer = new byte[256];
691             int n = 0;
692             while (n != -1) {
693                 n = fis.read(buffer);
694                 if (n > 0) {
695                     md.update(buffer, 0, n);
696                 }
697             }
698             hash = md.digest();
699         } catch (IOException e) {
700             ProvisionLogger.loge("IO error.", e);
701         } finally {
702             // Close input stream quietly.
703             try {
704                 if (fis != null) {
705                     fis.close();
706                 }
707             } catch (IOException e) {
708                 // Ignore.
709             }
710         }
711         return hash;
712     }
713 
714     /**
715      * Returns whether given intent can be resolved for the user.
716      */
canResolveIntentAsUser(Context context, Intent intent, int userId)717     public boolean canResolveIntentAsUser(Context context, Intent intent, int userId) {
718         return intent != null
719                 && context.getPackageManager().resolveActivityAsUser(intent, 0, userId) != null;
720     }
721 
isPackageDeviceOwner(DevicePolicyManager dpm, String packageName)722     public boolean isPackageDeviceOwner(DevicePolicyManager dpm, String packageName) {
723         final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnCallingUser();
724         return deviceOwner != null && deviceOwner.getPackageName().equals(packageName);
725     }
726 
getAccentColor(Context context)727     public int getAccentColor(Context context) {
728         return getAttrColor(context, android.R.attr.colorAccent);
729     }
730 
731     /**
732      * Returns the theme's background color.
733      */
getBackgroundColor(Context context)734     public int getBackgroundColor(Context context) {
735         return getAttrColor(context, android.R.attr.colorBackground);
736     }
737 
738     /**
739      * Returns the theme's text primary color.
740      */
getTextPrimaryColor(Context context)741     public int getTextPrimaryColor(Context context) {
742         return getAttrColor(context, android.R.attr.textColorPrimary);
743     }
744 
745     /**
746      * Returns the theme's text secondary color.
747      */
getTextSecondaryColor(Context context)748     public int getTextSecondaryColor(Context context) {
749         return getAttrColor(context, android.R.attr.textColorSecondary);
750     }
751 
getAttrColor(Context context, int attr)752     private int getAttrColor(Context context, int attr) {
753         TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
754         int attrColor = ta.getColor(0, 0);
755         ta.recycle();
756         return attrColor;
757     }
758 
handleSupportUrl(Context context, CustomizationParams customizationParams, AccessibilityContextMenuMaker contextMenuMaker, TextView textView, String deviceProvider, String contactDeviceProvider, Consumer<Intent> clickHandler)759     public void handleSupportUrl(Context context, CustomizationParams customizationParams,
760             AccessibilityContextMenuMaker contextMenuMaker, TextView textView,
761             String deviceProvider, String contactDeviceProvider,
762             Consumer<Intent> clickHandler) {
763         if (customizationParams.supportUrl == null) {
764             textView.setText(contactDeviceProvider);
765             return;
766         }
767         final Intent intent = WebActivity.createIntent(
768                 context, customizationParams.supportUrl);
769 
770         final ClickableSpanFactory spanFactory =
771                 new ClickableSpanFactory(getAccentColor(context), clickHandler);
772         handlePartialClickableTextView(
773                 textView, contactDeviceProvider, deviceProvider, intent, spanFactory);
774 
775         contextMenuMaker.registerWithActivity(textView);
776     }
777 
778     /**
779      * Utility function to make a TextView partial clickable. It also associates the TextView with
780      * an Intent. The intent will be triggered when the clickable part is clicked.
781      *
782      * @param textView The TextView which hosts the clickable string.
783      * @param content The content of the TextView.
784      * @param clickableString The substring which is clickable.
785      * @param intent The Intent that will be launched.
786      * @param clickableSpanFactory The factory which is used to create ClickableSpan to decorate
787      *                             clickable string.
788      */
handlePartialClickableTextView(TextView textView, String content, String clickableString, Intent intent, ClickableSpanFactory clickableSpanFactory)789     public void handlePartialClickableTextView(TextView textView, String content,
790             String clickableString, Intent intent, ClickableSpanFactory clickableSpanFactory) {
791         final SpannableString spannableString = new SpannableString(content);
792         if (intent != null) {
793             final ClickableSpan span = clickableSpanFactory.create(intent);
794             final int startIdx = content.indexOf(clickableString);
795             final int endIdx = startIdx + clickableString.length();
796 
797             spannableString.setSpan(span, startIdx, endIdx, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
798             textView.setMovementMethod(LinkMovementMethod.getInstance());
799         }
800 
801         textView.setText(spannableString);
802     }
803 
804     /**
805      * Gets the device's current device owner admin component.
806      */
807     @Nullable
getCurrentDeviceOwnerComponentName(DevicePolicyManager dpm)808     public ComponentName getCurrentDeviceOwnerComponentName(DevicePolicyManager dpm) {
809         return isHeadlessSystemUserMode()
810                 ? dpm.getDeviceOwnerComponentOnAnyUser()
811                 : dpm.getDeviceOwnerComponentOnCallingUser();
812     }
813 
addNextButton(GlifLayout layout, @NonNull OnClickListener listener)814     public static FooterButton addNextButton(GlifLayout layout, @NonNull OnClickListener listener) {
815         return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.next);
816     }
817 
818     /**
819      * Adds an encryption primary button mixin to a {@link GlifLayout} screen.
820      */
addEncryptButton( GlifLayout layout, @NonNull OnClickListener listener)821     public static FooterButton addEncryptButton(
822             GlifLayout layout, @NonNull OnClickListener listener) {
823         return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.encrypt);
824     }
825 
addAcceptAndContinueButton(GlifLayout layout, @NonNull OnClickListener listener)826     public static FooterButton addAcceptAndContinueButton(GlifLayout layout,
827         @NonNull OnClickListener listener) {
828         return setPrimaryButton(layout, listener, ButtonType.NEXT, R.string.accept_and_continue);
829     }
830 
831     /** Adds a primary "Cancel setup" button */
addResetButton(GlifLayout layout, @NonNull OnClickListener listener, @StringRes int resetButtonString)832     public static FooterButton addResetButton(GlifLayout layout,
833             @NonNull OnClickListener listener, @StringRes int resetButtonString) {
834         return setPrimaryButton(layout, listener, ButtonType.CANCEL,
835                 resetButtonString);
836     }
837 
setPrimaryButton(GlifLayout layout, OnClickListener listener, @ButtonType int buttonType, @StringRes int label)838     private static FooterButton setPrimaryButton(GlifLayout layout, OnClickListener listener,
839         @ButtonType int buttonType, @StringRes int label) {
840         final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
841         final FooterButton primaryButton = new FooterButton.Builder(layout.getContext())
842             .setText(label)
843             .setListener(listener)
844             .setButtonType(buttonType)
845             .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
846             .build();
847         mixin.setPrimaryButton(primaryButton);
848         return primaryButton;
849     }
850 
851     /** Adds a secondary "abort & reset" button. */
addAbortAndResetButton(GlifLayout layout, @NonNull OnClickListener listener)852     public static FooterButton addAbortAndResetButton(GlifLayout layout,
853             @NonNull OnClickListener listener) {
854         final int buttonType = ButtonType.CANCEL;
855         final int buttonLabel = R.string.fully_managed_device_cancel_setup_button;
856 
857         return addSecondaryButton(layout, listener, buttonType, buttonLabel);
858     }
859 
addSecondaryButton(GlifLayout layout, @NonNull OnClickListener listener, @ButtonType int buttonType, @StringRes int buttonLabel)860     private static FooterButton addSecondaryButton(GlifLayout layout,
861             @NonNull OnClickListener listener,
862             @ButtonType int buttonType, @StringRes int buttonLabel) {
863         final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
864         final FooterButton secondaryButton = new FooterButton.Builder(layout.getContext())
865                 .setText(buttonLabel)
866                 .setListener(listener)
867                 .setButtonType(buttonType)
868                 .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
869                 .build();
870         mixin.setSecondaryButton(secondaryButton);
871         return secondaryButton;
872     }
873 
createCancelProvisioningResetDialogBuilder(Context context)874     public SimpleDialog.Builder createCancelProvisioningResetDialogBuilder(Context context) {
875         CharSequence deviceName = DeviceHelper.getDeviceName(context);
876         final int positiveResId = R.string.reset;
877         final int negativeResId = R.string.device_owner_cancel_cancel;
878         return getBaseDialogBuilder(positiveResId, negativeResId)
879                 .setMessage(
880                         LazyStringResource.of(R.string.this_will_reset_take_back_first_screen,
881                                 deviceName))
882                 .setTitle(
883                         LazyStringResource.of(R.string.stop_setup_reset_device_question,
884                                 deviceName));
885     }
886 
887     /**
888      * Create a builder for cancel provisioning dialog
889      *
890      * @return builder
891      */
createCancelProvisioningDialogBuilder()892     public SimpleDialog.Builder createCancelProvisioningDialogBuilder() {
893         final int positiveResId = R.string.profile_owner_cancel_ok;
894         final int negativeResId = R.string.profile_owner_cancel_cancel;
895         final int dialogMsgResId = R.string.profile_owner_cancel_message;
896         return getBaseDialogBuilder(positiveResId, negativeResId).setMessage(dialogMsgResId);
897     }
898 
shouldShowOwnershipDisclaimerScreen(ProvisioningParams params)899     public boolean shouldShowOwnershipDisclaimerScreen(ProvisioningParams params) {
900         return !params.skipOwnershipDisclaimer;
901     }
902 
isOrganizationOwnedAllowed(ProvisioningParams params)903     public boolean isOrganizationOwnedAllowed(ProvisioningParams params) {
904         int provisioningModes = params.initiatorRequestedProvisioningModes;
905         return containsBinaryFlags(provisioningModes, FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED)
906                 || containsBinaryFlags(provisioningModes, FLAG_SUPPORTED_MODES_DEVICE_OWNER);
907     }
908 
isManagedProfileProvisioningStartedByDpc( Context context, ProvisioningParams params, SettingsFacade settingsFacade)909     public boolean isManagedProfileProvisioningStartedByDpc(
910             Context context,
911             ProvisioningParams params,
912             SettingsFacade settingsFacade) {
913         if (!ACTION_PROVISION_MANAGED_PROFILE.equals(params.provisioningAction)) {
914             return false;
915         }
916         if (params.startedByTrustedSource) {
917             return false;
918         }
919         return settingsFacade.isUserSetupCompleted(context);
920     }
921 
922     /**
923      * Returns {@code true} if {@code packageName} is installed on the primary user.
924      */
isPackageInstalled(String packageName, PackageManager packageManager)925     public boolean isPackageInstalled(String packageName, PackageManager packageManager) {
926         try {
927             final ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
928                     PackageManager.MATCH_DIRECT_BOOT_AWARE
929                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
930             return ai != null;
931         } catch (PackageManager.NameNotFoundException e) {
932             return false;
933         }
934     }
935 
getBaseDialogBuilder(int positiveResId, int negativeResId)936     private SimpleDialog.Builder getBaseDialogBuilder(int positiveResId, int negativeResId) {
937         return new SimpleDialog.Builder()
938                 .setCancelable(false)
939                 .setNegativeButtonMessage(negativeResId)
940                 .setPositiveButtonMessage(positiveResId);
941     }
942 
943     /**
944      * Returns {@code true} if {@code value} contains the {@code flags} binary flags.
945      */
containsBinaryFlags(int value, int flags)946     public boolean containsBinaryFlags(int value, int flags) {
947         return (value & flags) == flags;
948     }
949 
950     /**
951      * Calls {@code callback} when {@code view} has been measured.
952      */
onViewMeasured(View view, Consumer<View> callback)953     public void onViewMeasured(View view, Consumer<View> callback) {
954         view.getViewTreeObserver().addOnGlobalLayoutListener(
955                 new ViewTreeObserver.OnGlobalLayoutListener() {
956                 @Override
957                 public void onGlobalLayout() {
958                     view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
959                     callback.accept(view);
960                 }
961             });
962     }
963 }
964