mobile/android/base/updater/UpdateService.java

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
michael@0 2 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 package org.mozilla.gecko.updater;
michael@0 7
michael@0 8 import org.mozilla.gecko.AppConstants;
michael@0 9 import org.mozilla.gecko.R;
michael@0 10
michael@0 11 import org.mozilla.apache.commons.codec.binary.Hex;
michael@0 12
michael@0 13 import org.w3c.dom.Document;
michael@0 14 import org.w3c.dom.Node;
michael@0 15 import org.w3c.dom.NodeList;
michael@0 16
michael@0 17 import android.app.AlarmManager;
michael@0 18 import android.app.IntentService;
michael@0 19 import android.app.Notification;
michael@0 20 import android.app.NotificationManager;
michael@0 21 import android.app.PendingIntent;
michael@0 22 import android.app.Service;
michael@0 23 import android.content.Context;
michael@0 24 import android.content.Intent;
michael@0 25 import android.content.SharedPreferences;
michael@0 26 import android.net.ConnectivityManager;
michael@0 27 import android.net.NetworkInfo;
michael@0 28 import android.net.Uri;
michael@0 29 import android.os.Environment;
michael@0 30 import android.support.v4.app.NotificationCompat;
michael@0 31 import android.support.v4.app.NotificationCompat.Builder;
michael@0 32 import android.util.Log;
michael@0 33
michael@0 34 import java.io.BufferedInputStream;
michael@0 35 import java.io.BufferedOutputStream;
michael@0 36 import java.io.File;
michael@0 37 import java.io.FileInputStream;
michael@0 38 import java.io.FileOutputStream;
michael@0 39 import java.io.InputStream;
michael@0 40 import java.io.OutputStream;
michael@0 41 import java.net.Proxy;
michael@0 42 import java.net.ProxySelector;
michael@0 43 import java.net.URL;
michael@0 44 import java.net.URLConnection;
michael@0 45 import java.security.MessageDigest;
michael@0 46 import java.util.Calendar;
michael@0 47 import java.util.GregorianCalendar;
michael@0 48 import java.util.List;
michael@0 49 import java.util.TimeZone;
michael@0 50
michael@0 51 import javax.xml.parsers.DocumentBuilder;
michael@0 52 import javax.xml.parsers.DocumentBuilderFactory;
michael@0 53
michael@0 54 public class UpdateService extends IntentService {
michael@0 55 private static final int BUFSIZE = 8192;
michael@0 56 private static final int NOTIFICATION_ID = 0x3e40ddbd;
michael@0 57
michael@0 58 private static final String LOGTAG = "UpdateService";
michael@0 59
michael@0 60 private static final int INTERVAL_LONG = 86400000; // in milliseconds
michael@0 61 private static final int INTERVAL_SHORT = 14400000; // again, in milliseconds
michael@0 62 private static final int INTERVAL_RETRY = 3600000;
michael@0 63
michael@0 64 private static final String PREFS_NAME = "UpdateService";
michael@0 65 private static final String KEY_LAST_BUILDID = "UpdateService.lastBuildID";
michael@0 66 private static final String KEY_LAST_HASH_FUNCTION = "UpdateService.lastHashFunction";
michael@0 67 private static final String KEY_LAST_HASH_VALUE = "UpdateService.lastHashValue";
michael@0 68 private static final String KEY_LAST_FILE_NAME = "UpdateService.lastFileName";
michael@0 69 private static final String KEY_LAST_ATTEMPT_DATE = "UpdateService.lastAttemptDate";
michael@0 70 private static final String KEY_AUTODOWNLOAD_POLICY = "UpdateService.autoDownloadPolicy";
michael@0 71
michael@0 72 private SharedPreferences mPrefs;
michael@0 73
michael@0 74 private NotificationManager mNotificationManager;
michael@0 75 private ConnectivityManager mConnectivityManager;
michael@0 76 private Builder mBuilder;
michael@0 77
michael@0 78 private boolean mDownloading;
michael@0 79 private boolean mCancelDownload;
michael@0 80 private boolean mApplyImmediately;
michael@0 81
michael@0 82 public UpdateService() {
michael@0 83 super("updater");
michael@0 84 }
michael@0 85
michael@0 86 @Override
michael@0 87 public void onCreate () {
michael@0 88 super.onCreate();
michael@0 89
michael@0 90 mPrefs = getSharedPreferences(PREFS_NAME, 0);
michael@0 91 mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
michael@0 92 mConnectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
michael@0 93 mCancelDownload = false;
michael@0 94 }
michael@0 95
michael@0 96 @Override
michael@0 97 public synchronized int onStartCommand (Intent intent, int flags, int startId) {
michael@0 98 // If we are busy doing a download, the new Intent here would normally be queued for
michael@0 99 // execution once that is done. In this case, however, we want to flip the boolean
michael@0 100 // while that is running, so handle that now.
michael@0 101 if (mDownloading && UpdateServiceHelper.ACTION_APPLY_UPDATE.equals(intent.getAction())) {
michael@0 102 Log.i(LOGTAG, "will apply update when download finished");
michael@0 103
michael@0 104 mApplyImmediately = true;
michael@0 105 showDownloadNotification();
michael@0 106 } else if (UpdateServiceHelper.ACTION_CANCEL_DOWNLOAD.equals(intent.getAction())) {
michael@0 107 mCancelDownload = true;
michael@0 108 } else {
michael@0 109 super.onStartCommand(intent, flags, startId);
michael@0 110 }
michael@0 111
michael@0 112 return Service.START_REDELIVER_INTENT;
michael@0 113 }
michael@0 114
michael@0 115 @Override
michael@0 116 protected void onHandleIntent (Intent intent) {
michael@0 117 if (UpdateServiceHelper.ACTION_REGISTER_FOR_UPDATES.equals(intent.getAction())) {
michael@0 118 int policy = intent.getIntExtra(UpdateServiceHelper.EXTRA_AUTODOWNLOAD_NAME, -1);
michael@0 119 if (policy >= 0) {
michael@0 120 setAutoDownloadPolicy(policy);
michael@0 121 }
michael@0 122
michael@0 123 registerForUpdates(false);
michael@0 124 } else if (UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE.equals(intent.getAction())) {
michael@0 125 startUpdate(intent.getIntExtra(UpdateServiceHelper.EXTRA_UPDATE_FLAGS_NAME, 0));
michael@0 126 // Use this instead for forcing a download from about:fennec
michael@0 127 // startUpdate(UpdateServiceHelper.FLAG_FORCE_DOWNLOAD | UpdateServiceHelper.FLAG_REINSTALL);
michael@0 128 } else if (UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE.equals(intent.getAction())) {
michael@0 129 // We always want to do the download and apply it here
michael@0 130 mApplyImmediately = true;
michael@0 131 startUpdate(UpdateServiceHelper.FLAG_FORCE_DOWNLOAD);
michael@0 132 } else if (UpdateServiceHelper.ACTION_APPLY_UPDATE.equals(intent.getAction())) {
michael@0 133 applyUpdate(intent.getStringExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME));
michael@0 134 }
michael@0 135 }
michael@0 136
michael@0 137 private static boolean hasFlag(int flags, int flag) {
michael@0 138 return (flags & flag) == flag;
michael@0 139 }
michael@0 140
michael@0 141 private void sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult result) {
michael@0 142 Intent resultIntent = new Intent(UpdateServiceHelper.ACTION_CHECK_UPDATE_RESULT);
michael@0 143 resultIntent.putExtra("result", result.toString());
michael@0 144 sendBroadcast(resultIntent);
michael@0 145 }
michael@0 146
michael@0 147 private int getUpdateInterval(boolean isRetry) {
michael@0 148 int interval;
michael@0 149 if (isRetry) {
michael@0 150 interval = INTERVAL_RETRY;
michael@0 151 } else if (!AppConstants.RELEASE_BUILD) {
michael@0 152 interval = INTERVAL_SHORT;
michael@0 153 } else {
michael@0 154 interval = INTERVAL_LONG;
michael@0 155 }
michael@0 156
michael@0 157 return interval;
michael@0 158 }
michael@0 159
michael@0 160 private void registerForUpdates(boolean isRetry) {
michael@0 161 Calendar lastAttempt = getLastAttemptDate();
michael@0 162 Calendar now = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
michael@0 163
michael@0 164 int interval = getUpdateInterval(isRetry);
michael@0 165
michael@0 166 if (lastAttempt == null || (now.getTimeInMillis() - lastAttempt.getTimeInMillis()) > interval) {
michael@0 167 // We've either never attempted an update, or we are passed the desired
michael@0 168 // time. Start an update now.
michael@0 169 Log.i(LOGTAG, "no update has ever been attempted, checking now");
michael@0 170 startUpdate(0);
michael@0 171 return;
michael@0 172 }
michael@0 173
michael@0 174 AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
michael@0 175 if (manager == null)
michael@0 176 return;
michael@0 177
michael@0 178 PendingIntent pending = PendingIntent.getService(this, 0, new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE, null, this, UpdateService.class), PendingIntent.FLAG_UPDATE_CURRENT);
michael@0 179 manager.cancel(pending);
michael@0 180
michael@0 181 lastAttempt.setTimeInMillis(lastAttempt.getTimeInMillis() + interval);
michael@0 182 Log.i(LOGTAG, "next update will be at: " + lastAttempt.getTime());
michael@0 183
michael@0 184 manager.set(AlarmManager.RTC_WAKEUP, lastAttempt.getTimeInMillis(), pending);
michael@0 185 }
michael@0 186
michael@0 187 private void startUpdate(int flags) {
michael@0 188 setLastAttemptDate();
michael@0 189
michael@0 190 NetworkInfo netInfo = mConnectivityManager.getActiveNetworkInfo();
michael@0 191 if (netInfo == null || !netInfo.isConnected()) {
michael@0 192 Log.i(LOGTAG, "not connected to the network");
michael@0 193 registerForUpdates(true);
michael@0 194 sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.NOT_AVAILABLE);
michael@0 195 return;
michael@0 196 }
michael@0 197
michael@0 198 registerForUpdates(false);
michael@0 199
michael@0 200 UpdateInfo info = findUpdate(hasFlag(flags, UpdateServiceHelper.FLAG_REINSTALL));
michael@0 201 boolean haveUpdate = (info != null);
michael@0 202
michael@0 203 if (!haveUpdate) {
michael@0 204 Log.i(LOGTAG, "no update available");
michael@0 205 sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.NOT_AVAILABLE);
michael@0 206 return;
michael@0 207 }
michael@0 208
michael@0 209 Log.i(LOGTAG, "update available, buildID = " + info.buildID);
michael@0 210
michael@0 211 int connectionType = netInfo.getType();
michael@0 212 int autoDownloadPolicy = getAutoDownloadPolicy();
michael@0 213
michael@0 214
michael@0 215 /**
michael@0 216 * We only start a download automatically if one of following criteria are met:
michael@0 217 *
michael@0 218 * - We have a FORCE_DOWNLOAD flag passed in
michael@0 219 * - The preference is set to 'always'
michael@0 220 * - The preference is set to 'wifi' and we are actually using wifi (or regular ethernet)
michael@0 221 */
michael@0 222 boolean shouldStartDownload = hasFlag(flags, UpdateServiceHelper.FLAG_FORCE_DOWNLOAD) ||
michael@0 223 autoDownloadPolicy == UpdateServiceHelper.AUTODOWNLOAD_ENABLED ||
michael@0 224 (autoDownloadPolicy == UpdateServiceHelper.AUTODOWNLOAD_WIFI &&
michael@0 225 (connectionType == ConnectivityManager.TYPE_WIFI || connectionType == ConnectivityManager.TYPE_ETHERNET));
michael@0 226
michael@0 227 if (!shouldStartDownload) {
michael@0 228 Log.i(LOGTAG, "not initiating automatic update download due to policy " + autoDownloadPolicy);
michael@0 229 sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.AVAILABLE);
michael@0 230
michael@0 231 // We aren't autodownloading here, so prompt to start the update
michael@0 232 Notification notification = new Notification(R.drawable.ic_status_logo, null, System.currentTimeMillis());
michael@0 233
michael@0 234 Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE);
michael@0 235 notificationIntent.setClass(this, UpdateService.class);
michael@0 236
michael@0 237 PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
michael@0 238 notification.flags = Notification.FLAG_AUTO_CANCEL;
michael@0 239
michael@0 240 notification.setLatestEventInfo(this, getResources().getString(R.string.updater_start_title),
michael@0 241 getResources().getString(R.string.updater_start_select),
michael@0 242 contentIntent);
michael@0 243
michael@0 244 mNotificationManager.notify(NOTIFICATION_ID, notification);
michael@0 245
michael@0 246 return;
michael@0 247 }
michael@0 248
michael@0 249 File pkg = downloadUpdatePackage(info, hasFlag(flags, UpdateServiceHelper.FLAG_OVERWRITE_EXISTING));
michael@0 250 if (pkg == null) {
michael@0 251 sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.NOT_AVAILABLE);
michael@0 252 return;
michael@0 253 }
michael@0 254
michael@0 255 Log.i(LOGTAG, "have update package at " + pkg);
michael@0 256
michael@0 257 saveUpdateInfo(info, pkg);
michael@0 258 sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.DOWNLOADED);
michael@0 259
michael@0 260 if (mApplyImmediately) {
michael@0 261 applyUpdate(pkg);
michael@0 262 } else {
michael@0 263 // Prompt to apply the update
michael@0 264 Notification notification = new Notification(R.drawable.ic_status_logo, null, System.currentTimeMillis());
michael@0 265
michael@0 266 Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE);
michael@0 267 notificationIntent.setClass(this, UpdateService.class);
michael@0 268 notificationIntent.putExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME, pkg.getAbsolutePath());
michael@0 269
michael@0 270 PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
michael@0 271 notification.flags = Notification.FLAG_AUTO_CANCEL;
michael@0 272
michael@0 273 notification.setLatestEventInfo(this, getResources().getString(R.string.updater_apply_title),
michael@0 274 getResources().getString(R.string.updater_apply_select),
michael@0 275 contentIntent);
michael@0 276
michael@0 277 mNotificationManager.notify(NOTIFICATION_ID, notification);
michael@0 278 }
michael@0 279 }
michael@0 280
michael@0 281 private URLConnection openConnectionWithProxy(URL url) throws java.net.URISyntaxException, java.io.IOException {
michael@0 282 Log.i(LOGTAG, "opening connection with url: " + url);
michael@0 283
michael@0 284 ProxySelector ps = ProxySelector.getDefault();
michael@0 285 Proxy proxy = Proxy.NO_PROXY;
michael@0 286 if (ps != null) {
michael@0 287 List<Proxy> proxies = ps.select(url.toURI());
michael@0 288 if (proxies != null && !proxies.isEmpty()) {
michael@0 289 proxy = proxies.get(0);
michael@0 290 }
michael@0 291 }
michael@0 292
michael@0 293 return url.openConnection(proxy);
michael@0 294 }
michael@0 295
michael@0 296 private UpdateInfo findUpdate(boolean force) {
michael@0 297 try {
michael@0 298 URL url = UpdateServiceHelper.getUpdateUrl(this, force);
michael@0 299
michael@0 300 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
michael@0 301 Document dom = builder.parse(openConnectionWithProxy(url).getInputStream());
michael@0 302
michael@0 303 NodeList nodes = dom.getElementsByTagName("update");
michael@0 304 if (nodes == null || nodes.getLength() == 0)
michael@0 305 return null;
michael@0 306
michael@0 307 Node updateNode = nodes.item(0);
michael@0 308 Node buildIdNode = updateNode.getAttributes().getNamedItem("buildID");
michael@0 309 if (buildIdNode == null)
michael@0 310 return null;
michael@0 311
michael@0 312 nodes = dom.getElementsByTagName("patch");
michael@0 313 if (nodes == null || nodes.getLength() == 0)
michael@0 314 return null;
michael@0 315
michael@0 316 Node patchNode = nodes.item(0);
michael@0 317 Node urlNode = patchNode.getAttributes().getNamedItem("URL");
michael@0 318 Node hashFunctionNode = patchNode.getAttributes().getNamedItem("hashFunction");
michael@0 319 Node hashValueNode = patchNode.getAttributes().getNamedItem("hashValue");
michael@0 320 Node sizeNode = patchNode.getAttributes().getNamedItem("size");
michael@0 321
michael@0 322 if (urlNode == null || hashFunctionNode == null ||
michael@0 323 hashValueNode == null || sizeNode == null) {
michael@0 324 return null;
michael@0 325 }
michael@0 326
michael@0 327 // Fill in UpdateInfo from the XML data
michael@0 328 UpdateInfo info = new UpdateInfo();
michael@0 329 info.url = new URL(urlNode.getTextContent());
michael@0 330 info.buildID = buildIdNode.getTextContent();
michael@0 331 info.hashFunction = hashFunctionNode.getTextContent();
michael@0 332 info.hashValue = hashValueNode.getTextContent();
michael@0 333
michael@0 334 try {
michael@0 335 info.size = Integer.parseInt(sizeNode.getTextContent());
michael@0 336 } catch (NumberFormatException e) {
michael@0 337 Log.e(LOGTAG, "Failed to find APK size: ", e);
michael@0 338 return null;
michael@0 339 }
michael@0 340
michael@0 341 // Make sure we have all the stuff we need to apply the update
michael@0 342 if (!info.isValid()) {
michael@0 343 Log.e(LOGTAG, "missing some required update information, have: " + info);
michael@0 344 return null;
michael@0 345 }
michael@0 346
michael@0 347 return info;
michael@0 348 } catch (Exception e) {
michael@0 349 Log.e(LOGTAG, "failed to check for update: ", e);
michael@0 350 return null;
michael@0 351 }
michael@0 352 }
michael@0 353
michael@0 354 private MessageDigest createMessageDigest(String hashFunction) {
michael@0 355 String javaHashFunction = null;
michael@0 356
michael@0 357 if ("sha512".equalsIgnoreCase(hashFunction)) {
michael@0 358 javaHashFunction = "SHA-512";
michael@0 359 } else {
michael@0 360 Log.e(LOGTAG, "Unhandled hash function: " + hashFunction);
michael@0 361 return null;
michael@0 362 }
michael@0 363
michael@0 364 try {
michael@0 365 return MessageDigest.getInstance(javaHashFunction);
michael@0 366 } catch (java.security.NoSuchAlgorithmException e) {
michael@0 367 Log.e(LOGTAG, "Couldn't find algorithm " + javaHashFunction, e);
michael@0 368 return null;
michael@0 369 }
michael@0 370 }
michael@0 371
michael@0 372 private void showDownloadNotification() {
michael@0 373 showDownloadNotification(null);
michael@0 374 }
michael@0 375
michael@0 376 private void showDownloadNotification(File downloadFile) {
michael@0 377
michael@0 378 Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE);
michael@0 379 notificationIntent.setClass(this, UpdateService.class);
michael@0 380
michael@0 381 Intent cancelIntent = new Intent(UpdateServiceHelper.ACTION_CANCEL_DOWNLOAD);
michael@0 382 cancelIntent.setClass(this, UpdateService.class);
michael@0 383
michael@0 384 if (downloadFile != null)
michael@0 385 notificationIntent.putExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME, downloadFile.getAbsolutePath());
michael@0 386
michael@0 387 PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
michael@0 388 PendingIntent deleteIntent = PendingIntent.getService(this, 0, cancelIntent, PendingIntent.FLAG_CANCEL_CURRENT);
michael@0 389
michael@0 390 mBuilder = new NotificationCompat.Builder(this);
michael@0 391 mBuilder.setContentTitle(getResources().getString(R.string.updater_downloading_title))
michael@0 392 .setContentText(mApplyImmediately ? "" : getResources().getString(R.string.updater_downloading_select))
michael@0 393 .setSmallIcon(android.R.drawable.stat_sys_download)
michael@0 394 .setContentIntent(contentIntent)
michael@0 395 .setDeleteIntent(deleteIntent);
michael@0 396
michael@0 397 mBuilder.setProgress(100, 0, true);
michael@0 398 mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
michael@0 399 }
michael@0 400
michael@0 401 private void showDownloadFailure() {
michael@0 402 Notification notification = new Notification(R.drawable.ic_status_logo, null, System.currentTimeMillis());
michael@0 403
michael@0 404 Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE);
michael@0 405 notificationIntent.setClass(this, UpdateService.class);
michael@0 406
michael@0 407 PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
michael@0 408
michael@0 409 notification.setLatestEventInfo(this, getResources().getString(R.string.updater_downloading_title_failed),
michael@0 410 getResources().getString(R.string.updater_downloading_retry),
michael@0 411 contentIntent);
michael@0 412
michael@0 413 mNotificationManager.notify(NOTIFICATION_ID, notification);
michael@0 414 }
michael@0 415
michael@0 416 private File downloadUpdatePackage(UpdateInfo info, boolean overwriteExisting) {
michael@0 417 File downloadFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), new File(info.url.getFile()).getName());
michael@0 418
michael@0 419 if (!overwriteExisting && info.buildID.equals(getLastBuildID()) && downloadFile.exists()) {
michael@0 420 // The last saved buildID is the same as the one for the current update. We also have a file
michael@0 421 // already downloaded, so it's probably the package we want. Verify it to be sure and just
michael@0 422 // return that if it matches.
michael@0 423
michael@0 424 if (verifyDownloadedPackage(downloadFile)) {
michael@0 425 Log.i(LOGTAG, "using existing update package");
michael@0 426 return downloadFile;
michael@0 427 } else {
michael@0 428 // Didn't match, so we're going to download a new one.
michael@0 429 downloadFile.delete();
michael@0 430 }
michael@0 431 }
michael@0 432
michael@0 433 Log.i(LOGTAG, "downloading update package");
michael@0 434 sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.DOWNLOADING);
michael@0 435
michael@0 436 OutputStream output = null;
michael@0 437 InputStream input = null;
michael@0 438
michael@0 439 mDownloading = true;
michael@0 440 mCancelDownload = false;
michael@0 441 showDownloadNotification(downloadFile);
michael@0 442
michael@0 443 try {
michael@0 444 URLConnection conn = openConnectionWithProxy(info.url);
michael@0 445 int length = conn.getContentLength();
michael@0 446
michael@0 447 output = new BufferedOutputStream(new FileOutputStream(downloadFile));
michael@0 448 input = new BufferedInputStream(conn.getInputStream());
michael@0 449
michael@0 450 byte[] buf = new byte[BUFSIZE];
michael@0 451 int len = 0;
michael@0 452
michael@0 453 int bytesRead = 0;
michael@0 454 int lastNotify = 0;
michael@0 455
michael@0 456 while ((len = input.read(buf, 0, BUFSIZE)) > 0 && !mCancelDownload) {
michael@0 457 output.write(buf, 0, len);
michael@0 458 bytesRead += len;
michael@0 459 // Updating the notification takes time so only do it every 1MB
michael@0 460 if(bytesRead - lastNotify > 1048576) {
michael@0 461 mBuilder.setProgress(length, bytesRead, false);
michael@0 462 mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
michael@0 463 lastNotify = bytesRead;
michael@0 464 }
michael@0 465 }
michael@0 466
michael@0 467 mNotificationManager.cancel(NOTIFICATION_ID);
michael@0 468
michael@0 469 // if the download was canceled by the user
michael@0 470 // delete the update package
michael@0 471 if (mCancelDownload) {
michael@0 472 Log.i(LOGTAG, "download canceled by user!");
michael@0 473 downloadFile.delete();
michael@0 474
michael@0 475 return null;
michael@0 476 } else {
michael@0 477 Log.i(LOGTAG, "completed update download!");
michael@0 478 return downloadFile;
michael@0 479 }
michael@0 480 } catch (Exception e) {
michael@0 481 downloadFile.delete();
michael@0 482 showDownloadFailure();
michael@0 483
michael@0 484 Log.e(LOGTAG, "failed to download update: ", e);
michael@0 485 return null;
michael@0 486 } finally {
michael@0 487 try {
michael@0 488 if (input != null)
michael@0 489 input.close();
michael@0 490 } catch (java.io.IOException e) {}
michael@0 491
michael@0 492 try {
michael@0 493 if (output != null)
michael@0 494 output.close();
michael@0 495 } catch (java.io.IOException e) {}
michael@0 496
michael@0 497 mDownloading = false;
michael@0 498 }
michael@0 499 }
michael@0 500
michael@0 501 private boolean verifyDownloadedPackage(File updateFile) {
michael@0 502 MessageDigest digest = createMessageDigest(getLastHashFunction());
michael@0 503 if (digest == null)
michael@0 504 return false;
michael@0 505
michael@0 506 InputStream input = null;
michael@0 507
michael@0 508 try {
michael@0 509 input = new BufferedInputStream(new FileInputStream(updateFile));
michael@0 510
michael@0 511 byte[] buf = new byte[BUFSIZE];
michael@0 512 int len;
michael@0 513 while ((len = input.read(buf, 0, BUFSIZE)) > 0) {
michael@0 514 digest.update(buf, 0, len);
michael@0 515 }
michael@0 516 } catch (java.io.IOException e) {
michael@0 517 Log.e(LOGTAG, "Failed to verify update package: ", e);
michael@0 518 return false;
michael@0 519 } finally {
michael@0 520 try {
michael@0 521 if (input != null)
michael@0 522 input.close();
michael@0 523 } catch(java.io.IOException e) {}
michael@0 524 }
michael@0 525
michael@0 526 String hex = Hex.encodeHexString(digest.digest());
michael@0 527 if (!hex.equals(getLastHashValue())) {
michael@0 528 Log.e(LOGTAG, "Package hash does not match");
michael@0 529 return false;
michael@0 530 }
michael@0 531
michael@0 532 return true;
michael@0 533 }
michael@0 534
michael@0 535 private void applyUpdate(String updatePath) {
michael@0 536 if (updatePath == null) {
michael@0 537 updatePath = mPrefs.getString(KEY_LAST_FILE_NAME, null);
michael@0 538 }
michael@0 539 applyUpdate(new File(updatePath));
michael@0 540 }
michael@0 541
michael@0 542 private void applyUpdate(File updateFile) {
michael@0 543 mApplyImmediately = false;
michael@0 544
michael@0 545 if (!updateFile.exists())
michael@0 546 return;
michael@0 547
michael@0 548 Log.i(LOGTAG, "Verifying package: " + updateFile);
michael@0 549
michael@0 550 if (!verifyDownloadedPackage(updateFile)) {
michael@0 551 Log.e(LOGTAG, "Not installing update, failed verification");
michael@0 552 return;
michael@0 553 }
michael@0 554
michael@0 555 Intent intent = new Intent(Intent.ACTION_VIEW);
michael@0 556 intent.setDataAndType(Uri.fromFile(updateFile), "application/vnd.android.package-archive");
michael@0 557 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
michael@0 558 startActivity(intent);
michael@0 559 }
michael@0 560
michael@0 561 private String getLastBuildID() {
michael@0 562 return mPrefs.getString(KEY_LAST_BUILDID, null);
michael@0 563 }
michael@0 564
michael@0 565 private String getLastHashFunction() {
michael@0 566 return mPrefs.getString(KEY_LAST_HASH_FUNCTION, null);
michael@0 567 }
michael@0 568
michael@0 569 private String getLastHashValue() {
michael@0 570 return mPrefs.getString(KEY_LAST_HASH_VALUE, null);
michael@0 571 }
michael@0 572
michael@0 573 private Calendar getLastAttemptDate() {
michael@0 574 long lastAttempt = mPrefs.getLong(KEY_LAST_ATTEMPT_DATE, -1);
michael@0 575 if (lastAttempt < 0)
michael@0 576 return null;
michael@0 577
michael@0 578 GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
michael@0 579 cal.setTimeInMillis(lastAttempt);
michael@0 580 return cal;
michael@0 581 }
michael@0 582
michael@0 583 private void setLastAttemptDate() {
michael@0 584 SharedPreferences.Editor editor = mPrefs.edit();
michael@0 585 editor.putLong(KEY_LAST_ATTEMPT_DATE, System.currentTimeMillis());
michael@0 586 editor.commit();
michael@0 587 }
michael@0 588
michael@0 589 private int getAutoDownloadPolicy() {
michael@0 590 return mPrefs.getInt(KEY_AUTODOWNLOAD_POLICY, UpdateServiceHelper.AUTODOWNLOAD_WIFI);
michael@0 591 }
michael@0 592
michael@0 593 private void setAutoDownloadPolicy(int policy) {
michael@0 594 SharedPreferences.Editor editor = mPrefs.edit();
michael@0 595 editor.putInt(KEY_AUTODOWNLOAD_POLICY, policy);
michael@0 596 editor.commit();
michael@0 597 }
michael@0 598
michael@0 599 private void saveUpdateInfo(UpdateInfo info, File downloaded) {
michael@0 600 SharedPreferences.Editor editor = mPrefs.edit();
michael@0 601 editor.putString(KEY_LAST_BUILDID, info.buildID);
michael@0 602 editor.putString(KEY_LAST_HASH_FUNCTION, info.hashFunction);
michael@0 603 editor.putString(KEY_LAST_HASH_VALUE, info.hashValue);
michael@0 604 editor.putString(KEY_LAST_FILE_NAME, downloaded.toString());
michael@0 605 editor.commit();
michael@0 606 }
michael@0 607
michael@0 608 private class UpdateInfo {
michael@0 609 public URL url;
michael@0 610 public String buildID;
michael@0 611 public String hashFunction;
michael@0 612 public String hashValue;
michael@0 613 public int size;
michael@0 614
michael@0 615 private boolean isNonEmpty(String s) {
michael@0 616 return s != null && s.length() > 0;
michael@0 617 }
michael@0 618
michael@0 619 public boolean isValid() {
michael@0 620 return url != null && isNonEmpty(buildID) &&
michael@0 621 isNonEmpty(hashFunction) && isNonEmpty(hashValue) && size > 0;
michael@0 622 }
michael@0 623
michael@0 624 @Override
michael@0 625 public String toString() {
michael@0 626 return "url = " + url + ", buildID = " + buildID + ", hashFunction = " + hashFunction + ", hashValue = " + hashValue + ", size = " + size;
michael@0 627 }
michael@0 628 }
michael@0 629 }

mercurial