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 package com.android.managedprovisioning.task;
17 
18 import android.app.DownloadManager;
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.pm.IPackageInstallObserver;
22 import android.content.pm.ActivityInfo;
23 import android.content.pm.PackageInfo;
24 import android.content.pm.PackageManager;
25 import android.net.Uri;
26 import android.provider.Settings.Global;
27 import android.text.TextUtils;
28 import android.Manifest.permission;
29 
30 import com.android.managedprovisioning.ProvisionLogger;
31 import com.android.managedprovisioning.model.ProvisioningParams;
32 
33 import java.io.File;
34 import java.util.HashSet;
35 import java.util.Set;
36 
37 /**
38  * Installs all packages that were added. Can install a downloaded apk, or install an existing
39  * package which is already installed for a different user.
40  * <p>
41  * Before installing from a downloaded file, each file is checked to ensure it contains the correct
42  * package and admin receiver.
43  * </p>
44  */
45 public class InstallPackageTask {
46     public static final int ERROR_PACKAGE_INVALID = 0;
47     public static final int ERROR_INSTALLATION_FAILED = 1;
48     public static final int ERROR_PACKAGE_NAME_INVALID = 2;
49 
50     private final Context mContext;
51     private final Callback mCallback;
52 
53     private PackageManager mPm;
54     private int mPackageVerifierEnable;
55     private Set<InstallInfo> mPackagesToInstall;
56 
57     /**
58      * Create an InstallPackageTask. When run, this will attempt to install the device admin
59      * packages if it is non-null.
60      *
61      * {@see #run(String, String)} for more detail on package installation.
62      */
InstallPackageTask(Context context, Callback callback)63     public InstallPackageTask (Context context, Callback callback) {
64         mCallback = callback;
65         mContext = context;
66         mPackagesToInstall = new HashSet<InstallInfo>();
67         mPm = mContext.getPackageManager();
68     }
69 
70     /**
71      * Should be called before {@link #run}.
72      */
addInstallIfNecessary(String packageName, String packageLocation)73     public void addInstallIfNecessary(String packageName, String packageLocation) {
74         if (!TextUtils.isEmpty(packageName)) {
75             mPackagesToInstall.add(new InstallInfo(packageName, packageLocation));
76         }
77     }
78 
79     /**
80      * Install all packages given by {@link #addPackageToInstall}. Each package will be installed
81      * from the given location if one is provided. If a null or empty location is provided, and the
82      * package is installed for a different user, it will be enabled for the calling user. If the
83      * package location is not provided and the package is not installed for any other users, this
84      * task will produce an error.
85      *
86      * Errors will be indicated if a downloaded package is invalid, or installation fails.
87      */
run()88     public void run() {
89         if (mPackagesToInstall.size() == 0) {
90             ProvisionLogger.loge("No downloaded packages to install");
91             mCallback.onSuccess();
92             return;
93         }
94         ProvisionLogger.logi("Installing package(s)");
95 
96         for (InstallInfo info : mPackagesToInstall) {
97             if (TextUtils.isEmpty(info.location)) {
98                 installExistingPackage(info);
99 
100             } else if (packageContentIsCorrect(info.packageName, info.location)) {
101                 // Temporarily turn off package verification.
102                 mPackageVerifierEnable = Global.getInt(mContext.getContentResolver(),
103                         Global.PACKAGE_VERIFIER_ENABLE, 1);
104                 Global.putInt(mContext.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE, 0);
105 
106                 // Allow for replacing an existing package.
107                 // Needed in case this task is performed multiple times.
108                 mPm.installPackage(Uri.parse("file://" + info.location),
109                         new PackageInstallObserver(info),
110                         /* flags */ PackageManager.INSTALL_REPLACE_EXISTING,
111                         mContext.getPackageName());
112             } else {
113                 // Error should have been reported in packageContentIsCorrect().
114                 return;
115             }
116         }
117     }
118 
packageContentIsCorrect(String packageName, String packageLocation)119     private boolean packageContentIsCorrect(String packageName, String packageLocation) {
120         PackageInfo pi = mPm.getPackageArchiveInfo(packageLocation, PackageManager.GET_RECEIVERS);
121         if (pi == null) {
122             ProvisionLogger.loge("Package could not be parsed successfully.");
123             mCallback.onError(ERROR_PACKAGE_INVALID);
124             return false;
125         }
126         if (!pi.packageName.equals(packageName)) {
127             ProvisionLogger.loge("Package name in apk (" + pi.packageName
128                     + ") does not match package name specified by programmer ("
129                     + packageName + ").");
130             mCallback.onError(ERROR_PACKAGE_INVALID);
131             return false;
132         }
133         if (pi.receivers != null) {
134             for (ActivityInfo ai : pi.receivers) {
135                 if (!TextUtils.isEmpty(ai.permission) &&
136                         ai.permission.equals(android.Manifest.permission.BIND_DEVICE_ADMIN)) {
137                     return true;
138                 }
139             }
140         }
141         ProvisionLogger.loge("Installed package has no admin receiver.");
142         mCallback.onError(ERROR_PACKAGE_INVALID);
143         return false;
144     }
145 
146     private class PackageInstallObserver extends IPackageInstallObserver.Stub {
147         private final InstallInfo mInstallInfo;
148 
PackageInstallObserver(InstallInfo installInfo)149         public PackageInstallObserver(InstallInfo installInfo) {
150             mInstallInfo = installInfo;
151         }
152 
153         @Override
packageInstalled(String packageName, int returnCode)154         public void packageInstalled(String packageName, int returnCode) {
155             if (packageName != null && !packageName.equals(mInstallInfo.packageName))  {
156                 ProvisionLogger.loge("Package doesn't have expected package name.");
157                 mCallback.onError(ERROR_PACKAGE_INVALID);
158                 return;
159             }
160             if (returnCode == PackageManager.INSTALL_SUCCEEDED) {
161                 mInstallInfo.doneInstalling = true;
162                 ProvisionLogger.logd(
163                         "Package " + mInstallInfo.packageName + " is succesfully installed.");
164                 checkSuccess();
165             } else if (returnCode == PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE) {
166                 mInstallInfo.doneInstalling = true;
167                 ProvisionLogger.logd("Current version of " + mInstallInfo.packageName
168                         + " higher than the version to be installed. It was not reinstalled.");
169                 checkSuccess();
170             } else {
171                 ProvisionLogger.logd(
172                         "Installing package " + mInstallInfo.packageName + " failed.");
173                 ProvisionLogger.logd(
174                         "Errorcode returned by IPackageInstallObserver = " + returnCode);
175                 mCallback.onError(ERROR_INSTALLATION_FAILED);
176             }
177             // remove the file containing the apk in order not to use too much space.
178             new File(mInstallInfo.location).delete();
179         }
180     }
181 
182     /**
183      * Calls the success callback once all of the packages that needed to be installed are
184      * successfully installed.
185      */
checkSuccess()186     private void checkSuccess() {
187         for (InstallInfo info : mPackagesToInstall) {
188             if (!info.doneInstalling) {
189                 return;
190             }
191         }
192         // Set package verification flag to its original value.
193         Global.putInt(mContext.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE,
194                 mPackageVerifierEnable);
195         mCallback.onSuccess();
196     }
197 
198     /**
199      * Attempt to install this package from an existing package installed under a different user.
200      * If this package is already installed for this user, this is a no-op. If it is not installed
201      * for another user, this will produce an error.
202      * @param info The package to install
203      */
installExistingPackage(InstallInfo info)204     private void installExistingPackage(InstallInfo info) {
205         try {
206             ProvisionLogger.logi("Installing existing package " + info.packageName);
207             mPm.installExistingPackage(info.packageName);
208             info.doneInstalling = true;
209         } catch (PackageManager.NameNotFoundException e) {
210             mCallback.onError(ERROR_PACKAGE_INVALID);
211             return;
212         }
213         checkSuccess();
214     }
215 
216     public abstract static class Callback {
onSuccess()217         public abstract void onSuccess();
onError(int errorCode)218         public abstract void onError(int errorCode);
219     }
220 
221     private static class InstallInfo {
222         public String packageName;
223         public String location;
224         public boolean doneInstalling;
225 
InstallInfo(String packageName, String location)226         public InstallInfo(String packageName, String location) {
227             this.packageName = packageName;
228             this.location = location;
229         }
230     }
231 }
232