• 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 static android.app.PendingIntent.FLAG_MUTABLE;
19 import static android.app.PendingIntent.FLAG_ONE_SHOT;
20 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
21 import static android.content.pm.PackageManager.INSTALL_REPLACE_EXISTING;
22 
23 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_INSTALL_PACKAGE_TASK_MS;
24 
25 import static java.util.Objects.requireNonNull;
26 
27 import android.annotation.NonNull;
28 import android.app.PendingIntent;
29 import android.app.admin.DevicePolicyManager;
30 import android.content.BroadcastReceiver;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.content.pm.PackageInstaller;
35 import android.content.pm.PackageManager;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.managedprovisioning.analytics.MetricsWriterFactory;
39 import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker;
40 import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences;
41 import com.android.managedprovisioning.common.ProvisionLogger;
42 import com.android.managedprovisioning.common.SettingsFacade;
43 import com.android.managedprovisioning.common.Utils;
44 import com.android.managedprovisioning.model.ProvisioningParams;
45 
46 import java.io.File;
47 import java.io.FileInputStream;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.io.OutputStream;
51 import java.util.HashSet;
52 import java.util.Set;
53 
54 
55 /**
56  * Installs the management app apk from a download location provided by
57  * {@link PackageLocationProvider#getPackageLocation()}.
58  */
59 public class InstallPackageTask extends AbstractProvisioningTask {
60     private static final String ACTION_INSTALL_DONE = InstallPackageTask.class.getName() + ".DONE.";
61 
62     public static final int ERROR_PACKAGE_INVALID = 0;
63     public static final int ERROR_INSTALLATION_FAILED = 1;
64 
65     private final PackageLocationProvider mPackageLocationProvider;
66 
67     private final PackageManager mPm;
68     private final DevicePolicyManager mDpm;
69     private final PackageInstaller.SessionCallback mSessionCallback =  new SessionCallback();
70     private final String mPackageName;
71     private final Utils mUtils;
72 
73     private static final int SUCCESS_INSTALLED_BROADCAST = 1;
74     private static final int SUCCESS_INSTALLED_CALLBACK = 2;
75     private final Set<Integer> mSuccessCodes = new HashSet<>();
76 
77     /**
78      * Create an InstallPackageTask. When run, this will attempt to install the device admin package
79      * if it is non-null.
80      *
81      * {@see #run(String, String)} for more detail on package installation.
82      */
InstallPackageTask( PackageLocationProvider packageLocationProvider, Context context, ProvisioningParams params, Callback callback)83     public InstallPackageTask(
84             PackageLocationProvider packageLocationProvider,
85             Context context,
86             ProvisioningParams params,
87             Callback callback) {
88         this(packageLocationProvider, context, params, callback,
89                 new ProvisioningAnalyticsTracker(
90                         MetricsWriterFactory.getMetricsWriter(context, new SettingsFacade()),
91                         new ManagedProvisioningSharedPreferences(context)),
92                 new Utils());
93     }
94 
95     @VisibleForTesting
InstallPackageTask( PackageLocationProvider packageLocationProvider, Context context, ProvisioningParams params, Callback callback, ProvisioningAnalyticsTracker provisioningAnalyticsTracker, Utils utils)96     InstallPackageTask(
97             PackageLocationProvider packageLocationProvider,
98             Context context,
99             ProvisioningParams params,
100             Callback callback,
101             ProvisioningAnalyticsTracker provisioningAnalyticsTracker,
102             Utils utils) {
103         super(context, params, callback, provisioningAnalyticsTracker);
104 
105         mPm = context.getPackageManager();
106         mDpm = context.getSystemService(DevicePolicyManager.class);
107         mPackageLocationProvider = requireNonNull(packageLocationProvider);
108         mPackageName = requireNonNull(mProvisioningParams.inferDeviceAdminPackageName());
109         mUtils = requireNonNull(utils);
110     }
111 
copyStream(@onNull InputStream in, @NonNull OutputStream out)112     private static void copyStream(@NonNull InputStream in, @NonNull OutputStream out)
113             throws IOException {
114         byte[] buffer = new byte[16 * 1024];
115         int numRead;
116         while ((numRead = in.read(buffer)) != -1) {
117             out.write(buffer, 0, numRead);
118         }
119     }
120 
121     /**
122      * Installs a package. The package will be installed from the given location if one is provided.
123      * If a null or empty location is provided, and the package is installed for a different user,
124      * it will be enabled for the calling user. If the package location is not provided and the
125      * package is not installed for any other users, this task will produce an error.
126      *
127      * Errors will be indicated if a downloaded package is invalid, or installation fails.
128      */
129     @Override
run(int userId)130     public void run(int userId) {
131         startTaskTimer();
132 
133         File packageLocation = mPackageLocationProvider.getPackageLocation();
134         ProvisionLogger.logi("Installing package " + mPackageName + " on user " + userId + " from "
135                 + packageLocation);
136         if (packageLocation == null) {
137             success();
138             return;
139         }
140 
141         int installFlags = INSTALL_REPLACE_EXISTING;
142         // Current device owner (if exists) must be test-only, so it is fine to replace it with a
143         // test-only package of same package name. No need to further verify signature as
144         // installation will fail if signatures don't match.
145         if (mDpm.isDeviceOwnerApp(mPackageName)) {
146             installFlags |= PackageManager.INSTALL_ALLOW_TEST;
147         }
148 
149         PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
150                 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
151         params.installFlags |= installFlags;
152 
153         try {
154             installPackage(packageLocation, mPackageName, params, mContext, mSessionCallback);
155         } catch (IOException e) {
156             ProvisionLogger.loge("Installing package " + mPackageName + " failed.", e);
157             error(ERROR_INSTALLATION_FAILED);
158         } finally {
159             packageLocation.delete();
160         }
161     }
162 
installPackage( File source, String packageName, PackageInstaller.SessionParams params, Context context, PackageInstaller.SessionCallback sessionCallback)163     private void installPackage(
164             File source,
165             String packageName,
166             PackageInstaller.SessionParams params,
167             Context context,
168             PackageInstaller.SessionCallback sessionCallback)
169             throws IOException {
170         PackageInstaller pi = context.getPackageManager().getPackageInstaller();
171         context.registerReceiver(
172                 new PackageAddedReceiver(packageName),
173                 createPackageAddedIntentFilter());
174         pi.registerSessionCallback(sessionCallback);
175         int sessionId = pi.createSession(params);
176         try (PackageInstaller.Session session = pi.openSession(sessionId)) {
177             try (FileInputStream in = new FileInputStream(source);
178                  OutputStream out = session.openWrite(source.getName(), 0, -1)) {
179                 copyStream(in, out);
180             } catch (IOException e) {
181                 session.abandon();
182                 throw e;
183             }
184 
185             String action = ACTION_INSTALL_DONE + sessionId;
186             PendingIntent pendingIntent = PendingIntent.getBroadcast(
187                     context,
188                     sessionId,
189                     new Intent(action),
190                     FLAG_ONE_SHOT | FLAG_UPDATE_CURRENT | FLAG_MUTABLE);
191             session.commit(pendingIntent.getIntentSender());
192         }
193     }
194 
createPackageAddedIntentFilter()195     private IntentFilter createPackageAddedIntentFilter() {
196         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
197         intentFilter.addDataScheme("package");
198         return intentFilter;
199     }
200 
201     @Override
getMetricsCategory()202     protected int getMetricsCategory() {
203         return PROVISIONING_INSTALL_PACKAGE_TASK_MS;
204     }
205 
addSuccessStatus(int successStatus)206     private void addSuccessStatus(int successStatus) {
207         mSuccessCodes.add(successStatus);
208         if (mSuccessCodes.contains(SUCCESS_INSTALLED_BROADCAST)
209                 && mSuccessCodes.contains(SUCCESS_INSTALLED_CALLBACK)) {
210             ProvisionLogger.logd("Package " + mPackageName + " is successfully installed.");
211             stopTaskTimer();
212             success();
213         }
214     }
215 
216     private class PackageAddedReceiver extends BroadcastReceiver {
217 
218         private final String mPackageName;
219 
PackageAddedReceiver(String packageName)220         PackageAddedReceiver(String packageName) {
221             mPackageName = requireNonNull(packageName);
222         }
223 
224         @Override
onReceive(Context context, Intent intent)225         public void onReceive(Context context, Intent intent) {
226             ProvisionLogger.logd("PACKAGE_ADDED broadcast received with intent data "
227                     + intent.getDataString());
228             if (!mPackageName.equals(extractPackageNameFromDataString(intent.getDataString()))) {
229                 ProvisionLogger.logd("The package name provided in the intent data does not equal "
230                         + mPackageName);
231                 return;
232             }
233             addSuccessStatus(SUCCESS_INSTALLED_BROADCAST);
234             context.unregisterReceiver(this);
235         }
236 
extractPackageNameFromDataString(String dataString)237         private String extractPackageNameFromDataString(String dataString) {
238             return dataString.substring("package:".length());
239         }
240     }
241 
242     private class SessionCallback extends PackageInstaller.SessionCallback {
243 
244         @Override
onCreated(int sessionId)245         public void onCreated(int sessionId) {}
246 
247         @Override
onBadgingChanged(int sessionId)248         public void onBadgingChanged(int sessionId) {}
249 
250         @Override
onActiveChanged(int sessionId, boolean active)251         public void onActiveChanged(int sessionId, boolean active) {}
252 
253         @Override
onProgressChanged(int sessionId, float progress)254         public void onProgressChanged(int sessionId, float progress) {}
255 
256         @Override
onFinished(int sessionId, boolean success)257         public void onFinished(int sessionId, boolean success) {
258             PackageInstaller packageInstaller = mPm.getPackageInstaller();
259             packageInstaller.unregisterSessionCallback(mSessionCallback);
260             if (!success) {
261                 boolean packageInstalled =
262                         mUtils.isPackageInstalled(mPackageName, mContext.getPackageManager());
263                 if (packageInstalled) {
264                     ProvisionLogger.logd("Current version of " + mPackageName
265                             + " higher than the version to be installed. It was not reinstalled.");
266                     // If the package is already at a higher version: success.
267                     // Do not log time if package is already at a higher version, as that isn't
268                     // useful.
269                     success();
270                     return;
271                 } else {
272                     ProvisionLogger.logd("Installing package " + mPackageName + " failed.");
273                     error(ERROR_INSTALLATION_FAILED);
274                     return;
275                 }
276             }
277             ProvisionLogger.logd("Install package callback received for " + mPackageName);
278             addSuccessStatus(SUCCESS_INSTALLED_CALLBACK);
279         }
280     }
281 }
282