• 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 com.android.internal.logging.nano.MetricsProto.MetricsEvent
19         .PROVISIONING_INSTALL_PACKAGE_TASK_MS;
20 import static com.android.internal.util.Preconditions.checkNotNull;
21 
22 import android.annotation.NonNull;
23 import android.app.PendingIntent;
24 import android.app.admin.DevicePolicyManager;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.pm.PackageInstaller;
30 import android.content.pm.PackageManager;
31 import android.text.TextUtils;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.managedprovisioning.R;
35 import com.android.managedprovisioning.analytics.MetricsWriterFactory;
36 import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker;
37 import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences;
38 import com.android.managedprovisioning.common.ProvisionLogger;
39 import com.android.managedprovisioning.common.SettingsFacade;
40 import com.android.managedprovisioning.model.ProvisioningParams;
41 
42 import java.io.File;
43 import java.io.FileInputStream;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.OutputStream;
47 
48 /**
49  * Installs the management app apk from a download location provided by
50  * {@link DownloadPackageTask#getDownloadedPackageLocation()}.
51  */
52 public class InstallPackageTask extends AbstractProvisioningTask {
53     private static final String ACTION_INSTALL_DONE = InstallPackageTask.class.getName() + ".DONE.";
54 
55     public static final int ERROR_PACKAGE_INVALID = 0;
56     public static final int ERROR_INSTALLATION_FAILED = 1;
57 
58     private final DownloadPackageTask mDownloadPackageTask;
59 
60     private final PackageManager mPm;
61     private final DevicePolicyManager mDpm;
62 
63     /**
64      * Create an InstallPackageTask. When run, this will attempt to install the device admin package
65      * if it is non-null.
66      *
67      * {@see #run(String, String)} for more detail on package installation.
68      */
InstallPackageTask( DownloadPackageTask downloadPackageTask, Context context, ProvisioningParams params, Callback callback)69     public InstallPackageTask(
70             DownloadPackageTask downloadPackageTask,
71             Context context,
72             ProvisioningParams params,
73             Callback callback) {
74         this(downloadPackageTask, context, params, callback,
75                 new ProvisioningAnalyticsTracker(
76                         MetricsWriterFactory.getMetricsWriter(context, new SettingsFacade()),
77                         new ManagedProvisioningSharedPreferences(context)));
78     }
79 
80     @VisibleForTesting
InstallPackageTask( DownloadPackageTask downloadPackageTask, Context context, ProvisioningParams params, Callback callback, ProvisioningAnalyticsTracker provisioningAnalyticsTracker)81     InstallPackageTask(
82             DownloadPackageTask downloadPackageTask,
83             Context context,
84             ProvisioningParams params,
85             Callback callback,
86             ProvisioningAnalyticsTracker provisioningAnalyticsTracker) {
87         super(context, params, callback, provisioningAnalyticsTracker);
88 
89         mPm = context.getPackageManager();
90         mDpm = context.getSystemService(DevicePolicyManager.class);
91         mDownloadPackageTask = checkNotNull(downloadPackageTask);
92     }
93 
94     @Override
getStatusMsgId()95     public int getStatusMsgId() {
96         return R.string.progress_install;
97     }
98 
copyStream(@onNull InputStream in, @NonNull OutputStream out)99     private static void copyStream(@NonNull InputStream in, @NonNull OutputStream out)
100             throws IOException {
101         byte[] buffer = new byte[16 * 1024];
102         int numRead;
103         while ((numRead = in.read(buffer)) != -1) {
104             out.write(buffer, 0, numRead);
105         }
106     }
107 
108     /**
109      * Installs a package. The package will be installed from the given location if one is provided.
110      * If a null or empty location is provided, and the package is installed for a different user,
111      * it will be enabled for the calling user. If the package location is not provided and the
112      * package is not installed for any other users, this task will produce an error.
113      *
114      * Errors will be indicated if a downloaded package is invalid, or installation fails.
115      */
116     @Override
run(int userId)117     public void run(int userId) {
118         startTaskTimer();
119         String packageLocation = mDownloadPackageTask.getDownloadedPackageLocation();
120         String packageName = mProvisioningParams.inferDeviceAdminPackageName();
121 
122         ProvisionLogger.logi("Installing package " + packageName);
123         if (TextUtils.isEmpty(packageLocation)) {
124             // Do not log time if not installing any package, as that isn't useful.
125             success();
126             return;
127         }
128 
129         int installFlags = PackageManager.INSTALL_REPLACE_EXISTING;
130         // Current device owner (if exists) must be test-only, so it is fine to replace it with a
131         // test-only package of same package name. No need to further verify signature as
132         // installation will fail if signatures don't match.
133         if (mDpm.isDeviceOwnerApp(packageName)) {
134             installFlags |= PackageManager.INSTALL_ALLOW_TEST;
135         }
136 
137         PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
138                 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
139         params.installFlags = installFlags;
140 
141         File source = new File(packageLocation);
142         PackageInstaller pi = mPm.getPackageInstaller();
143         try {
144             int sessionId = pi.createSession(params);
145             try (PackageInstaller.Session session = pi.openSession(sessionId)) {
146                 try (FileInputStream in = new FileInputStream(source);
147                      OutputStream out = session.openWrite(source.getName(), 0, -1)) {
148                     copyStream(in, out);
149                 } catch (IOException e) {
150                     session.abandon();
151                     throw e;
152                 }
153 
154                 String action = ACTION_INSTALL_DONE + sessionId;
155                 mContext.registerReceiver(new PackageInstallReceiver(packageName),
156                         new IntentFilter(action));
157 
158                 PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, sessionId,
159                         new Intent(action),
160                         PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
161                 session.commit(pendingIntent.getIntentSender());
162             }
163         } catch (IOException e) {
164             ProvisionLogger.loge("Installing package " + packageName + " failed.", e);
165             error(ERROR_INSTALLATION_FAILED);
166         } finally {
167             source.delete();
168         }
169     }
170 
171     @Override
getMetricsCategory()172     protected int getMetricsCategory() {
173         return PROVISIONING_INSTALL_PACKAGE_TASK_MS;
174     }
175 
176     private class PackageInstallReceiver extends BroadcastReceiver {
177         private final String mPackageName;
178 
PackageInstallReceiver(String packageName)179         public PackageInstallReceiver(String packageName) {
180             mPackageName = packageName;
181         }
182 
183         @Override
onReceive(Context context, Intent intent)184         public void onReceive(Context context, Intent intent) {
185             // Should not happen as we use a one shot pending intent specifically for this receiver
186             if (intent.getAction() == null || !intent.getAction().startsWith(ACTION_INSTALL_DONE)) {
187                 ProvisionLogger.logw("Incorrect action");
188 
189                 error(ERROR_INSTALLATION_FAILED);
190                 return;
191             }
192 
193             // Should not happen as we use a one shot pending intent specifically for this receiver
194             if (!intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME).equals(mPackageName)) {
195                 ProvisionLogger.loge("Package doesn't have expected package name.");
196                 error(ERROR_PACKAGE_INVALID);
197                 return;
198             }
199 
200             int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
201             String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
202             int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 0);
203 
204             mContext.unregisterReceiver(this);
205             ProvisionLogger.logi(status + " " + legacyStatus + " " + statusMessage);
206 
207             if (status == PackageInstaller.STATUS_SUCCESS) {
208                 ProvisionLogger.logd("Package " + mPackageName + " is succesfully installed.");
209                 stopTaskTimer();
210                 success();
211             } else if (legacyStatus == PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE) {
212                 ProvisionLogger.logd("Current version of " + mPackageName
213                         + " higher than the version to be installed. It was not reinstalled.");
214                 // If the package is already at a higher version: success.
215                 // Do not log time if package is already at a higher version, as that isn't useful.
216                 success();
217             } else {
218                 ProvisionLogger.logd("Installing package " + mPackageName + " failed.");
219                 ProvisionLogger.logd("Status message returned  = " + statusMessage);
220                 error(ERROR_INSTALLATION_FAILED);
221             }
222         }
223     }
224 }
225