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