michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.updater; michael@0: michael@0: import org.mozilla.gecko.AppConstants; michael@0: import org.mozilla.gecko.R; michael@0: michael@0: import org.mozilla.apache.commons.codec.binary.Hex; michael@0: michael@0: import org.w3c.dom.Document; michael@0: import org.w3c.dom.Node; michael@0: import org.w3c.dom.NodeList; michael@0: michael@0: import android.app.AlarmManager; michael@0: import android.app.IntentService; michael@0: import android.app.Notification; michael@0: import android.app.NotificationManager; michael@0: import android.app.PendingIntent; michael@0: import android.app.Service; michael@0: import android.content.Context; michael@0: import android.content.Intent; michael@0: import android.content.SharedPreferences; michael@0: import android.net.ConnectivityManager; michael@0: import android.net.NetworkInfo; michael@0: import android.net.Uri; michael@0: import android.os.Environment; michael@0: import android.support.v4.app.NotificationCompat; michael@0: import android.support.v4.app.NotificationCompat.Builder; michael@0: import android.util.Log; michael@0: michael@0: import java.io.BufferedInputStream; michael@0: import java.io.BufferedOutputStream; michael@0: import java.io.File; michael@0: import java.io.FileInputStream; michael@0: import java.io.FileOutputStream; michael@0: import java.io.InputStream; michael@0: import java.io.OutputStream; michael@0: import java.net.Proxy; michael@0: import java.net.ProxySelector; michael@0: import java.net.URL; michael@0: import java.net.URLConnection; michael@0: import java.security.MessageDigest; michael@0: import java.util.Calendar; michael@0: import java.util.GregorianCalendar; michael@0: import java.util.List; michael@0: import java.util.TimeZone; michael@0: michael@0: import javax.xml.parsers.DocumentBuilder; michael@0: import javax.xml.parsers.DocumentBuilderFactory; michael@0: michael@0: public class UpdateService extends IntentService { michael@0: private static final int BUFSIZE = 8192; michael@0: private static final int NOTIFICATION_ID = 0x3e40ddbd; michael@0: michael@0: private static final String LOGTAG = "UpdateService"; michael@0: michael@0: private static final int INTERVAL_LONG = 86400000; // in milliseconds michael@0: private static final int INTERVAL_SHORT = 14400000; // again, in milliseconds michael@0: private static final int INTERVAL_RETRY = 3600000; michael@0: michael@0: private static final String PREFS_NAME = "UpdateService"; michael@0: private static final String KEY_LAST_BUILDID = "UpdateService.lastBuildID"; michael@0: private static final String KEY_LAST_HASH_FUNCTION = "UpdateService.lastHashFunction"; michael@0: private static final String KEY_LAST_HASH_VALUE = "UpdateService.lastHashValue"; michael@0: private static final String KEY_LAST_FILE_NAME = "UpdateService.lastFileName"; michael@0: private static final String KEY_LAST_ATTEMPT_DATE = "UpdateService.lastAttemptDate"; michael@0: private static final String KEY_AUTODOWNLOAD_POLICY = "UpdateService.autoDownloadPolicy"; michael@0: michael@0: private SharedPreferences mPrefs; michael@0: michael@0: private NotificationManager mNotificationManager; michael@0: private ConnectivityManager mConnectivityManager; michael@0: private Builder mBuilder; michael@0: michael@0: private boolean mDownloading; michael@0: private boolean mCancelDownload; michael@0: private boolean mApplyImmediately; michael@0: michael@0: public UpdateService() { michael@0: super("updater"); michael@0: } michael@0: michael@0: @Override michael@0: public void onCreate () { michael@0: super.onCreate(); michael@0: michael@0: mPrefs = getSharedPreferences(PREFS_NAME, 0); michael@0: mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); michael@0: mConnectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); michael@0: mCancelDownload = false; michael@0: } michael@0: michael@0: @Override michael@0: public synchronized int onStartCommand (Intent intent, int flags, int startId) { michael@0: // If we are busy doing a download, the new Intent here would normally be queued for michael@0: // execution once that is done. In this case, however, we want to flip the boolean michael@0: // while that is running, so handle that now. michael@0: if (mDownloading && UpdateServiceHelper.ACTION_APPLY_UPDATE.equals(intent.getAction())) { michael@0: Log.i(LOGTAG, "will apply update when download finished"); michael@0: michael@0: mApplyImmediately = true; michael@0: showDownloadNotification(); michael@0: } else if (UpdateServiceHelper.ACTION_CANCEL_DOWNLOAD.equals(intent.getAction())) { michael@0: mCancelDownload = true; michael@0: } else { michael@0: super.onStartCommand(intent, flags, startId); michael@0: } michael@0: michael@0: return Service.START_REDELIVER_INTENT; michael@0: } michael@0: michael@0: @Override michael@0: protected void onHandleIntent (Intent intent) { michael@0: if (UpdateServiceHelper.ACTION_REGISTER_FOR_UPDATES.equals(intent.getAction())) { michael@0: int policy = intent.getIntExtra(UpdateServiceHelper.EXTRA_AUTODOWNLOAD_NAME, -1); michael@0: if (policy >= 0) { michael@0: setAutoDownloadPolicy(policy); michael@0: } michael@0: michael@0: registerForUpdates(false); michael@0: } else if (UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE.equals(intent.getAction())) { michael@0: startUpdate(intent.getIntExtra(UpdateServiceHelper.EXTRA_UPDATE_FLAGS_NAME, 0)); michael@0: // Use this instead for forcing a download from about:fennec michael@0: // startUpdate(UpdateServiceHelper.FLAG_FORCE_DOWNLOAD | UpdateServiceHelper.FLAG_REINSTALL); michael@0: } else if (UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE.equals(intent.getAction())) { michael@0: // We always want to do the download and apply it here michael@0: mApplyImmediately = true; michael@0: startUpdate(UpdateServiceHelper.FLAG_FORCE_DOWNLOAD); michael@0: } else if (UpdateServiceHelper.ACTION_APPLY_UPDATE.equals(intent.getAction())) { michael@0: applyUpdate(intent.getStringExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME)); michael@0: } michael@0: } michael@0: michael@0: private static boolean hasFlag(int flags, int flag) { michael@0: return (flags & flag) == flag; michael@0: } michael@0: michael@0: private void sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult result) { michael@0: Intent resultIntent = new Intent(UpdateServiceHelper.ACTION_CHECK_UPDATE_RESULT); michael@0: resultIntent.putExtra("result", result.toString()); michael@0: sendBroadcast(resultIntent); michael@0: } michael@0: michael@0: private int getUpdateInterval(boolean isRetry) { michael@0: int interval; michael@0: if (isRetry) { michael@0: interval = INTERVAL_RETRY; michael@0: } else if (!AppConstants.RELEASE_BUILD) { michael@0: interval = INTERVAL_SHORT; michael@0: } else { michael@0: interval = INTERVAL_LONG; michael@0: } michael@0: michael@0: return interval; michael@0: } michael@0: michael@0: private void registerForUpdates(boolean isRetry) { michael@0: Calendar lastAttempt = getLastAttemptDate(); michael@0: Calendar now = new GregorianCalendar(TimeZone.getTimeZone("GMT")); michael@0: michael@0: int interval = getUpdateInterval(isRetry); michael@0: michael@0: if (lastAttempt == null || (now.getTimeInMillis() - lastAttempt.getTimeInMillis()) > interval) { michael@0: // We've either never attempted an update, or we are passed the desired michael@0: // time. Start an update now. michael@0: Log.i(LOGTAG, "no update has ever been attempted, checking now"); michael@0: startUpdate(0); michael@0: return; michael@0: } michael@0: michael@0: AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); michael@0: if (manager == null) michael@0: return; michael@0: michael@0: PendingIntent pending = PendingIntent.getService(this, 0, new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE, null, this, UpdateService.class), PendingIntent.FLAG_UPDATE_CURRENT); michael@0: manager.cancel(pending); michael@0: michael@0: lastAttempt.setTimeInMillis(lastAttempt.getTimeInMillis() + interval); michael@0: Log.i(LOGTAG, "next update will be at: " + lastAttempt.getTime()); michael@0: michael@0: manager.set(AlarmManager.RTC_WAKEUP, lastAttempt.getTimeInMillis(), pending); michael@0: } michael@0: michael@0: private void startUpdate(int flags) { michael@0: setLastAttemptDate(); michael@0: michael@0: NetworkInfo netInfo = mConnectivityManager.getActiveNetworkInfo(); michael@0: if (netInfo == null || !netInfo.isConnected()) { michael@0: Log.i(LOGTAG, "not connected to the network"); michael@0: registerForUpdates(true); michael@0: sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.NOT_AVAILABLE); michael@0: return; michael@0: } michael@0: michael@0: registerForUpdates(false); michael@0: michael@0: UpdateInfo info = findUpdate(hasFlag(flags, UpdateServiceHelper.FLAG_REINSTALL)); michael@0: boolean haveUpdate = (info != null); michael@0: michael@0: if (!haveUpdate) { michael@0: Log.i(LOGTAG, "no update available"); michael@0: sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.NOT_AVAILABLE); michael@0: return; michael@0: } michael@0: michael@0: Log.i(LOGTAG, "update available, buildID = " + info.buildID); michael@0: michael@0: int connectionType = netInfo.getType(); michael@0: int autoDownloadPolicy = getAutoDownloadPolicy(); michael@0: michael@0: michael@0: /** michael@0: * We only start a download automatically if one of following criteria are met: michael@0: * michael@0: * - We have a FORCE_DOWNLOAD flag passed in michael@0: * - The preference is set to 'always' michael@0: * - The preference is set to 'wifi' and we are actually using wifi (or regular ethernet) michael@0: */ michael@0: boolean shouldStartDownload = hasFlag(flags, UpdateServiceHelper.FLAG_FORCE_DOWNLOAD) || michael@0: autoDownloadPolicy == UpdateServiceHelper.AUTODOWNLOAD_ENABLED || michael@0: (autoDownloadPolicy == UpdateServiceHelper.AUTODOWNLOAD_WIFI && michael@0: (connectionType == ConnectivityManager.TYPE_WIFI || connectionType == ConnectivityManager.TYPE_ETHERNET)); michael@0: michael@0: if (!shouldStartDownload) { michael@0: Log.i(LOGTAG, "not initiating automatic update download due to policy " + autoDownloadPolicy); michael@0: sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.AVAILABLE); michael@0: michael@0: // We aren't autodownloading here, so prompt to start the update michael@0: Notification notification = new Notification(R.drawable.ic_status_logo, null, System.currentTimeMillis()); michael@0: michael@0: Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE); michael@0: notificationIntent.setClass(this, UpdateService.class); michael@0: michael@0: PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); michael@0: notification.flags = Notification.FLAG_AUTO_CANCEL; michael@0: michael@0: notification.setLatestEventInfo(this, getResources().getString(R.string.updater_start_title), michael@0: getResources().getString(R.string.updater_start_select), michael@0: contentIntent); michael@0: michael@0: mNotificationManager.notify(NOTIFICATION_ID, notification); michael@0: michael@0: return; michael@0: } michael@0: michael@0: File pkg = downloadUpdatePackage(info, hasFlag(flags, UpdateServiceHelper.FLAG_OVERWRITE_EXISTING)); michael@0: if (pkg == null) { michael@0: sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.NOT_AVAILABLE); michael@0: return; michael@0: } michael@0: michael@0: Log.i(LOGTAG, "have update package at " + pkg); michael@0: michael@0: saveUpdateInfo(info, pkg); michael@0: sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.DOWNLOADED); michael@0: michael@0: if (mApplyImmediately) { michael@0: applyUpdate(pkg); michael@0: } else { michael@0: // Prompt to apply the update michael@0: Notification notification = new Notification(R.drawable.ic_status_logo, null, System.currentTimeMillis()); michael@0: michael@0: Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE); michael@0: notificationIntent.setClass(this, UpdateService.class); michael@0: notificationIntent.putExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME, pkg.getAbsolutePath()); michael@0: michael@0: PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); michael@0: notification.flags = Notification.FLAG_AUTO_CANCEL; michael@0: michael@0: notification.setLatestEventInfo(this, getResources().getString(R.string.updater_apply_title), michael@0: getResources().getString(R.string.updater_apply_select), michael@0: contentIntent); michael@0: michael@0: mNotificationManager.notify(NOTIFICATION_ID, notification); michael@0: } michael@0: } michael@0: michael@0: private URLConnection openConnectionWithProxy(URL url) throws java.net.URISyntaxException, java.io.IOException { michael@0: Log.i(LOGTAG, "opening connection with url: " + url); michael@0: michael@0: ProxySelector ps = ProxySelector.getDefault(); michael@0: Proxy proxy = Proxy.NO_PROXY; michael@0: if (ps != null) { michael@0: List proxies = ps.select(url.toURI()); michael@0: if (proxies != null && !proxies.isEmpty()) { michael@0: proxy = proxies.get(0); michael@0: } michael@0: } michael@0: michael@0: return url.openConnection(proxy); michael@0: } michael@0: michael@0: private UpdateInfo findUpdate(boolean force) { michael@0: try { michael@0: URL url = UpdateServiceHelper.getUpdateUrl(this, force); michael@0: michael@0: DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); michael@0: Document dom = builder.parse(openConnectionWithProxy(url).getInputStream()); michael@0: michael@0: NodeList nodes = dom.getElementsByTagName("update"); michael@0: if (nodes == null || nodes.getLength() == 0) michael@0: return null; michael@0: michael@0: Node updateNode = nodes.item(0); michael@0: Node buildIdNode = updateNode.getAttributes().getNamedItem("buildID"); michael@0: if (buildIdNode == null) michael@0: return null; michael@0: michael@0: nodes = dom.getElementsByTagName("patch"); michael@0: if (nodes == null || nodes.getLength() == 0) michael@0: return null; michael@0: michael@0: Node patchNode = nodes.item(0); michael@0: Node urlNode = patchNode.getAttributes().getNamedItem("URL"); michael@0: Node hashFunctionNode = patchNode.getAttributes().getNamedItem("hashFunction"); michael@0: Node hashValueNode = patchNode.getAttributes().getNamedItem("hashValue"); michael@0: Node sizeNode = patchNode.getAttributes().getNamedItem("size"); michael@0: michael@0: if (urlNode == null || hashFunctionNode == null || michael@0: hashValueNode == null || sizeNode == null) { michael@0: return null; michael@0: } michael@0: michael@0: // Fill in UpdateInfo from the XML data michael@0: UpdateInfo info = new UpdateInfo(); michael@0: info.url = new URL(urlNode.getTextContent()); michael@0: info.buildID = buildIdNode.getTextContent(); michael@0: info.hashFunction = hashFunctionNode.getTextContent(); michael@0: info.hashValue = hashValueNode.getTextContent(); michael@0: michael@0: try { michael@0: info.size = Integer.parseInt(sizeNode.getTextContent()); michael@0: } catch (NumberFormatException e) { michael@0: Log.e(LOGTAG, "Failed to find APK size: ", e); michael@0: return null; michael@0: } michael@0: michael@0: // Make sure we have all the stuff we need to apply the update michael@0: if (!info.isValid()) { michael@0: Log.e(LOGTAG, "missing some required update information, have: " + info); michael@0: return null; michael@0: } michael@0: michael@0: return info; michael@0: } catch (Exception e) { michael@0: Log.e(LOGTAG, "failed to check for update: ", e); michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: private MessageDigest createMessageDigest(String hashFunction) { michael@0: String javaHashFunction = null; michael@0: michael@0: if ("sha512".equalsIgnoreCase(hashFunction)) { michael@0: javaHashFunction = "SHA-512"; michael@0: } else { michael@0: Log.e(LOGTAG, "Unhandled hash function: " + hashFunction); michael@0: return null; michael@0: } michael@0: michael@0: try { michael@0: return MessageDigest.getInstance(javaHashFunction); michael@0: } catch (java.security.NoSuchAlgorithmException e) { michael@0: Log.e(LOGTAG, "Couldn't find algorithm " + javaHashFunction, e); michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: private void showDownloadNotification() { michael@0: showDownloadNotification(null); michael@0: } michael@0: michael@0: private void showDownloadNotification(File downloadFile) { michael@0: michael@0: Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE); michael@0: notificationIntent.setClass(this, UpdateService.class); michael@0: michael@0: Intent cancelIntent = new Intent(UpdateServiceHelper.ACTION_CANCEL_DOWNLOAD); michael@0: cancelIntent.setClass(this, UpdateService.class); michael@0: michael@0: if (downloadFile != null) michael@0: notificationIntent.putExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME, downloadFile.getAbsolutePath()); michael@0: michael@0: PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); michael@0: PendingIntent deleteIntent = PendingIntent.getService(this, 0, cancelIntent, PendingIntent.FLAG_CANCEL_CURRENT); michael@0: michael@0: mBuilder = new NotificationCompat.Builder(this); michael@0: mBuilder.setContentTitle(getResources().getString(R.string.updater_downloading_title)) michael@0: .setContentText(mApplyImmediately ? "" : getResources().getString(R.string.updater_downloading_select)) michael@0: .setSmallIcon(android.R.drawable.stat_sys_download) michael@0: .setContentIntent(contentIntent) michael@0: .setDeleteIntent(deleteIntent); michael@0: michael@0: mBuilder.setProgress(100, 0, true); michael@0: mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build()); michael@0: } michael@0: michael@0: private void showDownloadFailure() { michael@0: Notification notification = new Notification(R.drawable.ic_status_logo, null, System.currentTimeMillis()); michael@0: michael@0: Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE); michael@0: notificationIntent.setClass(this, UpdateService.class); michael@0: michael@0: PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); michael@0: michael@0: notification.setLatestEventInfo(this, getResources().getString(R.string.updater_downloading_title_failed), michael@0: getResources().getString(R.string.updater_downloading_retry), michael@0: contentIntent); michael@0: michael@0: mNotificationManager.notify(NOTIFICATION_ID, notification); michael@0: } michael@0: michael@0: private File downloadUpdatePackage(UpdateInfo info, boolean overwriteExisting) { michael@0: File downloadFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), new File(info.url.getFile()).getName()); michael@0: michael@0: if (!overwriteExisting && info.buildID.equals(getLastBuildID()) && downloadFile.exists()) { michael@0: // The last saved buildID is the same as the one for the current update. We also have a file michael@0: // already downloaded, so it's probably the package we want. Verify it to be sure and just michael@0: // return that if it matches. michael@0: michael@0: if (verifyDownloadedPackage(downloadFile)) { michael@0: Log.i(LOGTAG, "using existing update package"); michael@0: return downloadFile; michael@0: } else { michael@0: // Didn't match, so we're going to download a new one. michael@0: downloadFile.delete(); michael@0: } michael@0: } michael@0: michael@0: Log.i(LOGTAG, "downloading update package"); michael@0: sendCheckUpdateResult(UpdateServiceHelper.CheckUpdateResult.DOWNLOADING); michael@0: michael@0: OutputStream output = null; michael@0: InputStream input = null; michael@0: michael@0: mDownloading = true; michael@0: mCancelDownload = false; michael@0: showDownloadNotification(downloadFile); michael@0: michael@0: try { michael@0: URLConnection conn = openConnectionWithProxy(info.url); michael@0: int length = conn.getContentLength(); michael@0: michael@0: output = new BufferedOutputStream(new FileOutputStream(downloadFile)); michael@0: input = new BufferedInputStream(conn.getInputStream()); michael@0: michael@0: byte[] buf = new byte[BUFSIZE]; michael@0: int len = 0; michael@0: michael@0: int bytesRead = 0; michael@0: int lastNotify = 0; michael@0: michael@0: while ((len = input.read(buf, 0, BUFSIZE)) > 0 && !mCancelDownload) { michael@0: output.write(buf, 0, len); michael@0: bytesRead += len; michael@0: // Updating the notification takes time so only do it every 1MB michael@0: if(bytesRead - lastNotify > 1048576) { michael@0: mBuilder.setProgress(length, bytesRead, false); michael@0: mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build()); michael@0: lastNotify = bytesRead; michael@0: } michael@0: } michael@0: michael@0: mNotificationManager.cancel(NOTIFICATION_ID); michael@0: michael@0: // if the download was canceled by the user michael@0: // delete the update package michael@0: if (mCancelDownload) { michael@0: Log.i(LOGTAG, "download canceled by user!"); michael@0: downloadFile.delete(); michael@0: michael@0: return null; michael@0: } else { michael@0: Log.i(LOGTAG, "completed update download!"); michael@0: return downloadFile; michael@0: } michael@0: } catch (Exception e) { michael@0: downloadFile.delete(); michael@0: showDownloadFailure(); michael@0: michael@0: Log.e(LOGTAG, "failed to download update: ", e); michael@0: return null; michael@0: } finally { michael@0: try { michael@0: if (input != null) michael@0: input.close(); michael@0: } catch (java.io.IOException e) {} michael@0: michael@0: try { michael@0: if (output != null) michael@0: output.close(); michael@0: } catch (java.io.IOException e) {} michael@0: michael@0: mDownloading = false; michael@0: } michael@0: } michael@0: michael@0: private boolean verifyDownloadedPackage(File updateFile) { michael@0: MessageDigest digest = createMessageDigest(getLastHashFunction()); michael@0: if (digest == null) michael@0: return false; michael@0: michael@0: InputStream input = null; michael@0: michael@0: try { michael@0: input = new BufferedInputStream(new FileInputStream(updateFile)); michael@0: michael@0: byte[] buf = new byte[BUFSIZE]; michael@0: int len; michael@0: while ((len = input.read(buf, 0, BUFSIZE)) > 0) { michael@0: digest.update(buf, 0, len); michael@0: } michael@0: } catch (java.io.IOException e) { michael@0: Log.e(LOGTAG, "Failed to verify update package: ", e); michael@0: return false; michael@0: } finally { michael@0: try { michael@0: if (input != null) michael@0: input.close(); michael@0: } catch(java.io.IOException e) {} michael@0: } michael@0: michael@0: String hex = Hex.encodeHexString(digest.digest()); michael@0: if (!hex.equals(getLastHashValue())) { michael@0: Log.e(LOGTAG, "Package hash does not match"); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: private void applyUpdate(String updatePath) { michael@0: if (updatePath == null) { michael@0: updatePath = mPrefs.getString(KEY_LAST_FILE_NAME, null); michael@0: } michael@0: applyUpdate(new File(updatePath)); michael@0: } michael@0: michael@0: private void applyUpdate(File updateFile) { michael@0: mApplyImmediately = false; michael@0: michael@0: if (!updateFile.exists()) michael@0: return; michael@0: michael@0: Log.i(LOGTAG, "Verifying package: " + updateFile); michael@0: michael@0: if (!verifyDownloadedPackage(updateFile)) { michael@0: Log.e(LOGTAG, "Not installing update, failed verification"); michael@0: return; michael@0: } michael@0: michael@0: Intent intent = new Intent(Intent.ACTION_VIEW); michael@0: intent.setDataAndType(Uri.fromFile(updateFile), "application/vnd.android.package-archive"); michael@0: intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); michael@0: startActivity(intent); michael@0: } michael@0: michael@0: private String getLastBuildID() { michael@0: return mPrefs.getString(KEY_LAST_BUILDID, null); michael@0: } michael@0: michael@0: private String getLastHashFunction() { michael@0: return mPrefs.getString(KEY_LAST_HASH_FUNCTION, null); michael@0: } michael@0: michael@0: private String getLastHashValue() { michael@0: return mPrefs.getString(KEY_LAST_HASH_VALUE, null); michael@0: } michael@0: michael@0: private Calendar getLastAttemptDate() { michael@0: long lastAttempt = mPrefs.getLong(KEY_LAST_ATTEMPT_DATE, -1); michael@0: if (lastAttempt < 0) michael@0: return null; michael@0: michael@0: GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); michael@0: cal.setTimeInMillis(lastAttempt); michael@0: return cal; michael@0: } michael@0: michael@0: private void setLastAttemptDate() { michael@0: SharedPreferences.Editor editor = mPrefs.edit(); michael@0: editor.putLong(KEY_LAST_ATTEMPT_DATE, System.currentTimeMillis()); michael@0: editor.commit(); michael@0: } michael@0: michael@0: private int getAutoDownloadPolicy() { michael@0: return mPrefs.getInt(KEY_AUTODOWNLOAD_POLICY, UpdateServiceHelper.AUTODOWNLOAD_WIFI); michael@0: } michael@0: michael@0: private void setAutoDownloadPolicy(int policy) { michael@0: SharedPreferences.Editor editor = mPrefs.edit(); michael@0: editor.putInt(KEY_AUTODOWNLOAD_POLICY, policy); michael@0: editor.commit(); michael@0: } michael@0: michael@0: private void saveUpdateInfo(UpdateInfo info, File downloaded) { michael@0: SharedPreferences.Editor editor = mPrefs.edit(); michael@0: editor.putString(KEY_LAST_BUILDID, info.buildID); michael@0: editor.putString(KEY_LAST_HASH_FUNCTION, info.hashFunction); michael@0: editor.putString(KEY_LAST_HASH_VALUE, info.hashValue); michael@0: editor.putString(KEY_LAST_FILE_NAME, downloaded.toString()); michael@0: editor.commit(); michael@0: } michael@0: michael@0: private class UpdateInfo { michael@0: public URL url; michael@0: public String buildID; michael@0: public String hashFunction; michael@0: public String hashValue; michael@0: public int size; michael@0: michael@0: private boolean isNonEmpty(String s) { michael@0: return s != null && s.length() > 0; michael@0: } michael@0: michael@0: public boolean isValid() { michael@0: return url != null && isNonEmpty(buildID) && michael@0: isNonEmpty(hashFunction) && isNonEmpty(hashValue) && size > 0; michael@0: } michael@0: michael@0: @Override michael@0: public String toString() { michael@0: return "url = " + url + ", buildID = " + buildID + ", hashFunction = " + hashFunction + ", hashValue = " + hashValue + ", size = " + size; michael@0: } michael@0: } michael@0: }