1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/updater/UpdateService.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,629 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko.updater; 1.10 + 1.11 +import org.mozilla.gecko.AppConstants; 1.12 +import org.mozilla.gecko.R; 1.13 + 1.14 +import org.mozilla.apache.commons.codec.binary.Hex; 1.15 + 1.16 +import org.w3c.dom.Document; 1.17 +import org.w3c.dom.Node; 1.18 +import org.w3c.dom.NodeList; 1.19 + 1.20 +import android.app.AlarmManager; 1.21 +import android.app.IntentService; 1.22 +import android.app.Notification; 1.23 +import android.app.NotificationManager; 1.24 +import android.app.PendingIntent; 1.25 +import android.app.Service; 1.26 +import android.content.Context; 1.27 +import android.content.Intent; 1.28 +import android.content.SharedPreferences; 1.29 +import android.net.ConnectivityManager; 1.30 +import android.net.NetworkInfo; 1.31 +import android.net.Uri; 1.32 +import android.os.Environment; 1.33 +import android.support.v4.app.NotificationCompat; 1.34 +import android.support.v4.app.NotificationCompat.Builder; 1.35 +import android.util.Log; 1.36 + 1.37 +import java.io.BufferedInputStream; 1.38 +import java.io.BufferedOutputStream; 1.39 +import java.io.File; 1.40 +import java.io.FileInputStream; 1.41 +import java.io.FileOutputStream; 1.42 +import java.io.InputStream; 1.43 +import java.io.OutputStream; 1.44 +import java.net.Proxy; 1.45 +import java.net.ProxySelector; 1.46 +import java.net.URL; 1.47 +import java.net.URLConnection; 1.48 +import java.security.MessageDigest; 1.49 +import java.util.Calendar; 1.50 +import java.util.GregorianCalendar; 1.51 +import java.util.List; 1.52 +import java.util.TimeZone; 1.53 + 1.54 +import javax.xml.parsers.DocumentBuilder; 1.55 +import javax.xml.parsers.DocumentBuilderFactory; 1.56 + 1.57 +public class UpdateService extends IntentService { 1.58 + private static final int BUFSIZE = 8192; 1.59 + private static final int NOTIFICATION_ID = 0x3e40ddbd; 1.60 + 1.61 + private static final String LOGTAG = "UpdateService"; 1.62 + 1.63 + private static final int INTERVAL_LONG = 86400000; // in milliseconds 1.64 + private static final int INTERVAL_SHORT = 14400000; // again, in milliseconds 1.65 + private static final int INTERVAL_RETRY = 3600000; 1.66 + 1.67 + private static final String PREFS_NAME = "UpdateService"; 1.68 + private static final String KEY_LAST_BUILDID = "UpdateService.lastBuildID"; 1.69 + private static final String KEY_LAST_HASH_FUNCTION = "UpdateService.lastHashFunction"; 1.70 + private static final String KEY_LAST_HASH_VALUE = "UpdateService.lastHashValue"; 1.71 + private static final String KEY_LAST_FILE_NAME = "UpdateService.lastFileName"; 1.72 + private static final String KEY_LAST_ATTEMPT_DATE = "UpdateService.lastAttemptDate"; 1.73 + private static final String KEY_AUTODOWNLOAD_POLICY = "UpdateService.autoDownloadPolicy"; 1.74 + 1.75 + private SharedPreferences mPrefs; 1.76 + 1.77 + private NotificationManager mNotificationManager; 1.78 + private ConnectivityManager mConnectivityManager; 1.79 + private Builder mBuilder; 1.80 + 1.81 + private boolean mDownloading; 1.82 + private boolean mCancelDownload; 1.83 + private boolean mApplyImmediately; 1.84 + 1.85 + public UpdateService() { 1.86 + super("updater"); 1.87 + } 1.88 + 1.89 + @Override 1.90 + public void onCreate () { 1.91 + super.onCreate(); 1.92 + 1.93 + mPrefs = getSharedPreferences(PREFS_NAME, 0); 1.94 + mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); 1.95 + mConnectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); 1.96 + mCancelDownload = false; 1.97 + } 1.98 + 1.99 + @Override 1.100 + public synchronized int onStartCommand (Intent intent, int flags, int startId) { 1.101 + // If we are busy doing a download, the new Intent here would normally be queued for 1.102 + // execution once that is done. In this case, however, we want to flip the boolean 1.103 + // while that is running, so handle that now. 1.104 + if (mDownloading && UpdateServiceHelper.ACTION_APPLY_UPDATE.equals(intent.getAction())) { 1.105 + Log.i(LOGTAG, "will apply update when download finished"); 1.106 + 1.107 + mApplyImmediately = true; 1.108 + showDownloadNotification(); 1.109 + } else if (UpdateServiceHelper.ACTION_CANCEL_DOWNLOAD.equals(intent.getAction())) { 1.110 + mCancelDownload = true; 1.111 + } else { 1.112 + super.onStartCommand(intent, flags, startId); 1.113 + } 1.114 + 1.115 + return Service.START_REDELIVER_INTENT; 1.116 + } 1.117 + 1.118 + @Override 1.119 + protected void onHandleIntent (Intent intent) { 1.120 + if (UpdateServiceHelper.ACTION_REGISTER_FOR_UPDATES.equals(intent.getAction())) { 1.121 + int policy = intent.getIntExtra(UpdateServiceHelper.EXTRA_AUTODOWNLOAD_NAME, -1); 1.122 + if (policy >= 0) { 1.123 + setAutoDownloadPolicy(policy); 1.124 + } 1.125 + 1.126 + registerForUpdates(false); 1.127 + } else if (UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE.equals(intent.getAction())) { 1.128 + startUpdate(intent.getIntExtra(UpdateServiceHelper.EXTRA_UPDATE_FLAGS_NAME, 0)); 1.129 + // Use this instead for forcing a download from about:fennec 1.130 + // startUpdate(UpdateServiceHelper.FLAG_FORCE_DOWNLOAD | UpdateServiceHelper.FLAG_REINSTALL); 1.131 + } else if (UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE.equals(intent.getAction())) { 1.132 + // We always want to do the download and apply it here 1.133 + mApplyImmediately = true; 1.134 + startUpdate(UpdateServiceHelper.FLAG_FORCE_DOWNLOAD); 1.135 + } else if (UpdateServiceHelper.ACTION_APPLY_UPDATE.equals(intent.getAction())) { 1.136 + applyUpdate(intent.getStringExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME)); 1.137 + } 1.138 + } 1.139 + 1.140 + private static boolean hasFlag(int flags, int flag) { 1.141 + return (flags & flag) == flag; 1.142 + } 1.143 + 1.144 + private void sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult result) { 1.145 + Intent resultIntent = new Intent(UpdateServiceHelper.ACTION_CHECK_UPDATE_RESULT); 1.146 + resultIntent.putExtra("result", result.toString()); 1.147 + sendBroadcast(resultIntent); 1.148 + } 1.149 + 1.150 + private int getUpdateInterval(boolean isRetry) { 1.151 + int interval; 1.152 + if (isRetry) { 1.153 + interval = INTERVAL_RETRY; 1.154 + } else if (!AppConstants.RELEASE_BUILD) { 1.155 + interval = INTERVAL_SHORT; 1.156 + } else { 1.157 + interval = INTERVAL_LONG; 1.158 + } 1.159 + 1.160 + return interval; 1.161 + } 1.162 + 1.163 + private void registerForUpdates(boolean isRetry) { 1.164 + Calendar lastAttempt = getLastAttemptDate(); 1.165 + Calendar now = new GregorianCalendar(TimeZone.getTimeZone("GMT")); 1.166 + 1.167 + int interval = getUpdateInterval(isRetry); 1.168 + 1.169 + if (lastAttempt == null || (now.getTimeInMillis() - lastAttempt.getTimeInMillis()) > interval) { 1.170 + // We've either never attempted an update, or we are passed the desired 1.171 + // time. Start an update now. 1.172 + Log.i(LOGTAG, "no update has ever been attempted, checking now"); 1.173 + startUpdate(0); 1.174 + return; 1.175 + } 1.176 + 1.177 + AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 1.178 + if (manager == null) 1.179 + return; 1.180 + 1.181 + PendingIntent pending = PendingIntent.getService(this, 0, new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE, null, this, UpdateService.class), PendingIntent.FLAG_UPDATE_CURRENT); 1.182 + manager.cancel(pending); 1.183 + 1.184 + lastAttempt.setTimeInMillis(lastAttempt.getTimeInMillis() + interval); 1.185 + Log.i(LOGTAG, "next update will be at: " + lastAttempt.getTime()); 1.186 + 1.187 + manager.set(AlarmManager.RTC_WAKEUP, lastAttempt.getTimeInMillis(), pending); 1.188 + } 1.189 + 1.190 + private void startUpdate(int flags) { 1.191 + setLastAttemptDate(); 1.192 + 1.193 + NetworkInfo netInfo = mConnectivityManager.getActiveNetworkInfo(); 1.194 + if (netInfo == null || !netInfo.isConnected()) { 1.195 + Log.i(LOGTAG, "not connected to the network"); 1.196 + registerForUpdates(true); 1.197 + sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.NOT_AVAILABLE); 1.198 + return; 1.199 + } 1.200 + 1.201 + registerForUpdates(false); 1.202 + 1.203 + UpdateInfo info = findUpdate(hasFlag(flags, UpdateServiceHelper.FLAG_REINSTALL)); 1.204 + boolean haveUpdate = (info != null); 1.205 + 1.206 + if (!haveUpdate) { 1.207 + Log.i(LOGTAG, "no update available"); 1.208 + sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.NOT_AVAILABLE); 1.209 + return; 1.210 + } 1.211 + 1.212 + Log.i(LOGTAG, "update available, buildID = " + info.buildID); 1.213 + 1.214 + int connectionType = netInfo.getType(); 1.215 + int autoDownloadPolicy = getAutoDownloadPolicy(); 1.216 + 1.217 + 1.218 + /** 1.219 + * We only start a download automatically if one of following criteria are met: 1.220 + * 1.221 + * - We have a FORCE_DOWNLOAD flag passed in 1.222 + * - The preference is set to 'always' 1.223 + * - The preference is set to 'wifi' and we are actually using wifi (or regular ethernet) 1.224 + */ 1.225 + boolean shouldStartDownload = hasFlag(flags, UpdateServiceHelper.FLAG_FORCE_DOWNLOAD) || 1.226 + autoDownloadPolicy == UpdateServiceHelper.AUTODOWNLOAD_ENABLED || 1.227 + (autoDownloadPolicy == UpdateServiceHelper.AUTODOWNLOAD_WIFI && 1.228 + (connectionType == ConnectivityManager.TYPE_WIFI || connectionType == ConnectivityManager.TYPE_ETHERNET)); 1.229 + 1.230 + if (!shouldStartDownload) { 1.231 + Log.i(LOGTAG, "not initiating automatic update download due to policy " + autoDownloadPolicy); 1.232 + sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.AVAILABLE); 1.233 + 1.234 + // We aren't autodownloading here, so prompt to start the update 1.235 + Notification notification = new Notification(R.drawable.ic_status_logo, null, System.currentTimeMillis()); 1.236 + 1.237 + Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE); 1.238 + notificationIntent.setClass(this, UpdateService.class); 1.239 + 1.240 + PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); 1.241 + notification.flags = Notification.FLAG_AUTO_CANCEL; 1.242 + 1.243 + notification.setLatestEventInfo(this, getResources().getString(R.string.updater_start_title), 1.244 + getResources().getString(R.string.updater_start_select), 1.245 + contentIntent); 1.246 + 1.247 + mNotificationManager.notify(NOTIFICATION_ID, notification); 1.248 + 1.249 + return; 1.250 + } 1.251 + 1.252 + File pkg = downloadUpdatePackage(info, hasFlag(flags, UpdateServiceHelper.FLAG_OVERWRITE_EXISTING)); 1.253 + if (pkg == null) { 1.254 + sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.NOT_AVAILABLE); 1.255 + return; 1.256 + } 1.257 + 1.258 + Log.i(LOGTAG, "have update package at " + pkg); 1.259 + 1.260 + saveUpdateInfo(info, pkg); 1.261 + sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.DOWNLOADED); 1.262 + 1.263 + if (mApplyImmediately) { 1.264 + applyUpdate(pkg); 1.265 + } else { 1.266 + // Prompt to apply the update 1.267 + Notification notification = new Notification(R.drawable.ic_status_logo, null, System.currentTimeMillis()); 1.268 + 1.269 + Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE); 1.270 + notificationIntent.setClass(this, UpdateService.class); 1.271 + notificationIntent.putExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME, pkg.getAbsolutePath()); 1.272 + 1.273 + PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); 1.274 + notification.flags = Notification.FLAG_AUTO_CANCEL; 1.275 + 1.276 + notification.setLatestEventInfo(this, getResources().getString(R.string.updater_apply_title), 1.277 + getResources().getString(R.string.updater_apply_select), 1.278 + contentIntent); 1.279 + 1.280 + mNotificationManager.notify(NOTIFICATION_ID, notification); 1.281 + } 1.282 + } 1.283 + 1.284 + private URLConnection openConnectionWithProxy(URL url) throws java.net.URISyntaxException, java.io.IOException { 1.285 + Log.i(LOGTAG, "opening connection with url: " + url); 1.286 + 1.287 + ProxySelector ps = ProxySelector.getDefault(); 1.288 + Proxy proxy = Proxy.NO_PROXY; 1.289 + if (ps != null) { 1.290 + List<Proxy> proxies = ps.select(url.toURI()); 1.291 + if (proxies != null && !proxies.isEmpty()) { 1.292 + proxy = proxies.get(0); 1.293 + } 1.294 + } 1.295 + 1.296 + return url.openConnection(proxy); 1.297 + } 1.298 + 1.299 + private UpdateInfo findUpdate(boolean force) { 1.300 + try { 1.301 + URL url = UpdateServiceHelper.getUpdateUrl(this, force); 1.302 + 1.303 + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 1.304 + Document dom = builder.parse(openConnectionWithProxy(url).getInputStream()); 1.305 + 1.306 + NodeList nodes = dom.getElementsByTagName("update"); 1.307 + if (nodes == null || nodes.getLength() == 0) 1.308 + return null; 1.309 + 1.310 + Node updateNode = nodes.item(0); 1.311 + Node buildIdNode = updateNode.getAttributes().getNamedItem("buildID"); 1.312 + if (buildIdNode == null) 1.313 + return null; 1.314 + 1.315 + nodes = dom.getElementsByTagName("patch"); 1.316 + if (nodes == null || nodes.getLength() == 0) 1.317 + return null; 1.318 + 1.319 + Node patchNode = nodes.item(0); 1.320 + Node urlNode = patchNode.getAttributes().getNamedItem("URL"); 1.321 + Node hashFunctionNode = patchNode.getAttributes().getNamedItem("hashFunction"); 1.322 + Node hashValueNode = patchNode.getAttributes().getNamedItem("hashValue"); 1.323 + Node sizeNode = patchNode.getAttributes().getNamedItem("size"); 1.324 + 1.325 + if (urlNode == null || hashFunctionNode == null || 1.326 + hashValueNode == null || sizeNode == null) { 1.327 + return null; 1.328 + } 1.329 + 1.330 + // Fill in UpdateInfo from the XML data 1.331 + UpdateInfo info = new UpdateInfo(); 1.332 + info.url = new URL(urlNode.getTextContent()); 1.333 + info.buildID = buildIdNode.getTextContent(); 1.334 + info.hashFunction = hashFunctionNode.getTextContent(); 1.335 + info.hashValue = hashValueNode.getTextContent(); 1.336 + 1.337 + try { 1.338 + info.size = Integer.parseInt(sizeNode.getTextContent()); 1.339 + } catch (NumberFormatException e) { 1.340 + Log.e(LOGTAG, "Failed to find APK size: ", e); 1.341 + return null; 1.342 + } 1.343 + 1.344 + // Make sure we have all the stuff we need to apply the update 1.345 + if (!info.isValid()) { 1.346 + Log.e(LOGTAG, "missing some required update information, have: " + info); 1.347 + return null; 1.348 + } 1.349 + 1.350 + return info; 1.351 + } catch (Exception e) { 1.352 + Log.e(LOGTAG, "failed to check for update: ", e); 1.353 + return null; 1.354 + } 1.355 + } 1.356 + 1.357 + private MessageDigest createMessageDigest(String hashFunction) { 1.358 + String javaHashFunction = null; 1.359 + 1.360 + if ("sha512".equalsIgnoreCase(hashFunction)) { 1.361 + javaHashFunction = "SHA-512"; 1.362 + } else { 1.363 + Log.e(LOGTAG, "Unhandled hash function: " + hashFunction); 1.364 + return null; 1.365 + } 1.366 + 1.367 + try { 1.368 + return MessageDigest.getInstance(javaHashFunction); 1.369 + } catch (java.security.NoSuchAlgorithmException e) { 1.370 + Log.e(LOGTAG, "Couldn't find algorithm " + javaHashFunction, e); 1.371 + return null; 1.372 + } 1.373 + } 1.374 + 1.375 + private void showDownloadNotification() { 1.376 + showDownloadNotification(null); 1.377 + } 1.378 + 1.379 + private void showDownloadNotification(File downloadFile) { 1.380 + 1.381 + Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE); 1.382 + notificationIntent.setClass(this, UpdateService.class); 1.383 + 1.384 + Intent cancelIntent = new Intent(UpdateServiceHelper.ACTION_CANCEL_DOWNLOAD); 1.385 + cancelIntent.setClass(this, UpdateService.class); 1.386 + 1.387 + if (downloadFile != null) 1.388 + notificationIntent.putExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME, downloadFile.getAbsolutePath()); 1.389 + 1.390 + PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); 1.391 + PendingIntent deleteIntent = PendingIntent.getService(this, 0, cancelIntent, PendingIntent.FLAG_CANCEL_CURRENT); 1.392 + 1.393 + mBuilder = new NotificationCompat.Builder(this); 1.394 + mBuilder.setContentTitle(getResources().getString(R.string.updater_downloading_title)) 1.395 + .setContentText(mApplyImmediately ? "" : getResources().getString(R.string.updater_downloading_select)) 1.396 + .setSmallIcon(android.R.drawable.stat_sys_download) 1.397 + .setContentIntent(contentIntent) 1.398 + .setDeleteIntent(deleteIntent); 1.399 + 1.400 + mBuilder.setProgress(100, 0, true); 1.401 + mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build()); 1.402 + } 1.403 + 1.404 + private void showDownloadFailure() { 1.405 + Notification notification = new Notification(R.drawable.ic_status_logo, null, System.currentTimeMillis()); 1.406 + 1.407 + Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE); 1.408 + notificationIntent.setClass(this, UpdateService.class); 1.409 + 1.410 + PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); 1.411 + 1.412 + notification.setLatestEventInfo(this, getResources().getString(R.string.updater_downloading_title_failed), 1.413 + getResources().getString(R.string.updater_downloading_retry), 1.414 + contentIntent); 1.415 + 1.416 + mNotificationManager.notify(NOTIFICATION_ID, notification); 1.417 + } 1.418 + 1.419 + private File downloadUpdatePackage(UpdateInfo info, boolean overwriteExisting) { 1.420 + File downloadFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), new File(info.url.getFile()).getName()); 1.421 + 1.422 + if (!overwriteExisting && info.buildID.equals(getLastBuildID()) && downloadFile.exists()) { 1.423 + // The last saved buildID is the same as the one for the current update. We also have a file 1.424 + // already downloaded, so it's probably the package we want. Verify it to be sure and just 1.425 + // return that if it matches. 1.426 + 1.427 + if (verifyDownloadedPackage(downloadFile)) { 1.428 + Log.i(LOGTAG, "using existing update package"); 1.429 + return downloadFile; 1.430 + } else { 1.431 + // Didn't match, so we're going to download a new one. 1.432 + downloadFile.delete(); 1.433 + } 1.434 + } 1.435 + 1.436 + Log.i(LOGTAG, "downloading update package"); 1.437 + sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.DOWNLOADING); 1.438 + 1.439 + OutputStream output = null; 1.440 + InputStream input = null; 1.441 + 1.442 + mDownloading = true; 1.443 + mCancelDownload = false; 1.444 + showDownloadNotification(downloadFile); 1.445 + 1.446 + try { 1.447 + URLConnection conn = openConnectionWithProxy(info.url); 1.448 + int length = conn.getContentLength(); 1.449 + 1.450 + output = new BufferedOutputStream(new FileOutputStream(downloadFile)); 1.451 + input = new BufferedInputStream(conn.getInputStream()); 1.452 + 1.453 + byte[] buf = new byte[BUFSIZE]; 1.454 + int len = 0; 1.455 + 1.456 + int bytesRead = 0; 1.457 + int lastNotify = 0; 1.458 + 1.459 + while ((len = input.read(buf, 0, BUFSIZE)) > 0 && !mCancelDownload) { 1.460 + output.write(buf, 0, len); 1.461 + bytesRead += len; 1.462 + // Updating the notification takes time so only do it every 1MB 1.463 + if(bytesRead - lastNotify > 1048576) { 1.464 + mBuilder.setProgress(length, bytesRead, false); 1.465 + mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build()); 1.466 + lastNotify = bytesRead; 1.467 + } 1.468 + } 1.469 + 1.470 + mNotificationManager.cancel(NOTIFICATION_ID); 1.471 + 1.472 + // if the download was canceled by the user 1.473 + // delete the update package 1.474 + if (mCancelDownload) { 1.475 + Log.i(LOGTAG, "download canceled by user!"); 1.476 + downloadFile.delete(); 1.477 + 1.478 + return null; 1.479 + } else { 1.480 + Log.i(LOGTAG, "completed update download!"); 1.481 + return downloadFile; 1.482 + } 1.483 + } catch (Exception e) { 1.484 + downloadFile.delete(); 1.485 + showDownloadFailure(); 1.486 + 1.487 + Log.e(LOGTAG, "failed to download update: ", e); 1.488 + return null; 1.489 + } finally { 1.490 + try { 1.491 + if (input != null) 1.492 + input.close(); 1.493 + } catch (java.io.IOException e) {} 1.494 + 1.495 + try { 1.496 + if (output != null) 1.497 + output.close(); 1.498 + } catch (java.io.IOException e) {} 1.499 + 1.500 + mDownloading = false; 1.501 + } 1.502 + } 1.503 + 1.504 + private boolean verifyDownloadedPackage(File updateFile) { 1.505 + MessageDigest digest = createMessageDigest(getLastHashFunction()); 1.506 + if (digest == null) 1.507 + return false; 1.508 + 1.509 + InputStream input = null; 1.510 + 1.511 + try { 1.512 + input = new BufferedInputStream(new FileInputStream(updateFile)); 1.513 + 1.514 + byte[] buf = new byte[BUFSIZE]; 1.515 + int len; 1.516 + while ((len = input.read(buf, 0, BUFSIZE)) > 0) { 1.517 + digest.update(buf, 0, len); 1.518 + } 1.519 + } catch (java.io.IOException e) { 1.520 + Log.e(LOGTAG, "Failed to verify update package: ", e); 1.521 + return false; 1.522 + } finally { 1.523 + try { 1.524 + if (input != null) 1.525 + input.close(); 1.526 + } catch(java.io.IOException e) {} 1.527 + } 1.528 + 1.529 + String hex = Hex.encodeHexString(digest.digest()); 1.530 + if (!hex.equals(getLastHashValue())) { 1.531 + Log.e(LOGTAG, "Package hash does not match"); 1.532 + return false; 1.533 + } 1.534 + 1.535 + return true; 1.536 + } 1.537 + 1.538 + private void applyUpdate(String updatePath) { 1.539 + if (updatePath == null) { 1.540 + updatePath = mPrefs.getString(KEY_LAST_FILE_NAME, null); 1.541 + } 1.542 + applyUpdate(new File(updatePath)); 1.543 + } 1.544 + 1.545 + private void applyUpdate(File updateFile) { 1.546 + mApplyImmediately = false; 1.547 + 1.548 + if (!updateFile.exists()) 1.549 + return; 1.550 + 1.551 + Log.i(LOGTAG, "Verifying package: " + updateFile); 1.552 + 1.553 + if (!verifyDownloadedPackage(updateFile)) { 1.554 + Log.e(LOGTAG, "Not installing update, failed verification"); 1.555 + return; 1.556 + } 1.557 + 1.558 + Intent intent = new Intent(Intent.ACTION_VIEW); 1.559 + intent.setDataAndType(Uri.fromFile(updateFile), "application/vnd.android.package-archive"); 1.560 + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1.561 + startActivity(intent); 1.562 + } 1.563 + 1.564 + private String getLastBuildID() { 1.565 + return mPrefs.getString(KEY_LAST_BUILDID, null); 1.566 + } 1.567 + 1.568 + private String getLastHashFunction() { 1.569 + return mPrefs.getString(KEY_LAST_HASH_FUNCTION, null); 1.570 + } 1.571 + 1.572 + private String getLastHashValue() { 1.573 + return mPrefs.getString(KEY_LAST_HASH_VALUE, null); 1.574 + } 1.575 + 1.576 + private Calendar getLastAttemptDate() { 1.577 + long lastAttempt = mPrefs.getLong(KEY_LAST_ATTEMPT_DATE, -1); 1.578 + if (lastAttempt < 0) 1.579 + return null; 1.580 + 1.581 + GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); 1.582 + cal.setTimeInMillis(lastAttempt); 1.583 + return cal; 1.584 + } 1.585 + 1.586 + private void setLastAttemptDate() { 1.587 + SharedPreferences.Editor editor = mPrefs.edit(); 1.588 + editor.putLong(KEY_LAST_ATTEMPT_DATE, System.currentTimeMillis()); 1.589 + editor.commit(); 1.590 + } 1.591 + 1.592 + private int getAutoDownloadPolicy() { 1.593 + return mPrefs.getInt(KEY_AUTODOWNLOAD_POLICY, UpdateServiceHelper.AUTODOWNLOAD_WIFI); 1.594 + } 1.595 + 1.596 + private void setAutoDownloadPolicy(int policy) { 1.597 + SharedPreferences.Editor editor = mPrefs.edit(); 1.598 + editor.putInt(KEY_AUTODOWNLOAD_POLICY, policy); 1.599 + editor.commit(); 1.600 + } 1.601 + 1.602 + private void saveUpdateInfo(UpdateInfo info, File downloaded) { 1.603 + SharedPreferences.Editor editor = mPrefs.edit(); 1.604 + editor.putString(KEY_LAST_BUILDID, info.buildID); 1.605 + editor.putString(KEY_LAST_HASH_FUNCTION, info.hashFunction); 1.606 + editor.putString(KEY_LAST_HASH_VALUE, info.hashValue); 1.607 + editor.putString(KEY_LAST_FILE_NAME, downloaded.toString()); 1.608 + editor.commit(); 1.609 + } 1.610 + 1.611 + private class UpdateInfo { 1.612 + public URL url; 1.613 + public String buildID; 1.614 + public String hashFunction; 1.615 + public String hashValue; 1.616 + public int size; 1.617 + 1.618 + private boolean isNonEmpty(String s) { 1.619 + return s != null && s.length() > 0; 1.620 + } 1.621 + 1.622 + public boolean isValid() { 1.623 + return url != null && isNonEmpty(buildID) && 1.624 + isNonEmpty(hashFunction) && isNonEmpty(hashValue) && size > 0; 1.625 + } 1.626 + 1.627 + @Override 1.628 + public String toString() { 1.629 + return "url = " + url + ", buildID = " + buildID + ", hashFunction = " + hashFunction + ", hashValue = " + hashValue + ", size = " + size; 1.630 + } 1.631 + } 1.632 +}