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;
18
19import android.app.admin.DevicePolicyManager;
20import android.content.BroadcastReceiver;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.ActivityInfo;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.IPackageManager;
27import android.content.pm.PackageInfo;
28import android.content.pm.PackageManager;
29import android.content.pm.PackageManager.NameNotFoundException;
30import android.content.pm.UserInfo;
31import android.graphics.drawable.Drawable;
32import android.os.AsyncTask;
33import android.os.Binder;
34import android.os.Bundle;
35import android.os.RemoteException;
36import android.os.ServiceManager;
37import android.os.UserHandle;
38import android.os.UserManager;
39import android.provider.Settings.Global;
40import android.provider.Settings.Secure;
41import android.text.TextUtils;
42import android.util.Base64;
43
44import java.io.IOException;
45import java.util.HashSet;
46import java.util.List;
47import java.util.Set;
48
49import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
50import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
51
52import android.accounts.Account;
53import android.accounts.AccountManager;
54import android.accounts.AccountManagerFuture;
55import android.accounts.AuthenticatorException;
56import android.accounts.OperationCanceledException;
57
58/**
59 * Class containing various auxiliary methods.
60 */
61public class Utils {
62    private Utils() {}
63
64    public static Set<String> getCurrentSystemApps(int userId) {
65        IPackageManager ipm = IPackageManager.Stub.asInterface(ServiceManager
66                .getService("package"));
67        Set<String> apps = new HashSet<String>();
68        List<ApplicationInfo> aInfos = null;
69        try {
70            aInfos = ipm.getInstalledApplications(
71                    PackageManager.GET_UNINSTALLED_PACKAGES, userId).getList();
72        } catch (RemoteException neverThrown) {
73            ProvisionLogger.loge("This should not happen.", neverThrown);
74        }
75        for (ApplicationInfo aInfo : aInfos) {
76            if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
77                apps.add(aInfo.packageName);
78            }
79        }
80        return apps;
81    }
82
83    public static void disableComponent(ComponentName toDisable, int userId) {
84        try {
85            IPackageManager ipm = IPackageManager.Stub.asInterface(ServiceManager
86                .getService("package"));
87
88            ipm.setComponentEnabledSetting(toDisable,
89                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP,
90                    userId);
91        } catch (RemoteException neverThrown) {
92            ProvisionLogger.loge("This should not happen.", neverThrown);
93        } catch (Exception e) {
94            ProvisionLogger.logw("Component not found, not disabling it: "
95                + toDisable.toShortString());
96        }
97    }
98
99    /**
100     * Exception thrown when the provisioning has failed completely.
101     *
102     * We're using a custom exception to avoid catching subsequent exceptions that might be
103     * significant.
104     */
105    public static class IllegalProvisioningArgumentException extends Exception {
106        public IllegalProvisioningArgumentException(String message) {
107            super(message);
108        }
109
110        public IllegalProvisioningArgumentException(String message, Throwable t) {
111            super(message, t);
112        }
113    }
114
115    /**
116     * Check the validity of the admin component name supplied, or try to infer this componentName
117     * from the package.
118     *
119     * We are supporting lookup by package name for legacy reasons.
120     *
121     * If mdmComponentName is supplied (not null):
122     * mdmPackageName is ignored.
123     * Check that the package of mdmComponentName is installed, that mdmComponentName is a
124     * receiver in this package, and return it.
125     *
126     * Otherwise:
127     * mdmPackageName must be supplied (not null).
128     * Check that this package is installed, try to infer a potential device admin in this package,
129     * and return it.
130     */
131    public static ComponentName findDeviceAdmin(String mdmPackageName,
132            ComponentName mdmComponentName, Context c) throws IllegalProvisioningArgumentException {
133        if (mdmComponentName != null) {
134            mdmPackageName = mdmComponentName.getPackageName();
135        }
136        if (mdmPackageName == null) {
137            throw new IllegalProvisioningArgumentException("Neither the package name nor the"
138                    + " component name of the admin are supplied");
139        }
140        PackageInfo pi;
141        try {
142            pi = c.getPackageManager().getPackageInfo(mdmPackageName,
143                    PackageManager.GET_RECEIVERS);
144        } catch (NameNotFoundException e) {
145            throw new IllegalProvisioningArgumentException("Mdm "+ mdmPackageName
146                    + " is not installed. ", e);
147        }
148        if (mdmComponentName != null) {
149            // If the component was specified in the intent: check that it is in the manifest.
150            checkAdminComponent(mdmComponentName, pi);
151            return mdmComponentName;
152        } else {
153            // Otherwise: try to find a potential device admin in the manifest.
154            return findDeviceAdminInPackage(mdmPackageName, pi);
155        }
156    }
157
158    private static void checkAdminComponent(ComponentName mdmComponentName, PackageInfo pi)
159            throws IllegalProvisioningArgumentException{
160        for (ActivityInfo ai : pi.receivers) {
161            if (mdmComponentName.getClassName().equals(ai.name)) {
162                return;
163            }
164        }
165        throw new IllegalProvisioningArgumentException("The component " + mdmComponentName
166                + " cannot be found");
167    }
168
169    private static ComponentName findDeviceAdminInPackage(String mdmPackageName, PackageInfo pi)
170            throws IllegalProvisioningArgumentException {
171        ComponentName mdmComponentName = null;
172        for (ActivityInfo ai : pi.receivers) {
173            if (!TextUtils.isEmpty(ai.permission) &&
174                    ai.permission.equals(android.Manifest.permission.BIND_DEVICE_ADMIN)) {
175                if (mdmComponentName != null) {
176                    throw new IllegalProvisioningArgumentException("There are several "
177                            + "device admins in " + mdmPackageName + " but no one in specified");
178                } else {
179                    mdmComponentName = new ComponentName(mdmPackageName, ai.name);
180                }
181            }
182        }
183        if (mdmComponentName == null) {
184            throw new IllegalProvisioningArgumentException("There are no device admins in"
185                    + mdmPackageName);
186        }
187        return mdmComponentName;
188    }
189
190    public static MdmPackageInfo getMdmPackageInfo(PackageManager pm, String packageName) {
191        if (packageName != null) {
192            try {
193                ApplicationInfo ai = pm.getApplicationInfo(packageName, /* default flags */ 0);
194                if (ai != null) {
195                    return new MdmPackageInfo(pm.getApplicationIcon(packageName),
196                            pm.getApplicationLabel(ai).toString());
197                }
198            } catch (PackageManager.NameNotFoundException e) {
199                // Package does not exist, ignore. Should never happen.
200                ProvisionLogger.loge("Package does not exist. Should never happen.");
201            }
202        }
203
204        return null;
205    }
206
207    /**
208     * Information relating to the currently installed MDM package manager.
209     */
210    public static final class MdmPackageInfo {
211        private final Drawable packageIcon;
212        private final String appLabel;
213
214        private MdmPackageInfo(Drawable packageIcon, String appLabel) {
215            this.packageIcon = packageIcon;
216            this.appLabel = appLabel;
217        }
218
219        public String getAppLabel() {
220            return appLabel;
221        }
222
223        public Drawable getPackageIcon() {
224            return packageIcon;
225        }
226    }
227
228    public static boolean isCurrentUserOwner() {
229        return UserHandle.myUserId() == UserHandle.USER_OWNER;
230    }
231
232    public static boolean hasDeviceOwner(Context context) {
233        DevicePolicyManager dpm =
234                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
235        return !TextUtils.isEmpty(dpm.getDeviceOwner());
236    }
237
238    public static boolean hasDeviceInitializer(Context context) {
239        DevicePolicyManager dpm =
240                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
241        return dpm != null && dpm.getDeviceInitializerApp() != null;
242    }
243
244    public static boolean isManagedProfile(Context context) {
245        UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
246        UserInfo user = um.getUserInfo(UserHandle.myUserId());
247        return user != null ? user.isManagedProfile() : false;
248    }
249
250    /**
251     * Returns true if the given package does not exist on the device or if its version code is less
252     * than the given version, and false otherwise.
253     */
254    public static boolean packageRequiresUpdate(String packageName, int minSupportedVersion,
255            Context context) {
256        try {
257            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
258            if (packageInfo.versionCode >= minSupportedVersion) {
259                return false;
260            }
261        } catch (NameNotFoundException e) {
262            // Package not on device.
263        }
264
265        return true;
266    }
267
268    public static byte[] stringToByteArray(String s)
269        throws NumberFormatException {
270        try {
271            return Base64.decode(s, Base64.URL_SAFE);
272        } catch (IllegalArgumentException e) {
273            throw new NumberFormatException("Incorrect format. Should be Url-safe Base64 encoded.");
274        }
275    }
276
277    public static String byteArrayToString(byte[] bytes) {
278        return Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
279    }
280
281    public static void markDeviceProvisioned(Context context) {
282        if (isCurrentUserOwner()) {
283            // This only needs to be set once per device
284            Global.putInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 1);
285        }
286
287        // Setting this flag will either cause Setup Wizard to finish immediately when it starts (if
288        // it is not already running), or when its next activity starts (if it is already running,
289        // e.g. the non-NFC flow).
290        // When either of these things happen, a home intent is fired. We catch that in
291        // HomeReceiverActivity before sending the intent to notify the mdm that provisioning is
292        // complete.
293        // Note that, in the NFC flow or for secondary users, setting this flag will prevent the
294        // user from seeing SUW, even if no other device initialization app was specified.
295        Secure.putInt(context.getContentResolver(), Secure.USER_SETUP_COMPLETE, 1);
296    }
297
298    public static boolean isUserSetupCompleted(Context context) {
299        return Secure.getInt(context.getContentResolver(), Secure.USER_SETUP_COMPLETE, 0) != 0;
300    }
301
302    public static UserHandle getManagedProfile(Context context) {
303        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
304        int currentUserId = userManager.getUserHandle();
305        List<UserInfo> userProfiles = userManager.getProfiles(currentUserId);
306        for (UserInfo profile : userProfiles) {
307            if (profile.isManagedProfile()) {
308                return new UserHandle(profile.id);
309            }
310        }
311        return null;
312    }
313
314    /**
315     * @return The User id of an already existing managed profile or -1 if none
316     * exists
317     */
318    public static int alreadyHasManagedProfile(Context context) {
319        UserHandle managedUser = getManagedProfile(context);
320        if (managedUser != null) {
321            return managedUser.getIdentifier();
322        } else {
323            return -1;
324        }
325    }
326
327    public static void removeAccount(Context context, Account account) {
328        try {
329            AccountManager accountManager =
330                    (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
331            AccountManagerFuture<Bundle> bundle = accountManager.removeAccount(account,
332                    null, null /* callback */, null /* handler */);
333            // Block to get the result of the removeAccount operation
334            if (bundle.getResult().getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
335                ProvisionLogger.logw("Account removed from the primary user.");
336            } else {
337                Intent removeIntent = (Intent) bundle.getResult().getParcelable(
338                        AccountManager.KEY_INTENT);
339                if (removeIntent != null) {
340                    ProvisionLogger.logi("Starting activity to remove account");
341                    TrampolineActivity.startActivity(context, removeIntent);
342                } else {
343                    ProvisionLogger.logw("Could not remove account from the primary user.");
344                }
345            }
346        } catch (OperationCanceledException | AuthenticatorException | IOException e) {
347            ProvisionLogger.logw("Exception removing account from the primary user.", e);
348        }
349    }
350
351    public static boolean isFrpSupported(Context context) {
352        Object pdbManager = context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
353        return pdbManager != null;
354    }
355
356}
357