Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(59)

Side by Side Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/omaha/OmahaClient.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.omaha;
6
7 import android.app.AlarmManager;
8 import android.app.IntentService;
9 import android.app.PendingIntent;
10 import android.content.Context;
11 import android.content.Intent;
12 import android.content.SharedPreferences;
13 import android.content.pm.ApplicationInfo;
14 import android.os.Looper;
15 import android.util.Log;
16
17 import org.chromium.base.ApiCompatibilityUtils;
18 import org.chromium.base.ApplicationStatus;
19 import org.chromium.base.VisibleForTesting;
20 import org.chromium.base.annotations.SuppressFBWarnings;
21 import org.chromium.chrome.browser.ChromeMobileApplication;
22
23 import java.io.BufferedOutputStream;
24 import java.io.BufferedReader;
25 import java.io.IOException;
26 import java.io.InputStreamReader;
27 import java.io.OutputStream;
28 import java.io.OutputStreamWriter;
29 import java.net.HttpURLConnection;
30 import java.net.MalformedURLException;
31 import java.net.URL;
32 import java.util.Map;
33 import java.util.UUID;
34
35 /**
36 * Keeps tabs on the current state of Chrome, tracking if and when a request sho uld be sent to the
37 * Omaha Server.
38 *
39 * A hook in ChromeActivity's doDeferredResume() initializes the service. Furth er attempts to
40 * reschedule events will be scheduled by the class itself.
41 *
42 * Each request to the server will perform an update check and ping the server.
43 * We use a repeating alarm to schedule the XML requests to be generated 5 hours apart.
44 * If Chrome isn't running when the alarm is fired, the request generation will be stalled until
45 * the next time Chrome runs.
46 *
47 * mevissen suggested being conservative with our timers for sending requests.
48 * POST attempts that fail to be acknowledged by the server are re-attempted, wi th at least
49 * one hour between each attempt.
50 *
51 * Status is saved directly to the the disk after every operation. Unit tests t esting the code
52 * paths without using Intents may need to call restoreState() manually as it is not automatically
53 * handled in onCreate().
54 *
55 * Implementation notes:
56 * http://docs.google.com/a/google.com/document/d/1scTCovqASf5ktkOeVj8wFRkWTCeDY w2LrOBNn05CDB0/edit
57 */
58 public class OmahaClient extends IntentService {
59 private static final String TAG = "OmahaClient";
60
61 // Intent actions.
62 private static final String ACTION_INITIALIZE =
63 "org.chromium.chrome.browser.omaha.ACTION_INITIALIZE";
64 private static final String ACTION_REGISTER_REQUEST =
65 "org.chromium.chrome.browser.omaha.ACTION_REGISTER_REQUEST";
66 private static final String ACTION_POST_REQUEST =
67 "org.chromium.chrome.browser.omaha.ACTION_POST_REQUEST";
68
69 // Strings for extras.
70 private static final String EXTRA_FORCE_ACTION = "forceAction";
71
72 // Delays between events.
73 private static final long MS_PER_HOUR = 3600000;
74 private static final long MS_POST_BASE_DELAY = MS_PER_HOUR;
75 private static final long MS_POST_MAX_DELAY = 5 * MS_PER_HOUR;
76 private static final long MS_BETWEEN_REQUESTS = 5 * MS_PER_HOUR;
77 private static final int MS_CONNECTION_TIMEOUT = 60000;
78
79 // Flags for retrieving the OmahaClient's state after it's written to disk.
80 // The PREF_PACKAGE doesn't match the current OmahaClient package for histor ical reasons.
81 @VisibleForTesting
82 static final String PREF_PACKAGE = "com.google.android.apps.chrome.omaha";
83 @VisibleForTesting
84 static final String PREF_PERSISTED_REQUEST_ID = "persistedRequestID";
85 @VisibleForTesting
86 static final String PREF_TIMESTAMP_OF_REQUEST = "timestampOfRequest";
87 @VisibleForTesting
88 static final String PREF_INSTALL_SOURCE = "installSource";
89 private static final String PREF_SEND_INSTALL_EVENT = "sendInstallEvent";
90 private static final String PREF_TIMESTAMP_OF_INSTALL = "timestampOfInstall" ;
91 private static final String PREF_TIMESTAMP_FOR_NEXT_POST_ATTEMPT =
92 "timestampForNextPostAttempt";
93 private static final String PREF_TIMESTAMP_FOR_NEW_REQUEST = "timestampForNe wRequest";
94
95 // Strings indicating how the Chrome APK arrived on the user's device. These values MUST NOT
96 // be changed without updating the corresponding Omaha server strings.
97 static final String INSTALL_SOURCE_SYSTEM = "system_image";
98 static final String INSTALL_SOURCE_ORGANIC = "organic";
99
100 // Lock object used to synchronize all calls that modify or read sIsFreshIns tallOrDataCleared.
101 private static final Object sIsFreshInstallLock = new Object();
102
103 @VisibleForTesting
104 static final String PREF_LATEST_VERSION = "latestVersion";
105 @VisibleForTesting
106 static final String PREF_MARKET_URL = "marketURL";
107
108 private static final long INVALID_TIMESTAMP = -1;
109 @VisibleForTesting
110 static final String INVALID_REQUEST_ID = "invalid";
111
112 // Static fields
113 private static boolean sEnableCommunication = true;
114 private static boolean sEnableUpdateDetection = true;
115 private static VersionNumberGetter sVersionNumberGetter = null;
116 private static MarketURLGetter sMarketURLGetter = null;
117 private static Boolean sIsFreshInstallOrDataCleared = null;
118
119 // Member fields not persisted to disk.
120 private boolean mStateHasBeenRestored;
121 private Context mApplicationContext;
122 private ExponentialBackoffScheduler mBackoffScheduler;
123 private RequestGenerator mGenerator;
124
125 // State saved written to and read from disk.
126 private RequestData mCurrentRequest;
127 private long mTimestampOfInstall;
128 private long mTimestampForNextPostAttempt;
129 private long mTimestampForNewRequest;
130 private String mLatestVersion;
131 private String mMarketURL;
132 private String mInstallSource;
133 protected boolean mSendInstallEvent;
134
135 public OmahaClient() {
136 super(TAG);
137 setIntentRedelivery(true);
138 }
139
140 @Override
141 public void onCreate() {
142 super.onCreate();
143 mApplicationContext = getApplicationContext();
144 mBackoffScheduler = createBackoffScheduler(PREF_PACKAGE, mApplicationCon text,
145 MS_POST_BASE_DELAY, MS_POST_MAX_DELAY);
146 mGenerator = createRequestGenerator(mApplicationContext);
147 }
148
149 /**
150 * Sets whether Chrome should be communicating with the Omaha server.
151 * The alternative to using a static field within OmahaClient is using a mem ber variable in
152 * the ChromeTabbedActivity. The problem is that it is difficult to set the variable before
153 * ChromeTabbedActivity is started.
154 */
155 @VisibleForTesting
156 public static void setEnableCommunication(boolean state) {
157 sEnableCommunication = state;
158 }
159
160 /**
161 * If false, OmahaClient will never report that a newer version is available .
162 */
163 @VisibleForTesting
164 public static void setEnableUpdateDetection(boolean state) {
165 sEnableUpdateDetection = state;
166 }
167
168 @VisibleForTesting
169 long getTimestampForNextPostAttempt() {
170 return mTimestampForNextPostAttempt;
171 }
172
173 @VisibleForTesting
174 long getTimestampForNewRequest() {
175 return mTimestampForNewRequest;
176 }
177
178 @VisibleForTesting
179 int getCumulativeFailedAttempts() {
180 return mBackoffScheduler.getNumFailedAttempts();
181 }
182
183 /**
184 * Creates the scheduler used to space out POST attempts.
185 */
186 @VisibleForTesting
187 ExponentialBackoffScheduler createBackoffScheduler(String prefPackage, Conte xt context,
188 long base, long max) {
189 return new ExponentialBackoffScheduler(prefPackage, context, base, max);
190 }
191
192 /**
193 * Creates the request generator used to create Omaha XML.
194 */
195 @VisibleForTesting
196 RequestGenerator createRequestGenerator(Context context) {
197 return ((ChromeMobileApplication) getApplicationContext()).createOmahaRe questGenerator();
198 }
199
200 /**
201 * Handles an action on a thread separate from the UI thread.
202 * @param intent Intent fired by some part of Chrome.
203 */
204 @Override
205 public void onHandleIntent(Intent intent) {
206 assert Looper.myLooper() != Looper.getMainLooper();
207
208 if (!sEnableCommunication) {
209 Log.v(TAG, "Disabled. Ignoring intent.");
210 return;
211 }
212
213 if (mGenerator == null) {
214 Log.e(TAG, "No request generator set. Ignoring intent.");
215 return;
216 }
217
218 if (!mStateHasBeenRestored) {
219 restoreState();
220 }
221
222 if (ACTION_INITIALIZE.equals(intent.getAction())) {
223 handleInitialize();
224 } else if (ACTION_REGISTER_REQUEST.equals(intent.getAction())) {
225 handleRegisterRequest(intent);
226 } else if (ACTION_POST_REQUEST.equals(intent.getAction())) {
227 handlePostRequestIntent(intent);
228 } else {
229 Log.e(TAG, "Got unknown action from intent: " + intent.getAction());
230 }
231 }
232
233 public static Intent createInitializeIntent(Context context) {
234 Intent intent = new Intent(context, OmahaClient.class);
235 intent.setAction(ACTION_INITIALIZE);
236 return intent;
237 }
238
239 /**
240 * Start a recurring alarm to fire request generation intents.
241 */
242 private void handleInitialize() {
243 scheduleRepeatingAlarm();
244
245 // If a request exists, fire a POST intent to restart its timer.
246 if (hasRequest()) {
247 Intent postIntent = createPostRequestIntent(mApplicationContext, fal se);
248 startService(postIntent);
249 }
250 }
251
252 public static Intent createRegisterRequestIntent(Context context, boolean fo rce) {
253 Intent intent = new Intent(context, OmahaClient.class);
254 intent.setAction(ACTION_REGISTER_REQUEST);
255 intent.putExtra(EXTRA_FORCE_ACTION, force);
256 return intent;
257 }
258
259 /**
260 * Determines if a new request should be generated. New requests are only g enerated if enough
261 * time has passed between now and the last time a request was generated.
262 */
263 private void handleRegisterRequest(Intent intent) {
264 boolean force = intent.getBooleanExtra(EXTRA_FORCE_ACTION, false);
265 if (!isChromeBeingUsed() && !force) {
266 cancelRepeatingAlarm();
267 return;
268 }
269
270 // If the current request is too old, generate a new one.
271 long currentTimestamp = mBackoffScheduler.getCurrentTime();
272 boolean isTooOld = hasRequest()
273 && mCurrentRequest.getAgeInMilliseconds(currentTimestamp) >= MS_ BETWEEN_REQUESTS;
274 boolean isOverdue = !hasRequest() && currentTimestamp >= mTimestampForNe wRequest;
275 if (isTooOld || isOverdue || force) {
276 registerNewRequest(currentTimestamp);
277 }
278
279 // Create an intent to send the request. If we're forcing a registratio n, force the POST,
280 // as well.
281 if (hasRequest()) {
282 Intent postIntent = createPostRequestIntent(mApplicationContext, for ce);
283 startService(postIntent);
284 }
285 }
286
287 public static Intent createPostRequestIntent(Context context, boolean force) {
288 Intent intent = new Intent(context, OmahaClient.class);
289 intent.setAction(ACTION_POST_REQUEST);
290 intent.putExtra(EXTRA_FORCE_ACTION, force);
291 return intent;
292 }
293
294 /**
295 * Sends the request it is holding.
296 */
297 @VisibleForTesting
298 private void handlePostRequestIntent(Intent intent) {
299 if (!hasRequest()) {
300 return;
301 }
302
303 boolean force = intent.getBooleanExtra(EXTRA_FORCE_ACTION, false);
304
305 // If enough time has passed since the last attempt, try sending a reque st.
306 long currentTimestamp = mBackoffScheduler.getCurrentTime();
307 if (currentTimestamp >= mTimestampForNextPostAttempt || force) {
308 // All requests made during the same session should have the same ID .
309 String sessionID = generateRandomUUID();
310 boolean sendingInstallRequest = mSendInstallEvent;
311 boolean succeeded = generateAndPostRequest(currentTimestamp, session ID);
312
313 if (succeeded && sendingInstallRequest) {
314 // Only the first request ever generated should contain an insta ll event.
315 mSendInstallEvent = false;
316
317 // Create and immediately send another request for a ping and up date check.
318 registerNewRequest(currentTimestamp);
319 succeeded = generateAndPostRequest(currentTimestamp, sessionID);
320 }
321
322 if (force) {
323 if (succeeded) {
324 Log.v(TAG, "Requests successfully sent to Omaha server.");
325 } else {
326 Log.e(TAG, "Requests failed to reach Omaha server.");
327 }
328 }
329 } else {
330 // Set an alarm to POST at the proper time. Previous alarms are des troyed.
331 Intent postIntent = createPostRequestIntent(mApplicationContext, fal se);
332 mBackoffScheduler.createAlarm(postIntent, mTimestampForNextPostAttem pt);
333 }
334
335 // Write everything back out again to save our state.
336 saveState();
337 }
338
339 private boolean generateAndPostRequest(long currentTimestamp, String session ID) {
340 try {
341 // Generate the XML for the current request.
342 long installAgeInDays = RequestGenerator.installAge(currentTimestamp ,
343 mTimestampOfInstall, mCurrentRequest.isSendInstallEvent());
344 String version = getVersionNumberGetter().getCurrentlyUsedVersion(mA pplicationContext);
345 String xml =
346 mGenerator.generateXML(sessionID, version, installAgeInDays, mCurrentRequest);
347
348 // Send the request to the server & wait for a response.
349 String response = postRequest(currentTimestamp, xml);
350 parseServerResponse(response);
351
352 // If we've gotten this far, we've successfully sent a request.
353 mCurrentRequest = null;
354 mTimestampForNextPostAttempt = currentTimestamp + MS_POST_BASE_DELAY ;
355 mBackoffScheduler.resetFailedAttempts();
356 Log.i(TAG, "Request to Server Successful. Timestamp for next request :"
357 + String.valueOf(mTimestampForNextPostAttempt));
358
359 return true;
360 } catch (RequestFailureException e) {
361 // Set the alarm to try again later.
362 Log.e(TAG, "Failed to contact server: ", e);
363 Intent postIntent = createPostRequestIntent(mApplicationContext, fal se);
364 mTimestampForNextPostAttempt = mBackoffScheduler.createAlarm(postInt ent);
365 mBackoffScheduler.increaseFailedAttempts();
366 return false;
367 }
368 }
369
370 /**
371 * Sets a repeating alarm that fires request registration Intents.
372 * Setting the alarm overwrites whatever alarm is already there, and rebooti ng
373 * clears whatever alarms are currently set.
374 */
375 private void scheduleRepeatingAlarm() {
376 Intent registerIntent = createRegisterRequestIntent(mApplicationContext, false);
377 PendingIntent pIntent =
378 PendingIntent.getService(mApplicationContext, 0, registerIntent, 0);
379 AlarmManager am =
380 (AlarmManager) mApplicationContext.getSystemService(Context.ALAR M_SERVICE);
381 setAlarm(am, pIntent, AlarmManager.RTC, mTimestampForNewRequest);
382 }
383
384 /**
385 * Sets up a timer to fire after each interval.
386 * Override to prevent a real alarm from being set.
387 */
388 @VisibleForTesting
389 protected void setAlarm(AlarmManager am, PendingIntent operation, int alarmT ype,
390 long triggerAtTime) {
391 am.setRepeating(AlarmManager.RTC, triggerAtTime, MS_BETWEEN_REQUESTS, op eration);
392 }
393
394 /**
395 * Cancels the alarm that launches this service. It will be replaced when C hrome next resumes.
396 */
397 private void cancelRepeatingAlarm() {
398 Intent requestIntent = createRegisterRequestIntent(mApplicationContext, false);
399 PendingIntent pendingIntent = PendingIntent.getService(mApplicationConte xt, 0,
400 requestIntent, PendingIntent.FLAG_NO_CREATE);
401 // Setting FLAG_NO_CREATE forces Android to return an already existing P endingIntent.
402 // Here it would be the one that was used to create the existing alarm ( if it exists).
403 // If the pendingIntent is null, it is likely that no alarm was created.
404 if (pendingIntent != null) {
405 AlarmManager am =
406 (AlarmManager) mApplicationContext.getSystemService(Context. ALARM_SERVICE);
407 am.cancel(pendingIntent);
408 pendingIntent.cancel();
409 }
410 }
411
412 /**
413 * Determine whether or not Chrome is currently being used actively.
414 */
415 @VisibleForTesting
416 protected boolean isChromeBeingUsed() {
417 boolean isChromeVisible = ApplicationStatus.hasVisibleActivities();
418 boolean isScreenOn = ApiCompatibilityUtils.isInteractive(mApplicationCon text);
419 return isChromeVisible && isScreenOn;
420 }
421
422 /**
423 * Registers a new request with the current timestamp. Internal timestamps are reset to start
424 * fresh.
425 * @param currentTimestamp Current time.
426 */
427 @VisibleForTesting
428 void registerNewRequest(long currentTimestamp) {
429 mCurrentRequest = createRequestData(currentTimestamp, null);
430 mBackoffScheduler.resetFailedAttempts();
431 mTimestampForNextPostAttempt = currentTimestamp;
432
433 // Tentatively set the timestamp for a new request. This will be update d when the server
434 // is successfully contacted.
435 mTimestampForNewRequest = currentTimestamp + MS_BETWEEN_REQUESTS;
436 scheduleRepeatingAlarm();
437
438 saveState();
439 }
440
441 private RequestData createRequestData(long currentTimestamp, String persiste dID) {
442 // If we're sending a persisted event, keep trying to send the same requ est ID.
443 String requestID;
444 if (persistedID == null || INVALID_REQUEST_ID.equals(persistedID)) {
445 requestID = generateRandomUUID();
446 } else {
447 requestID = persistedID;
448 }
449 return new RequestData(mSendInstallEvent, currentTimestamp, requestID, m InstallSource);
450 }
451
452 @VisibleForTesting
453 boolean hasRequest() {
454 return mCurrentRequest != null;
455 }
456
457 /**
458 * Posts the request to the Omaha server.
459 * @return the XML response as a String.
460 * @throws RequestFailureException if the request fails.
461 */
462 @VisibleForTesting
463 String postRequest(long timestamp, String xml) throws RequestFailureExceptio n {
464 String response = null;
465
466 HttpURLConnection urlConnection = null;
467 try {
468 urlConnection = createConnection();
469 setUpPostRequest(timestamp, urlConnection, xml);
470 sendRequestToServer(urlConnection, xml);
471 response = readResponseFromServer(urlConnection);
472 } finally {
473 if (urlConnection != null) {
474 urlConnection.disconnect();
475 }
476 }
477
478 return response;
479 }
480
481 /**
482 * Parse the server's response and confirm that we received an OK response.
483 */
484 private void parseServerResponse(String response) throws RequestFailureExcep tion {
485 String appId = mGenerator.getAppId();
486 boolean sentPingAndUpdate = !mSendInstallEvent;
487 ResponseParser parser =
488 new ResponseParser(appId, mSendInstallEvent, sentPingAndUpdate, sentPingAndUpdate);
489 parser.parseResponse(response);
490 mTimestampForNewRequest = mBackoffScheduler.getCurrentTime() + MS_BETWEE N_REQUESTS;
491 mLatestVersion = parser.getNewVersion();
492 mMarketURL = parser.getURL();
493 scheduleRepeatingAlarm();
494 }
495
496 /**
497 * Returns a HttpURLConnection to the server.
498 */
499 @VisibleForTesting
500 protected HttpURLConnection createConnection() throws RequestFailureExceptio n {
501 try {
502 URL url = new URL(mGenerator.getServerUrl());
503 HttpURLConnection connection = (HttpURLConnection) url.openConnectio n();
504 connection.setConnectTimeout(MS_CONNECTION_TIMEOUT);
505 connection.setReadTimeout(MS_CONNECTION_TIMEOUT);
506 return connection;
507 } catch (MalformedURLException e) {
508 throw new RequestFailureException("Caught a malformed URL exception. ", e);
509 } catch (IOException e) {
510 throw new RequestFailureException("Failed to open connection to URL" , e);
511 }
512 }
513
514 /**
515 * Prepares the HTTP header.
516 */
517 private void setUpPostRequest(long timestamp, HttpURLConnection urlConnectio n, String xml)
518 throws RequestFailureException {
519 try {
520 urlConnection.setDoOutput(true);
521 urlConnection.setFixedLengthStreamingMode(xml.getBytes().length);
522 if (mSendInstallEvent && getCumulativeFailedAttempts() > 0) {
523 String age = Long.toString(mCurrentRequest.getAgeInSeconds(times tamp));
524 urlConnection.addRequestProperty("X-RequestAge", age);
525 }
526 } catch (IllegalAccessError e) {
527 throw new RequestFailureException("Caught an IllegalAccessError:", e );
528 } catch (IllegalArgumentException e) {
529 throw new RequestFailureException("Caught an IllegalArgumentExceptio n:", e);
530 } catch (IllegalStateException e) {
531 throw new RequestFailureException("Caught an IllegalStateException:" , e);
532 }
533 }
534
535 /**
536 * Sends the request to the server.
537 */
538 private void sendRequestToServer(HttpURLConnection urlConnection, String xml )
539 throws RequestFailureException {
540 try {
541 OutputStream out = new BufferedOutputStream(urlConnection.getOutputS tream());
542 OutputStreamWriter writer = new OutputStreamWriter(out);
543 writer.write(xml, 0, xml.length());
544 writer.close();
545 checkServerResponseCode(urlConnection);
546 } catch (IOException e) {
547 throw new RequestFailureException("Failed to write request to server : ", e);
548 }
549 }
550
551 /**
552 * Reads the response from the Omaha Server.
553 */
554 private String readResponseFromServer(HttpURLConnection urlConnection)
555 throws RequestFailureException {
556 try {
557 InputStreamReader reader = new InputStreamReader(urlConnection.getIn putStream());
558 BufferedReader in = new BufferedReader(reader);
559 try {
560 StringBuilder response = new StringBuilder();
561 for (String line = in.readLine(); line != null; line = in.readLi ne()) {
562 response.append(line);
563 }
564 checkServerResponseCode(urlConnection);
565 return response.toString();
566 } finally {
567 in.close();
568 }
569 } catch (IOException e) {
570 throw new RequestFailureException("Failed when reading response from server: ", e);
571 }
572 }
573
574 /**
575 * Confirms that the Omaha server sent back an "OK" code.
576 */
577 private void checkServerResponseCode(HttpURLConnection urlConnection)
578 throws RequestFailureException {
579 try {
580 if (urlConnection.getResponseCode() != 200) {
581 throw new RequestFailureException(
582 "Received " + urlConnection.getResponseCode()
583 + " code instead of 200 (OK) from the server. Aborting. ");
584 }
585 } catch (IOException e) {
586 throw new RequestFailureException("Failed to read response code from server: ", e);
587 }
588 }
589
590 /**
591 * Checks if we know about a newer version available than the one we're usin g. This does not
592 * actually fire any requests over to the server; it just checks the version we stored the last
593 * time we talked to the Omaha server.
594 *
595 * NOTE: This function incurs I/O, so don't use it on the main thread.
596 */
597 public static boolean isNewerVersionAvailable(Context applicationContext) {
598 assert Looper.myLooper() != Looper.getMainLooper();
599
600 // This may be explicitly enabled for some channels and for unit tests.
601 if (!sEnableUpdateDetection) {
602 return false;
603 }
604
605 // If the market link is bad, don't show an update to avoid frustrating users trying to
606 // hit the "Update" button.
607 if ("".equals(getMarketURL(applicationContext))) {
608 return false;
609 }
610
611 // Compare version numbers.
612 VersionNumberGetter getter = getVersionNumberGetter();
613 String currentStr = getter.getCurrentlyUsedVersion(applicationContext);
614 String latestStr =
615 getter.getLatestKnownVersion(applicationContext, PREF_PACKAGE, P REF_LATEST_VERSION);
616
617 VersionNumber currentVersionNumber = VersionNumber.fromString(currentStr );
618 VersionNumber latestVersionNumber = VersionNumber.fromString(latestStr);
619
620 if (currentVersionNumber == null || latestVersionNumber == null) {
621 return false;
622 }
623
624 return currentVersionNumber.isSmallerThan(latestVersionNumber);
625 }
626
627 /**
628 * Determine how the Chrome APK arrived on the device.
629 * @param context Context to pull resources from.
630 * @return A String indicating the install source.
631 */
632 String determineInstallSource(Context context) {
633 boolean isInSystemImage = (getApplicationFlags() & ApplicationInfo.FLAG_ SYSTEM) != 0;
634 return isInSystemImage ? INSTALL_SOURCE_SYSTEM : INSTALL_SOURCE_ORGANIC;
635 }
636
637 /**
638 * Returns the Application's flags, used to determine if Chrome was installe d as part of the
639 * system image.
640 * @return The Application's flags.
641 */
642 @VisibleForTesting
643 public int getApplicationFlags() {
644 return mApplicationContext.getApplicationInfo().flags;
645 }
646
647 /**
648 * Reads the data back from the file it was saved to. Uses SharedPreference s to handle I/O.
649 * Sanity checks are performed on the timestamps to guard against clock chan ging.
650 */
651 @VisibleForTesting
652 void restoreState() {
653 boolean mustRewriteState = false;
654 SharedPreferences preferences =
655 mApplicationContext.getSharedPreferences(PREF_PACKAGE, Context.M ODE_PRIVATE);
656 Map<String, ?> items = preferences.getAll();
657
658 // Read out the recorded data.
659 long currentTime = mBackoffScheduler.getCurrentTime();
660 mTimestampForNewRequest =
661 getLongFromMap(items, PREF_TIMESTAMP_FOR_NEW_REQUEST, currentTim e);
662 mTimestampForNextPostAttempt =
663 getLongFromMap(items, PREF_TIMESTAMP_FOR_NEXT_POST_ATTEMPT, curr entTime);
664
665 long requestTimestamp = getLongFromMap(items, PREF_TIMESTAMP_OF_REQUEST, INVALID_TIMESTAMP);
666
667 // If the preference doesn't exist, it's likely that we haven't sent an install event.
668 mSendInstallEvent = getBooleanFromMap(items, PREF_SEND_INSTALL_EVENT, tr ue);
669
670 // Restore the install source.
671 String defaultInstallSource = determineInstallSource(mApplicationContext );
672 mInstallSource = getStringFromMap(items, PREF_INSTALL_SOURCE, defaultIns tallSource);
673
674 // If we're not sending an install event, don't bother restoring the req uest ID:
675 // the server does not expect to have persisted request IDs for pings or update checks.
676 String persistedRequestId = mSendInstallEvent
677 ? getStringFromMap(items, PREF_PERSISTED_REQUEST_ID, INVALID_REQ UEST_ID)
678 : INVALID_REQUEST_ID;
679
680 mCurrentRequest = requestTimestamp == INVALID_TIMESTAMP
681 ? null : createRequestData(requestTimestamp, persistedRequestId) ;
682
683 mLatestVersion = getStringFromMap(items, PREF_LATEST_VERSION, "");
684 mMarketURL = getStringFromMap(items, PREF_MARKET_URL, "");
685
686 // If we don't have a timestamp for when we installed Chrome, then set i t to now.
687 mTimestampOfInstall = getLongFromMap(items, PREF_TIMESTAMP_OF_INSTALL, c urrentTime);
688
689 // Confirm that the timestamp for the next request is less than the base delay.
690 long delayToNewRequest = mTimestampForNewRequest - currentTime;
691 if (delayToNewRequest > MS_BETWEEN_REQUESTS) {
692 Log.w(TAG, "Delay to next request (" + delayToNewRequest
693 + ") is longer than expected. Resetting to now.");
694 mTimestampForNewRequest = currentTime;
695 mustRewriteState = true;
696 }
697
698 // Confirm that the timestamp for the next POST is less than the current delay.
699 long delayToNextPost = mTimestampForNextPostAttempt - currentTime;
700 if (delayToNextPost > mBackoffScheduler.getGeneratedDelay()) {
701 Log.w(TAG, "Delay to next post attempt (" + delayToNextPost
702 + ") is greater than expected (" + mBackoffScheduler.getGene ratedDelay()
703 + "). Resetting to now.");
704 mTimestampForNextPostAttempt = currentTime;
705 mustRewriteState = true;
706 }
707
708 if (mustRewriteState) {
709 saveState();
710 }
711
712 mStateHasBeenRestored = true;
713 }
714
715 /**
716 * Writes out the current state to a file.
717 */
718 private void saveState() {
719 SharedPreferences prefs =
720 mApplicationContext.getSharedPreferences(PREF_PACKAGE, Context.M ODE_PRIVATE);
721 SharedPreferences.Editor editor = prefs.edit();
722 editor.putBoolean(PREF_SEND_INSTALL_EVENT, mSendInstallEvent);
723 setIsFreshInstallOrDataHasBeenCleared(mApplicationContext);
724 editor.putLong(PREF_TIMESTAMP_OF_INSTALL, mTimestampOfInstall);
725 editor.putLong(PREF_TIMESTAMP_FOR_NEXT_POST_ATTEMPT, mTimestampForNextPo stAttempt);
726 editor.putLong(PREF_TIMESTAMP_FOR_NEW_REQUEST, mTimestampForNewRequest);
727 editor.putLong(PREF_TIMESTAMP_OF_REQUEST,
728 hasRequest() ? mCurrentRequest.getCreationTimestamp() : INVALID_ TIMESTAMP);
729 editor.putString(PREF_PERSISTED_REQUEST_ID,
730 hasRequest() ? mCurrentRequest.getRequestID() : INVALID_REQUEST_ ID);
731 editor.putString(PREF_LATEST_VERSION, mLatestVersion == null ? "" : mLat estVersion);
732 editor.putString(PREF_MARKET_URL, mMarketURL == null ? "" : mMarketURL);
733
734 if (mInstallSource != null) editor.putString(PREF_INSTALL_SOURCE, mInsta llSource);
735
736 editor.apply();
737 }
738
739 /**
740 * Generates a random UUID.
741 */
742 @VisibleForTesting
743 protected String generateRandomUUID() {
744 return UUID.randomUUID().toString();
745 }
746
747 /**
748 * Sets the VersionNumberGetter used to get version numbers. Set a new one to override what
749 * version numbers are returned.
750 */
751 @VisibleForTesting
752 static void setVersionNumberGetterForTests(VersionNumberGetter getter) {
753 sVersionNumberGetter = getter;
754 }
755
756 @SuppressFBWarnings("LI_LAZY_INIT_STATIC")
757 @VisibleForTesting
758 static VersionNumberGetter getVersionNumberGetter() {
759 if (sVersionNumberGetter == null) {
760 sVersionNumberGetter = new VersionNumberGetter();
761 }
762 return sVersionNumberGetter;
763 }
764
765 /**
766 * Sets the MarketURLGetter used to get version numbers. Set a new one to o verride what
767 * URL is returned.
768 */
769 @VisibleForTesting
770 static void setMarketURLGetterForTests(MarketURLGetter getter) {
771 sMarketURLGetter = getter;
772 }
773
774 /**
775 * Returns the stub used to grab the market URL for Chrome.
776 */
777 @SuppressFBWarnings("LI_LAZY_INIT_STATIC")
778 public static String getMarketURL(Context context) {
779 if (sMarketURLGetter == null) {
780 sMarketURLGetter = new MarketURLGetter();
781 }
782 return sMarketURLGetter.getMarketURL(context, PREF_PACKAGE, PREF_MARKET_ URL);
783 }
784
785 /**
786 * Pulls a long from the shared preferences map.
787 */
788 private static long getLongFromMap(final Map<String, ?> items, String key, l ong defaultValue) {
789 Long value = (Long) items.get(key);
790 return value != null ? value : defaultValue;
791 }
792
793 /**
794 * Pulls a string from the shared preferences map.
795 */
796 private static String getStringFromMap(final Map<String, ?> items, String ke y,
797 String defaultValue) {
798 String value = (String) items.get(key);
799 return value != null ? value : defaultValue;
800 }
801
802 /**
803 * Pulls a boolean from the shared preferences map.
804 */
805 private static boolean getBooleanFromMap(final Map<String, ?> items, String key,
806 boolean defaultValue) {
807 Boolean value = (Boolean) items.get(key);
808 return value != null ? value : defaultValue;
809 }
810
811 /**
812 * @return Whether it is either a fresh install or data has been cleared.
813 * PREF_TIMESTAMP_OF_INSTALL is set within the first few seconds after a fre sh install.
814 * sIsFreshInstallOrDataCleared will be set to true if PREF_TIMESTAMP_OF_INS TALL has not
815 * been previously set. Else, it will be set to false. sIsFreshInstallOrData Cleared is
816 * guarded by sLock.
817 * @param applicationContext The current application Context.
818 */
819 public static boolean isFreshInstallOrDataHasBeenCleared(Context application Context) {
820 return setIsFreshInstallOrDataHasBeenCleared(applicationContext);
821 }
822
823 private static boolean setIsFreshInstallOrDataHasBeenCleared(Context applica tionContext) {
824 synchronized (sIsFreshInstallLock) {
825 if (sIsFreshInstallOrDataCleared == null) {
826 SharedPreferences prefs = applicationContext.getSharedPreference s(
827 PREF_PACKAGE, Context.MODE_PRIVATE);
828 sIsFreshInstallOrDataCleared = (prefs.getLong(PREF_TIMESTAMP_OF_ INSTALL, -1) == -1);
829 }
830 return sIsFreshInstallOrDataCleared;
831 }
832 }
833 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698