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