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