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