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