• 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 android.app.DownloadManager;
19 import android.app.DownloadManager.Query;
20 import android.app.DownloadManager.Request;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.pm.ActivityInfo;
26 import android.content.pm.PackageInfo;
27 import android.content.pm.PackageManager;
28 import android.database.Cursor;
29 import android.net.Uri;
30 import android.text.TextUtils;
31 import android.util.Base64;
32 
33 import com.android.managedprovisioning.ProvisionLogger;
34 
35 import java.io.InputStream;
36 import java.io.IOException;
37 import java.io.FileInputStream;
38 import java.security.MessageDigest;
39 import java.security.NoSuchAlgorithmException;
40 import java.util.Arrays;
41 
42 /**
43  * Downloads a given file and checks whether its hash matches a given hash to verify that the
44  * intended file was downloaded.
45  */
46 public class DownloadPackageTask {
47     public static final int ERROR_HASH_MISMATCH = 0;
48     public static final int ERROR_DOWNLOAD_FAILED = 1;
49     public static final int ERROR_OTHER = 2;
50 
51     private static final String HASH_TYPE = "SHA-1";
52 
53     private final Context mContext;
54     private final String mDownloadLocationFrom;
55     private final Callback mCallback;
56     private final byte[] mHash;
57     private final String mHttpCookieHeader;
58 
59     private boolean mDoneDownloading;
60     private String mDownloadLocationTo;
61     private long mDownloadId;
62     private BroadcastReceiver mReceiver;
63 
DownloadPackageTask(Context context, String downloadLocation, byte[] hash, String httpCookieHeader, Callback callback)64     public DownloadPackageTask (Context context, String downloadLocation, byte[] hash,
65             String httpCookieHeader, Callback callback) {
66         mCallback = callback;
67         mContext = context;
68         mDownloadLocationFrom = downloadLocation;
69         mHash = hash;
70         mHttpCookieHeader = httpCookieHeader;
71         mDoneDownloading = false;
72     }
73 
downloadLocationWasProvided()74     public boolean downloadLocationWasProvided() {
75         return !TextUtils.isEmpty(mDownloadLocationFrom);
76     }
77 
run()78     public void run() {
79         mReceiver = createDownloadReceiver();
80         mContext.registerReceiver(mReceiver,
81                 new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
82 
83         ProvisionLogger.logd("Starting download from " + mDownloadLocationFrom);
84         DownloadManager dm = (DownloadManager) mContext
85                 .getSystemService(Context.DOWNLOAD_SERVICE);
86         Request request = new Request(Uri.parse(mDownloadLocationFrom));
87         if (mHttpCookieHeader != null) {
88             request.addRequestHeader("Cookie", mHttpCookieHeader);
89             ProvisionLogger.logd("Downloading with http cookie header: " + mHttpCookieHeader);
90         }
91         mDownloadId = dm.enqueue(request);
92     }
93 
createDownloadReceiver()94     private BroadcastReceiver createDownloadReceiver() {
95         return new BroadcastReceiver() {
96             @Override
97             public void onReceive(Context context, Intent intent) {
98                 if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
99                     Query q = new Query();
100                     q.setFilterById(mDownloadId);
101                     DownloadManager dm = (DownloadManager) mContext
102                             .getSystemService(Context.DOWNLOAD_SERVICE);
103                     Cursor c = dm.query(q);
104                     if (c.moveToFirst()) {
105                         int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
106                         if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
107                             String location = c.getString(
108                                     c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
109                             c.close();
110                             onDownloadSuccess(location);
111                         } else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)){
112                             int reason = c.getColumnIndex(DownloadManager.COLUMN_REASON);
113                             c.close();
114                             onDownloadFail(reason);
115                         }
116                     }
117                 }
118             }
119         };
120     }
121 
122     private void onDownloadSuccess(String location) {
123         if (mDoneDownloading) {
124             // DownloadManager can send success more than once. Only act first time.
125             return;
126         } else {
127             mDoneDownloading = true;
128         }
129 
130         ProvisionLogger.logd("Downloaded succesfully to: " + location);
131 
132         // Check whether hash of downloaded file matches hash given in constructor.
133         byte[] hash = computeHash(location);
134         if (hash == null) {
135 
136             // Error should have been reported in computeHash().
137             return;
138         }
139 
140         if (Arrays.equals(mHash, hash)) {
141             ProvisionLogger.logd(HASH_TYPE + "-hashes matched, both are "
142                     + byteArrayToString(hash));
143             mDownloadLocationTo = location;
144             mCallback.onSuccess();
145         } else {
146             ProvisionLogger.loge(HASH_TYPE + "-hash of downloaded file does not match given hash.");
147             ProvisionLogger.loge(HASH_TYPE + "-hash of downloaded file: "
148                     + byteArrayToString(hash));
149             ProvisionLogger.loge(HASH_TYPE + "-hash provided by programmer: "
150                     + byteArrayToString(mHash));
151 
152             mCallback.onError(ERROR_HASH_MISMATCH);
153         }
154     }
155 
156     private void onDownloadFail(int errorCode) {
157         ProvisionLogger.loge("Downloading package failed.");
158         ProvisionLogger.loge("COLUMN_REASON in DownloadManager response has value: "
159                 + errorCode);
160         mCallback.onError(ERROR_DOWNLOAD_FAILED);
161     }
162 
163     private byte[] computeHash(String fileLocation) {
164         InputStream fis = null;
165         MessageDigest md;
166         byte hash[] = null;
167         try {
168             md = MessageDigest.getInstance(HASH_TYPE);
169         } catch (NoSuchAlgorithmException e) {
170             ProvisionLogger.loge("Hashing algorithm " + HASH_TYPE + " not supported.", e);
171             mCallback.onError(ERROR_OTHER);
172             return null;
173         }
174         try {
175             fis = new FileInputStream(fileLocation);
176 
177             byte[] buffer = new byte[256];
178             int n = 0;
179             while (n != -1) {
180                 n = fis.read(buffer);
181                 if (n > 0) {
182                     md.update(buffer, 0, n);
183                 }
184             }
185             hash = md.digest();
186         } catch (IOException e) {
187             ProvisionLogger.loge("IO error.", e);
188             mCallback.onError(ERROR_OTHER);
189         } finally {
190             // Close input stream quietly.
191             try {
192                 if (fis != null) {
193                     fis.close();
194                 }
195             } catch (IOException e) {
196                 // Ignore.
197             }
198         }
199         return hash;
200     }
201 
202     public String getDownloadedPackageLocation() {
203         return mDownloadLocationTo;
204     }
205 
206     public void cleanUp() {
207         if (mReceiver != null) {
208             //Unregister receiver.
209             mContext.unregisterReceiver(mReceiver);
210             mReceiver = null;
211         }
212 
213         //Remove download.
214         DownloadManager dm = (DownloadManager) mContext
215                 .getSystemService(Context.DOWNLOAD_SERVICE);
216         boolean removeSuccess = dm.remove(mDownloadId) == 1;
217         if (removeSuccess) {
218             ProvisionLogger.logd("Successfully removed the device owner installer file.");
219         } else {
220             ProvisionLogger.loge("Could not remove the device owner installer file.");
221             // Ignore this error. Failing cleanup should not stop provisioning flow.
222         }
223     }
224 
225     // For logging purposes only.
226     String byteArrayToString(byte[] ba) {
227         return Base64.encodeToString(ba, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
228     }
229 
230     public abstract static class Callback {
231         public abstract void onSuccess();
232         public abstract void onError(int errorCode);
233     }
234 }
235