• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.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 initializer
59      * and device admin packages if they are 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         for (ActivityInfo ai : pi.receivers) {
134             if (!TextUtils.isEmpty(ai.permission) &&
135                     ai.permission.equals(android.Manifest.permission.BIND_DEVICE_ADMIN)) {
136                 return true;
137             }
138         }
139         ProvisionLogger.loge("Installed package has no admin receiver.");
140         mCallback.onError(ERROR_PACKAGE_INVALID);
141         return false;
142     }
143 
144     private class PackageInstallObserver extends IPackageInstallObserver.Stub {
145         private final InstallInfo mInstallInfo;
146 
PackageInstallObserver(InstallInfo installInfo)147         public PackageInstallObserver(InstallInfo installInfo) {
148             mInstallInfo = installInfo;
149         }
150 
151         @Override
packageInstalled(String packageName, int returnCode)152         public void packageInstalled(String packageName, int returnCode) {
153             if (packageName != null && !packageName.equals(mInstallInfo.packageName))  {
154                 ProvisionLogger.loge("Package doesn't have expected package name.");
155                 mCallback.onError(ERROR_PACKAGE_INVALID);
156                 return;
157             }
158             if (returnCode == PackageManager.INSTALL_SUCCEEDED) {
159                 mInstallInfo.doneInstalling = true;
160                 ProvisionLogger.logd(
161                         "Package " + mInstallInfo.packageName + " is succesfully installed.");
162                 checkSuccess();
163             } else if (returnCode == PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE) {
164                 mInstallInfo.doneInstalling = true;
165                 ProvisionLogger.logd("Current version of " + mInstallInfo.packageName
166                         + " higher than the version to be installed. It was not reinstalled.");
167                 checkSuccess();
168             } else {
169                 ProvisionLogger.logd(
170                         "Installing package " + mInstallInfo.packageName + " failed.");
171                 ProvisionLogger.logd(
172                         "Errorcode returned by IPackageInstallObserver = " + returnCode);
173                 mCallback.onError(ERROR_INSTALLATION_FAILED);
174             }
175         }
176     }
177 
178     /**
179      * Calls the success callback once all of the packages that needed to be installed are
180      * successfully installed.
181      */
checkSuccess()182     private void checkSuccess() {
183         for (InstallInfo info : mPackagesToInstall) {
184             if (!info.doneInstalling) {
185                 return;
186             }
187         }
188         // Set package verification flag to its original value.
189         Global.putInt(mContext.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE,
190                 mPackageVerifierEnable);
191         mCallback.onSuccess();
192     }
193 
194     /**
195      * Attempt to install this package from an existing package installed under a different user.
196      * If this package is already installed for this user, this is a no-op. If it is not installed
197      * for another user, this will produce an error.
198      * @param info The package to install
199      */
installExistingPackage(InstallInfo info)200     private void installExistingPackage(InstallInfo info) {
201         try {
202             ProvisionLogger.logi("Installing existing package " + info.packageName);
203             mPm.installExistingPackage(info.packageName);
204             info.doneInstalling = true;
205         } catch (PackageManager.NameNotFoundException e) {
206             mCallback.onError(ERROR_PACKAGE_INVALID);
207             return;
208         }
209         checkSuccess();
210     }
211 
212     public abstract static class Callback {
onSuccess()213         public abstract void onSuccess();
onError(int errorCode)214         public abstract void onError(int errorCode);
215     }
216 
217     private static class InstallInfo {
218         public String packageName;
219         public String location;
220         public boolean doneInstalling;
221 
InstallInfo(String packageName, String location)222         public InstallInfo(String packageName, String location) {
223             this.packageName = packageName;
224             this.location = location;
225         }
226     }
227 }
228