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