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 */
16package com.android.managedprovisioning.task;
17
18import android.app.DownloadManager;
19import android.content.Context;
20import android.content.pm.IPackageInstallObserver;
21import android.content.pm.ActivityInfo;
22import android.content.pm.PackageInfo;
23import android.content.pm.PackageManager;
24import android.net.Uri;
25import android.provider.Settings.Global;
26import android.text.TextUtils;
27import android.Manifest.permission;
28
29import com.android.managedprovisioning.ProvisionLogger;
30
31import java.io.File;
32
33/**
34 * Installs a device owner package from a given path.
35 * <p>
36 * Before installing it is checked whether the file at the specified path contains the given package
37 * and the given admin receiver.
38 * </p>
39 */
40public class InstallPackageTask {
41    public static final int ERROR_PACKAGE_INVALID = 0;
42    public static final int ERROR_INSTALLATION_FAILED = 1;
43
44    private final Context mContext;
45    private final Callback mCallback;
46    private final String mPackageName;
47
48    private String mPackageLocation;
49    private PackageManager mPm;
50    private int mPackageVerifierEnable;
51
52    public InstallPackageTask (Context context, String packageName,
53            Callback callback) {
54        mCallback = callback;
55        mContext = context;
56        mPackageLocation = null; // Initialized in run().
57        mPackageName = packageName;
58    }
59
60    public void run(String packageLocation) {
61        if (TextUtils.isEmpty(packageLocation)) {
62            ProvisionLogger.loge("Package Location is empty.");
63            mCallback.onError(ERROR_PACKAGE_INVALID);
64            return;
65        }
66        mPackageLocation = packageLocation;
67
68        PackageInstallObserver observer = new PackageInstallObserver();
69        mPm = mContext.getPackageManager();
70
71        if (packageContentIsCorrect()) {
72            // Temporarily turn off package verification.
73            mPackageVerifierEnable = Global.getInt(mContext.getContentResolver(),
74                    Global.PACKAGE_VERIFIER_ENABLE, 1);
75            Global.putInt(mContext.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE, 0);
76
77            Uri packageUri = Uri.parse("file://" + mPackageLocation);
78
79            // Allow for replacing an existing package.
80            // Needed in case this task is performed multiple times.
81            mPm.installPackage(packageUri, observer,
82                    /* flags */ PackageManager.INSTALL_REPLACE_EXISTING, mContext.getPackageName());
83        } else {
84            // Error should have been reported in packageContentIsCorrect().
85            return;
86        }
87    }
88
89    private boolean packageContentIsCorrect() {
90        PackageInfo pi = mPm.getPackageArchiveInfo(mPackageLocation,
91                PackageManager.GET_RECEIVERS);
92        if (pi == null) {
93            ProvisionLogger.loge("Package could not be parsed successfully.");
94            mCallback.onError(ERROR_PACKAGE_INVALID);
95            return false;
96        }
97        if (!pi.packageName.equals(mPackageName)) {
98            ProvisionLogger.loge("Package name in apk (" + pi.packageName
99                    + ") does not match package name specified by programmer ("
100                    + mPackageName + ").");
101            mCallback.onError(ERROR_PACKAGE_INVALID);
102            return false;
103        }
104        for (ActivityInfo ai : pi.receivers) {
105            if (!TextUtils.isEmpty(ai.permission) &&
106                    ai.permission.equals(android.Manifest.permission.BIND_DEVICE_ADMIN)) {
107                return true;
108            }
109        }
110        ProvisionLogger.loge("Installed package has no admin receiver.");
111        mCallback.onError(ERROR_PACKAGE_INVALID);
112        return false;
113    }
114
115    private class PackageInstallObserver extends IPackageInstallObserver.Stub {
116        @Override
117        public void packageInstalled(String packageName, int returnCode) {
118            // Set package verification flag to its original value.
119            Global.putInt(mContext.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE,
120                    mPackageVerifierEnable);
121
122            if (returnCode == PackageManager.INSTALL_SUCCEEDED
123                    && mPackageName.equals(packageName)) {
124                ProvisionLogger.logd("Package " + mPackageName + " is succesfully installed.");
125
126                mCallback.onSuccess();
127            } else {
128                if (returnCode == PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE) {
129                    ProvisionLogger.logd("Current version of " + mPackageName
130                            + " higher than the version to be installed.");
131                    ProvisionLogger.logd("Package " + mPackageName + " was not reinstalled.");
132                    mCallback.onSuccess();
133                    return;
134                }
135
136                ProvisionLogger.logd("Installing package " + mPackageName + " failed.");
137                ProvisionLogger.logd("Errorcode returned by IPackageInstallObserver = "
138                        + returnCode);
139                mCallback.onError(ERROR_INSTALLATION_FAILED);
140            }
141        }
142    }
143
144    public abstract static class Callback {
145        public abstract void onSuccess();
146        public abstract void onError(int errorCode);
147    }
148}