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 
20 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_DOWNLOAD_PACKAGE_TASK_MS;
21 import static com.android.internal.util.Preconditions.checkNotNull;
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.ManagedProvisioningSharedPreferences;
40 import com.android.managedprovisioning.common.ProvisionLogger;
41 import com.android.managedprovisioning.R;
42 import com.android.managedprovisioning.common.Globals;
43 import com.android.managedprovisioning.common.SettingsFacade;
44 import com.android.managedprovisioning.common.Utils;
45 import com.android.managedprovisioning.model.PackageDownloadInfo;
46 import com.android.managedprovisioning.model.ProvisioningParams;
47 
48 import java.io.File;
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 #getDownloadedPackageLocation()}.
53  */
54 public class DownloadPackageTask extends AbstractProvisioningTask {
55     public static final int ERROR_DOWNLOAD_FAILED = 0;
56     public static final int ERROR_OTHER = 1;
57 
58     private BroadcastReceiver mReceiver;
59     private final DownloadManager mDownloadManager;
60     private final String mPackageName;
61     private final PackageDownloadInfo mPackageDownloadInfo;
62     private long mDownloadId;
63 
64     private final Utils mUtils;
65 
66     private String mDownloadLocationTo; //local file where the package is downloaded.
67     private boolean mDoneDownloading;
68 
DownloadPackageTask( Context context, ProvisioningParams provisioningParams, Callback callback)69     public DownloadPackageTask(
70             Context context,
71             ProvisioningParams provisioningParams,
72             Callback callback) {
73         this(new Utils(), context, provisioningParams, callback,
74                 new ProvisioningAnalyticsTracker(
75                         MetricsWriterFactory.getMetricsWriter(context, new SettingsFacade()),
76                         new ManagedProvisioningSharedPreferences(context)));
77     }
78 
79     @VisibleForTesting
DownloadPackageTask( Utils utils, Context context, ProvisioningParams provisioningParams, Callback callback, ProvisioningAnalyticsTracker provisioningAnalyticsTracker)80     DownloadPackageTask(
81             Utils utils,
82             Context context,
83             ProvisioningParams provisioningParams,
84             Callback callback,
85             ProvisioningAnalyticsTracker provisioningAnalyticsTracker) {
86         super(context, provisioningParams, callback, provisioningAnalyticsTracker);
87 
88         mUtils = checkNotNull(utils);
89         mDownloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
90         mDownloadManager.setAccessFilename(true);
91         mPackageName = provisioningParams.inferDeviceAdminPackageName();
92         mPackageDownloadInfo = checkNotNull(provisioningParams.deviceAdminDownloadInfo);
93     }
94 
95     @Override
getStatusMsgId()96     public int getStatusMsgId() {
97         return R.string.progress_download;
98     }
99 
100     @Override
run(int userId)101     public void run(int userId) {
102         startTaskTimer();
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             success();
108             return;
109         }
110         if (!mUtils.isConnectedToNetwork(mContext)) {
111             ProvisionLogger.loge("DownloadPackageTask: not connected to the network, can't download"
112                     + " the package");
113             error(ERROR_OTHER);
114             return;
115         }
116 
117         setDpcDownloadedSetting(mContext);
118 
119         mReceiver = createDownloadReceiver();
120         // register the receiver on the worker thread to avoid threading issues with respect to
121         // the location variable
122         mContext.registerReceiver(mReceiver,
123                 new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
124                 null,
125                 new Handler(Looper.myLooper()));
126 
127         if (Globals.DEBUG) {
128             ProvisionLogger.logd("Starting download from " + mPackageDownloadInfo.location);
129         }
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         mDownloadId = mDownloadManager.enqueue(request);
149     }
150 
151     /**
152      * Set MANAGED_PROVISIONING_DPC_DOWNLOADED to 1, which will prevent restarting setup-wizard.
153      *
154      * <p>See b/132261064.
155      */
setDpcDownloadedSetting(Context context)156     private static void setDpcDownloadedSetting(Context context) {
157         Settings.Secure.putInt(
158                 context.getContentResolver(), MANAGED_PROVISIONING_DPC_DOWNLOADED, 1);
159     }
160 
161     @Override
getMetricsCategory()162     protected int getMetricsCategory() {
163         return PROVISIONING_DOWNLOAD_PACKAGE_TASK_MS;
164     }
165 
createDownloadReceiver()166     private BroadcastReceiver createDownloadReceiver() {
167         return new BroadcastReceiver() {
168             @Override
169             public void onReceive(Context context, Intent intent) {
170                 if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
171                     Query q = new Query();
172                     q.setFilterById(mDownloadId);
173                     Cursor c = mDownloadManager.query(q);
174                     if (c.moveToFirst()) {
175                         int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
176                         if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
177                             mDownloadLocationTo = c.getString(
178                                     c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
179                             c.close();
180                             onDownloadSuccess();
181                         } else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)) {
182                             int reason = c.getColumnIndex(DownloadManager.COLUMN_REASON);
183                             c.close();
184                             onDownloadFail(reason);
185                         }
186                     }
187                 }
188             }
189         };
190     }
191 
192     /**
193      * For a successful download, check that the downloaded file is the expected file.
194      * If the package hash is provided then that is used, otherwise a signature hash is used.
195      */
196     private void onDownloadSuccess() {
197         if (mDoneDownloading) {
198             // DownloadManager can send success more than once. Only act first time.
199             return;
200         }
201 
202         ProvisionLogger.logd("Downloaded succesfully to: " + mDownloadLocationTo);
203         mDoneDownloading = true;
204         stopTaskTimer();
205         success();
206     }
207 
208     public String getDownloadedPackageLocation() {
209         return mDownloadLocationTo;
210     }
211 
212     private void onDownloadFail(int errorCode) {
213         ProvisionLogger.loge("Downloading package failed.");
214         ProvisionLogger.loge("COLUMN_REASON in DownloadManager response has value: "
215                 + errorCode);
216         error(ERROR_DOWNLOAD_FAILED);
217     }
218 
219     public void cleanUp() {
220         if (mReceiver != null) {
221             //Unregister receiver.
222             mContext.unregisterReceiver(mReceiver);
223             mReceiver = null;
224         }
225 
226         boolean removeSuccess = mDownloadManager.remove(mDownloadId) == 1;
227         if (removeSuccess) {
228             ProvisionLogger.logd("Successfully removed installer file.");
229         } else {
230             ProvisionLogger.loge("Could not remove installer file.");
231             // Ignore this error. Failing cleanup should not stop provisioning flow.
232         }
233     }
234 }
235