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.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
19 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_DOWNLOAD_PACKAGE_TASK_MS;
20 import static com.android.internal.util.Preconditions.checkNotNull;
21 import static java.lang.String.format;
22 
23 import android.app.DownloadManager;
24 import android.app.DownloadManager.Query;
25 import android.app.DownloadManager.Request;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.database.Cursor;
31 import android.net.Uri;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.provider.Settings;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.managedprovisioning.analytics.MetricsWriterFactory;
38 import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker;
39 import com.android.managedprovisioning.common.Globals;
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.PackageDownloadInfo;
45 import com.android.managedprovisioning.model.ProvisioningParams;
46 
47 import java.io.File;
48 import java.net.URI;
49 
50 /**
51  * Downloads the management app apk from the url provided by {@link PackageDownloadInfo#location}.
52  * The location of the downloaded file can be read via {@link PackageLocationProvider
53  * #getDownloadLocation()}}.
54  */
55 public class DownloadPackageTask extends AbstractProvisioningTask
56         implements PackageLocationProvider {
57     public static final int ERROR_DOWNLOAD_FAILED = 0;
58     public static final int ERROR_OTHER = 1;
59 
60     private BroadcastReceiver mReceiver;
61     private final DownloadManager mDownloadManager;
62     private final String mPackageName;
63     private final PackageDownloadInfo mPackageDownloadInfo;
64     private long mDownloadId;
65 
66     private final Utils mUtils;
67 
68     private File mDownloadLocationTo; //local file where the package is downloaded.
69     private boolean mDoneDownloading;
70 
DownloadPackageTask( Context context, ProvisioningParams provisioningParams, PackageDownloadInfo packageDownloadInfo, Callback callback)71     public DownloadPackageTask(
72             Context context,
73             ProvisioningParams provisioningParams,
74             PackageDownloadInfo packageDownloadInfo,
75             Callback callback) {
76         this(new Utils(), context, provisioningParams, packageDownloadInfo, callback,
77                 new ProvisioningAnalyticsTracker(
78                         MetricsWriterFactory.getMetricsWriter(context, new SettingsFacade()),
79                         new ManagedProvisioningSharedPreferences(context)));
80     }
81 
82     @VisibleForTesting
DownloadPackageTask( Utils utils, Context context, ProvisioningParams provisioningParams, PackageDownloadInfo packageDownloadInfo, Callback callback, ProvisioningAnalyticsTracker provisioningAnalyticsTracker)83     DownloadPackageTask(
84             Utils utils,
85             Context context,
86             ProvisioningParams provisioningParams,
87             PackageDownloadInfo packageDownloadInfo,
88             Callback callback,
89             ProvisioningAnalyticsTracker provisioningAnalyticsTracker) {
90         super(context, provisioningParams, callback, provisioningAnalyticsTracker);
91 
92         mUtils = checkNotNull(utils);
93         mDownloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
94         mDownloadManager.setAccessFilename(true);
95         mPackageName = provisioningParams.inferDeviceAdminPackageName();
96         mPackageDownloadInfo = checkNotNull(packageDownloadInfo);
97     }
98 
99     @Override
run(int userId)100     public void run(int userId) {
101         startTaskTimer();
102         ProvisionLogger.logd(format("Starting download of %s on user %s for user %s", mPackageName, mContext.getUserId(), userId));
103         if (!mUtils.packageRequiresUpdate(mPackageName, mPackageDownloadInfo.minVersion,
104                 mContext)) {
105             // Do not log time if package is already on device and does not require an update, as
106             // that isn't useful.
107             ProvisionLogger.logd(format("No update needed for %s", mPackageName));
108             success();
109             return;
110         }
111         if (!mUtils.isConnectedToNetwork(mContext)) {
112             ProvisionLogger.loge("DownloadPackageTask: not connected to the network, can't download"
113                     + " the package");
114             error(ERROR_OTHER);
115             return;
116         }
117 
118         setDpcDownloadedSetting(mContext);
119 
120         ProvisionLogger.logd(format("Creating download receiver for %s", mPackageName));
121         mReceiver = createDownloadReceiver();
122         // register the receiver on the worker thread to avoid threading issues with respect to
123         // the location variable
124         ProvisionLogger.logd(format("Registering download receiver for %s", mPackageName));
125         mContext.registerReceiver(mReceiver,
126                 new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
127                 null,
128                 new Handler(Looper.myLooper()),
129                 Context.RECEIVER_EXPORTED);
130 
131         Request request = new Request(Uri.parse(mPackageDownloadInfo.location));
132 
133         // Note that the apk may not actually be downloaded to this path. This could happen if
134         // this file already exists.
135         String path = mContext.getExternalFilesDir(null)
136                 + "/download_cache/managed_provisioning_downloaded_app.apk";
137         File downloadedFile = new File(path);
138         downloadedFile.getParentFile().mkdirs(); // If the folder doesn't exists it is created
139         request.setDestinationUri(Uri.fromFile(downloadedFile));
140 
141         if (mPackageDownloadInfo.cookieHeader != null) {
142             request.addRequestHeader("Cookie", mPackageDownloadInfo.cookieHeader);
143             if (Globals.DEBUG) {
144                 ProvisionLogger.logd("Downloading with http cookie header: "
145                         + mPackageDownloadInfo.cookieHeader);
146             }
147         }
148         ProvisionLogger.logd(format("Starting download for %s from %s into %s", mPackageName, mPackageDownloadInfo.location, path));
149         mDownloadId = mDownloadManager.enqueue(request);
150     }
151 
152     /**
153      * Set MANAGED_PROVISIONING_DPC_DOWNLOADED to 1, which will prevent restarting setup-wizard.
154      *
155      * <p>See b/132261064.
156      */
setDpcDownloadedSetting(Context context)157     private static void setDpcDownloadedSetting(Context context) {
158         Settings.Secure.putInt(
159                 context.getContentResolver(), MANAGED_PROVISIONING_DPC_DOWNLOADED, 1);
160     }
161 
162     @Override
getMetricsCategory()163     protected int getMetricsCategory() {
164         return PROVISIONING_DOWNLOAD_PACKAGE_TASK_MS;
165     }
166 
createDownloadReceiver()167     private BroadcastReceiver createDownloadReceiver() {
168         return new BroadcastReceiver() {
169             @Override
170             public void onReceive(Context context, Intent intent) {
171                 if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
172                     ProvisionLogger.logd(format("Download receiver invoked for %s", mPackageName));
173                     Query q = new Query();
174                     q.setFilterById(mDownloadId);
175                     Cursor c = mDownloadManager.query(q);
176                     if (c.moveToFirst()) {
177                         int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
178                         if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
179                             mDownloadLocationTo = new File(URI.create(c.getString(
180                                     c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))));
181                             c.close();
182                             onDownloadSuccess();
183                         } else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)) {
184                             final int reason =
185                                     c.getInt(c.getColumnIndex(DownloadManager.COLUMN_REASON));
186                             c.close();
187                             onDownloadFail(reason);
188                         }
189                     }
190                 }
191             }
192         };
193     }
194 
195     /**
196      * For a successful download, check that the downloaded file is the expected file.
197      * If the package hash is provided then that is used, otherwise a signature hash is used.
198      */
199     private void onDownloadSuccess() {
200         if (mDoneDownloading) {
201             // DownloadManager can send success more than once. Only act first time.
202             return;
203         }
204 
205         ProvisionLogger.logd("Downloaded successfully to: "
206                 + mDownloadLocationTo.getAbsolutePath());
207         mDoneDownloading = true;
208         stopTaskTimer();
209         success();
210     }
211 
212     @Override
213     public File getPackageLocation() {
214         return mDownloadLocationTo;
215     }
216 
217     private void onDownloadFail(int errorCode) {
218         ProvisionLogger.loge("Downloading package failed (download id " + mDownloadId
219                 + "). COLUMN_REASON in DownloadManager response has value: " + errorCode);
220         error(ERROR_DOWNLOAD_FAILED);
221     }
222 
223     public void cleanUp() {
224         if (mReceiver != null) {
225             //Unregister receiver.
226             mContext.unregisterReceiver(mReceiver);
227             mReceiver = null;
228         }
229 
230         boolean removeSuccess = mDownloadManager.remove(mDownloadId) == 1;
231         if (removeSuccess) {
232             ProvisionLogger.logd("Successfully removed installer file.");
233         } else {
234             ProvisionLogger.loge("Could not remove installer file.");
235             // Ignore this error. Failing cleanup should not stop provisioning flow.
236         }
237     }
238 }
239