Utils.java revision 534af0f92c92f01602fb352c043708db47a55203
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
17package com.android.managedprovisioning.common;
18
19import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
20import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE;
21import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
22import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE;
23import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
24import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
25import static android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED;
26
27import android.accounts.Account;
28import android.accounts.AccountManager;
29import android.accounts.AccountManagerFuture;
30import android.accounts.AuthenticatorException;
31import android.accounts.OperationCanceledException;
32import android.annotation.NonNull;
33import android.annotation.Nullable;
34import android.app.admin.DevicePolicyManager;
35import android.content.ComponentName;
36import android.content.Context;
37import android.content.Intent;
38import android.content.pm.ActivityInfo;
39import android.content.pm.ApplicationInfo;
40import android.content.pm.IPackageManager;
41import android.content.pm.PackageInfo;
42import android.content.pm.PackageManager;
43import android.content.pm.PackageManager.NameNotFoundException;
44import android.content.pm.ResolveInfo;
45import android.content.pm.UserInfo;
46import android.graphics.Color;
47import android.net.ConnectivityManager;
48import android.net.NetworkInfo;
49import android.net.wifi.WifiManager;
50import android.os.Build;
51import android.os.Bundle;
52import android.os.RemoteException;
53import android.os.ServiceManager;
54import android.os.SystemProperties;
55import android.os.UserHandle;
56import android.os.UserManager;
57import android.os.storage.StorageManager;
58import android.text.TextUtils;
59
60import com.android.internal.annotations.VisibleForTesting;
61import com.android.managedprovisioning.TrampolineActivity;
62import com.android.managedprovisioning.model.PackageDownloadInfo;
63
64import java.io.FileInputStream;
65import java.io.IOException;
66import java.io.InputStream;
67import java.security.MessageDigest;
68import java.security.NoSuchAlgorithmException;
69import java.util.HashSet;
70import java.util.List;
71import java.util.Set;
72
73/**
74 * Class containing various auxiliary methods.
75 */
76public class Utils {
77    public static final String SHA256_TYPE = "SHA-256";
78    public static final String SHA1_TYPE = "SHA-1";
79
80    private static final int THRESHOLD_BRIGHT_COLOR = 160; // A color needs a brightness of at least
81    // this value to be considered bright. (brightness being between 0 and 255).
82
83    public Utils() {}
84
85    /**
86     * Returns the currently installed system apps on a given user.
87     *
88     * <p>Calls into the {@link IPackageManager} to retrieve all installed packages on the given
89     * user and returns the package names of all system apps.
90     *
91     * @param ipm an {@link IPackageManager} object
92     * @param userId the id of the user we are interested in
93     */
94    public Set<String> getCurrentSystemApps(IPackageManager ipm, int userId) {
95        Set<String> apps = new HashSet<String>();
96        List<ApplicationInfo> aInfos = null;
97        try {
98            aInfos = ipm.getInstalledApplications(
99                    PackageManager.GET_UNINSTALLED_PACKAGES, userId).getList();
100        } catch (RemoteException neverThrown) {
101            ProvisionLogger.loge("This should not happen.", neverThrown);
102        }
103        for (ApplicationInfo aInfo : aInfos) {
104            if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
105                apps.add(aInfo.packageName);
106            }
107        }
108        return apps;
109    }
110
111    /**
112     * Disables a given component in a given user.
113     *
114     * @param toDisable the component that should be disabled
115     * @param userId the id of the user where the component should be disabled.
116     */
117    public void disableComponent(ComponentName toDisable, int userId) {
118        setComponentEnabledSetting(
119                IPackageManager.Stub.asInterface(ServiceManager.getService("package")),
120                toDisable,
121                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
122                userId);
123    }
124
125    /**
126     * Enables a given component in a given user.
127     *
128     * @param toEnable the component that should be enabled
129     * @param userId the id of the user where the component should be disabled.
130     */
131    public void enableComponent(ComponentName toEnable, int userId) {
132        setComponentEnabledSetting(
133                IPackageManager.Stub.asInterface(ServiceManager.getService("package")),
134                toEnable,
135                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
136                userId);
137    }
138
139    /**
140     * Disables a given component in a given user.
141     *
142     * @param ipm an {@link IPackageManager} object
143     * @param toDisable the component that should be disabled
144     * @param userId the id of the user where the component should be disabled.
145     */
146    @VisibleForTesting
147    void setComponentEnabledSetting(IPackageManager ipm, ComponentName toDisable,
148            int enabledSetting, int userId) {
149        try {
150            ipm.setComponentEnabledSetting(toDisable,
151                    enabledSetting, PackageManager.DONT_KILL_APP,
152                    userId);
153        } catch (RemoteException neverThrown) {
154            ProvisionLogger.loge("This should not happen.", neverThrown);
155        } catch (Exception e) {
156            ProvisionLogger.logw("Component not found, not changing enabled setting: "
157                + toDisable.toShortString());
158        }
159    }
160
161    /**
162     * Check the validity of the admin component name supplied, or try to infer this componentName
163     * from the package.
164     *
165     * We are supporting lookup by package name for legacy reasons.
166     *
167     * If dpcComponentName is supplied (not null): dpcPackageName is ignored.
168     * Check that the package of dpcComponentName is installed, that dpcComponentName is a
169     * receiver in this package, and return it. The receiver can be in disabled state.
170     *
171     * Otherwise: dpcPackageName must be supplied (not null).
172     * Check that this package is installed, try to infer a potential device admin in this package,
173     * and return it.
174     */
175    @NonNull
176    public ComponentName findDeviceAdmin(String dpcPackageName, ComponentName dpcComponentName,
177            Context context) throws IllegalProvisioningArgumentException {
178        if (dpcComponentName != null) {
179            dpcPackageName = dpcComponentName.getPackageName();
180        }
181        if (dpcPackageName == null) {
182            throw new IllegalProvisioningArgumentException("Neither the package name nor the"
183                    + " component name of the admin are supplied");
184        }
185        PackageInfo pi;
186        try {
187            pi = context.getPackageManager().getPackageInfo(dpcPackageName,
188                    PackageManager.GET_RECEIVERS | PackageManager.MATCH_DISABLED_COMPONENTS);
189        } catch (NameNotFoundException e) {
190            throw new IllegalProvisioningArgumentException("Dpc "+ dpcPackageName
191                    + " is not installed. ", e);
192        }
193
194        final ComponentName componentName;
195        if (dpcComponentName != null) {
196            if (!isComponentInPackageInfo(dpcComponentName, pi)) {
197                throw new IllegalProvisioningArgumentException("The component " + dpcComponentName
198                        + " cannot be found");
199            }
200            componentName = dpcComponentName;
201        } else {
202            componentName = findDeviceAdminInPackage(dpcPackageName, pi);
203            if (componentName == null) {
204                throw new IllegalProvisioningArgumentException("Cannot find any admin receiver in "
205                        + "package " + dpcPackageName);
206            }
207        }
208
209        return componentName;
210    }
211
212    /**
213     * Finds a device admin in a given {@link PackageInfo} object.
214     *
215     * <p>This function returns {@code null} if no or multiple admin receivers were found, and if
216     * the package name does not match dpcPackageName.</p>
217     * @param packageName packge name that should match the {@link PackageInfo} object.
218     * @param packageInfo package info to be examined.
219     * @return admin receiver or null in case of error.
220     */
221    @Nullable
222    public ComponentName findDeviceAdminInPackage(String packageName, PackageInfo packageInfo) {
223        if (packageInfo == null || !TextUtils.equals(packageInfo.packageName, packageName)) {
224            return null;
225        }
226
227        ComponentName mdmComponentName = null;
228        for (ActivityInfo ai : packageInfo.receivers) {
229            if (TextUtils.equals(ai.permission, android.Manifest.permission.BIND_DEVICE_ADMIN)) {
230                if (mdmComponentName != null) {
231                    return null;
232                } else {
233                    mdmComponentName = new ComponentName(packageName, ai.name);
234                }
235            }
236        }
237        return mdmComponentName;
238    }
239
240    private boolean isComponentInPackageInfo(ComponentName dpcComponentName,
241            PackageInfo pi) {
242        for (ActivityInfo ai : pi.receivers) {
243            if (dpcComponentName.getClassName().equals(ai.name)) {
244                return true;
245            }
246        }
247        return false;
248    }
249
250    /**
251     * Returns whether the current user is the system user.
252     */
253    public boolean isCurrentUserSystem() {
254        return UserHandle.myUserId() == UserHandle.USER_SYSTEM;
255    }
256
257    /**
258     * Returns whether the device is currently managed.
259     */
260    public boolean isDeviceManaged(Context context) {
261        DevicePolicyManager dpm =
262                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
263        return dpm.isDeviceManaged();
264    }
265
266    /**
267     * Returns true if the given package requires an update.
268     *
269     * <p>There are two cases where an update is required:
270     * 1. The package is not currently present on the device.
271     * 2. The package is present, but the version is below the minimum supported version.
272     *
273     * @param packageName the package to be checked for updates
274     * @param minSupportedVersion the minimum supported version
275     * @param context a {@link Context} object
276     */
277    public boolean packageRequiresUpdate(String packageName, int minSupportedVersion,
278            Context context) {
279        try {
280            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
281            // Always download packages if no minimum version given.
282            if (minSupportedVersion != PackageDownloadInfo.DEFAULT_MINIMUM_VERSION
283                    && packageInfo.versionCode >= minSupportedVersion) {
284                return false;
285            }
286        } catch (NameNotFoundException e) {
287            // Package not on device.
288        }
289
290        return true;
291    }
292
293    /**
294     * Returns the first existing managed profile if any present, null otherwise.
295     *
296     * <p>Note that we currently only support one managed profile per device.
297     */
298    // TODO: Add unit tests
299    public UserHandle getManagedProfile(Context context) {
300        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
301        int currentUserId = userManager.getUserHandle();
302        List<UserInfo> userProfiles = userManager.getProfiles(currentUserId);
303        for (UserInfo profile : userProfiles) {
304            if (profile.isManagedProfile()) {
305                return new UserHandle(profile.id);
306            }
307        }
308        return null;
309    }
310
311    /**
312     * Returns the user id of an already existing managed profile or -1 if none exists.
313     */
314    // TODO: Add unit tests
315    public int alreadyHasManagedProfile(Context context) {
316        UserHandle managedUser = getManagedProfile(context);
317        if (managedUser != null) {
318            return managedUser.getIdentifier();
319        } else {
320            return -1;
321        }
322    }
323
324    /**
325     * Removes an account.
326     *
327     * <p>This removes the given account from the calling user's list of accounts.
328     *
329     * @param context a {@link Context} object
330     * @param account the account to be removed
331     */
332    // TODO: Add unit tests
333    public void removeAccount(Context context, Account account) {
334        try {
335            AccountManager accountManager =
336                    (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
337            AccountManagerFuture<Bundle> bundle = accountManager.removeAccount(account,
338                    null, null /* callback */, null /* handler */);
339            // Block to get the result of the removeAccount operation
340            if (bundle.getResult().getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
341                ProvisionLogger.logw("Account removed from the primary user.");
342            } else {
343                Intent removeIntent = (Intent) bundle.getResult().getParcelable(
344                        AccountManager.KEY_INTENT);
345                if (removeIntent != null) {
346                    ProvisionLogger.logi("Starting activity to remove account");
347                    TrampolineActivity.startActivity(context, removeIntent);
348                } else {
349                    ProvisionLogger.logw("Could not remove account from the primary user.");
350                }
351            }
352        } catch (OperationCanceledException | AuthenticatorException | IOException e) {
353            ProvisionLogger.logw("Exception removing account from the primary user.", e);
354        }
355    }
356
357    /**
358     * Returns whether FRP is supported on the device.
359     */
360    public boolean isFrpSupported(Context context) {
361        Object pdbManager = context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
362        return pdbManager != null;
363    }
364
365    /**
366     * Translates a given managed provisioning intent to its corresponding provisioning flow, using
367     * the action from the intent.
368     *
369     * <p/>This is necessary because, unlike other provisioning actions which has 1:1 mapping, there
370     * are multiple actions that can trigger the device owner provisioning flow. This includes
371     * {@link ACTION_PROVISION_MANAGED_DEVICE}, {@link ACTION_NDEF_DISCOVERED} and
372     * {@link ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}. These 3 actions are equivalent
373     * excepts they are sent from a different source.
374     *
375     * @return the appropriate DevicePolicyManager declared action for the given incoming intent.
376     * @throws IllegalProvisioningArgumentException if intent is malformed
377     */
378    // TODO: Add unit tests
379    public String mapIntentToDpmAction(Intent intent)
380            throws IllegalProvisioningArgumentException {
381        if (intent == null || intent.getAction() == null) {
382            throw new IllegalProvisioningArgumentException("Null intent action.");
383        }
384
385        // Map the incoming intent to a DevicePolicyManager.ACTION_*, as there is a N:1 mapping in
386        // some cases.
387        String dpmProvisioningAction;
388        switch (intent.getAction()) {
389            // Trivial cases.
390            case ACTION_PROVISION_MANAGED_DEVICE:
391            case ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE:
392            case ACTION_PROVISION_MANAGED_USER:
393            case ACTION_PROVISION_MANAGED_PROFILE:
394                dpmProvisioningAction = intent.getAction();
395                break;
396
397            // NFC cases which need to take mime-type into account.
398            case ACTION_NDEF_DISCOVERED:
399                String mimeType = intent.getType();
400                if (mimeType == null) {
401                    throw new IllegalProvisioningArgumentException(
402                            "Unknown NFC bump mime-type: " + mimeType);
403                }
404                switch (mimeType) {
405                    case MIME_TYPE_PROVISIONING_NFC:
406                        dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE;
407                        break;
408
409                    default:
410                        throw new IllegalProvisioningArgumentException(
411                                "Unknown NFC bump mime-type: " + mimeType);
412                }
413                break;
414
415            // Device owner provisioning from a trusted app.
416            // TODO (b/27217042): review for new management modes in split system-user model
417            case ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE:
418                dpmProvisioningAction = ACTION_PROVISION_MANAGED_DEVICE;
419                break;
420
421            default:
422                throw new IllegalProvisioningArgumentException("Unknown intent action "
423                        + intent.getAction());
424        }
425        return dpmProvisioningAction;
426    }
427
428    /**
429     * Sends an intent to trigger a factory reset.
430     */
431    // TODO: Move the FR intent into a Globals class.
432    public void sendFactoryResetBroadcast(Context context, String reason) {
433        Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
434        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
435        intent.putExtra(Intent.EXTRA_REASON, reason);
436        context.sendBroadcast(intent);
437    }
438
439    /**
440     * Returns whether the given provisioning action is a profile owner action.
441     */
442    // TODO: Move the list of device owner actions into a Globals class.
443    public final boolean isProfileOwnerAction(String action) {
444        return action.equals(ACTION_PROVISION_MANAGED_PROFILE)
445                || action.equals(ACTION_PROVISION_MANAGED_USER);
446    }
447
448    /**
449     * Returns whether the given provisioning action is a device owner action.
450     */
451    // TODO: Move the list of device owner actions into a Globals class.
452    public final boolean isDeviceOwnerAction(String action) {
453        return action.equals(ACTION_PROVISION_MANAGED_DEVICE)
454                || action.equals(ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE);
455    }
456
457    /**
458     * Returns whether the device currently has connectivity.
459     */
460    public boolean isConnectedToNetwork(Context context) {
461        NetworkInfo info = getActiveNetworkInfo(context);
462        return info != null && info.isConnected();
463    }
464
465    /**
466     * Returns whether the device is currently connected to a wifi.
467     */
468    public boolean isConnectedToWifi(Context context) {
469        NetworkInfo info = getActiveNetworkInfo(context);
470        return info != null
471                && info.isConnected()
472                && info.getType() == ConnectivityManager.TYPE_WIFI;
473    }
474
475    /**
476     * Returns the active network info of the device.
477     */
478    public NetworkInfo getActiveNetworkInfo(Context context) {
479        ConnectivityManager cm =
480                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
481        return cm.getActiveNetworkInfo();
482    }
483
484    /**
485     * Returns whether encryption is required on this device.
486     *
487     * <p>Encryption is required if the device is not currently encrypted and the persistent
488     * system flag {@code persist.sys.no_req_encrypt} is not set.
489     */
490    public boolean isEncryptionRequired() {
491        return !isPhysicalDeviceEncrypted()
492                && !SystemProperties.getBoolean("persist.sys.no_req_encrypt", false);
493    }
494
495    /**
496     * Returns whether the device is currently encrypted.
497     */
498    public boolean isPhysicalDeviceEncrypted() {
499        return StorageManager.isEncrypted();
500    }
501
502    /**
503     * Returns the wifi pick intent.
504     */
505    // TODO: Move this intent into a Globals class.
506    public Intent getWifiPickIntent() {
507        Intent wifiIntent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK);
508        wifiIntent.putExtra("extra_prefs_show_button_bar", true);
509        wifiIntent.putExtra("wifi_enable_next_on_connect", true);
510        return wifiIntent;
511    }
512
513    /**
514     * Returns whether the device has a split system user.
515     *
516     * <p>Split system user means that user 0 is system only and all meat users are separate from
517     * the system user.
518     */
519    public boolean isSplitSystemUser() {
520        return UserManager.isSplitSystemUser();
521    }
522
523    /**
524     * Returns whether the currently chosen launcher supports managed profiles.
525     *
526     * <p>A launcher is deemed to support managed profiles when its target API version is at least
527     * {@link Build.VERSION_CODES#LOLLIPOP}.
528     */
529    public boolean currentLauncherSupportsManagedProfiles(Context context) {
530        Intent intent = new Intent(Intent.ACTION_MAIN);
531        intent.addCategory(Intent.CATEGORY_HOME);
532
533        PackageManager pm = context.getPackageManager();
534        ResolveInfo launcherResolveInfo = pm.resolveActivity(intent,
535                PackageManager.MATCH_DEFAULT_ONLY);
536        if (launcherResolveInfo == null) {
537            return false;
538        }
539        try {
540            // If the user has not chosen a default launcher, then launcherResolveInfo will be
541            // referring to the resolver activity. It is fine to create a managed profile in
542            // this case since there will always be at least one launcher on the device that
543            // supports managed profile feature.
544            ApplicationInfo launcherAppInfo = pm.getApplicationInfo(
545                    launcherResolveInfo.activityInfo.packageName, 0 /* default flags */);
546            return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion);
547        } catch (PackageManager.NameNotFoundException e) {
548            return false;
549        }
550    }
551
552    /**
553     * Returns whether the given version number is at least lollipop.
554     *
555     * @param versionNumber the version number to be verified.
556     */
557    private boolean versionNumberAtLeastL(int versionNumber) {
558        return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
559    }
560
561    /**
562     * Computes the sha 256 hash of a byte array.
563     */
564    @Nullable
565    public byte[] computeHashOfByteArray(byte[] bytes) {
566        try {
567            MessageDigest md = MessageDigest.getInstance(SHA256_TYPE);
568            md.update(bytes);
569            return md.digest();
570        } catch (NoSuchAlgorithmException e) {
571            ProvisionLogger.loge("Hashing algorithm " + SHA256_TYPE + " not supported.", e);
572            return null;
573        }
574    }
575
576    /**
577     * Computes a hash of a file with a spcific hash algorithm.
578     */
579    // TODO: Add unit tests
580    @Nullable
581    public byte[] computeHashOfFile(String fileLocation, String hashType) {
582        InputStream fis = null;
583        MessageDigest md;
584        byte hash[] = null;
585        try {
586            md = MessageDigest.getInstance(hashType);
587        } catch (NoSuchAlgorithmException e) {
588            ProvisionLogger.loge("Hashing algorithm " + hashType + " not supported.", e);
589            return null;
590        }
591        try {
592            fis = new FileInputStream(fileLocation);
593
594            byte[] buffer = new byte[256];
595            int n = 0;
596            while (n != -1) {
597                n = fis.read(buffer);
598                if (n > 0) {
599                    md.update(buffer, 0, n);
600                }
601            }
602            hash = md.digest();
603        } catch (IOException e) {
604            ProvisionLogger.loge("IO error.", e);
605        } finally {
606            // Close input stream quietly.
607            try {
608                if (fis != null) {
609                    fis.close();
610                }
611            } catch (IOException e) {
612                // Ignore.
613            }
614        }
615        return hash;
616    }
617
618    public boolean isBrightColor(int color) {
619        // This comes from the YIQ transformation. We're using the formula:
620        // Y = .299 * R + .587 * G + .114 * B
621        return Color.red(color) * 299 + Color.green(color) * 587 + Color.blue(color) * 114
622                >= 1000 * THRESHOLD_BRIGHT_COLOR;
623    }
624
625    /**
626     * Returns whether given intent can be resolved for the user.
627     */
628    public boolean canResolveIntentAsUser(Context context, Intent intent, int userId) {
629        return intent != null
630                && context.getPackageManager().resolveActivityAsUser(intent, 0, userId) != null;
631    }
632}
633