blob: 14fb363f071b3111f036477391aaea9a23be2ad4 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.updater;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.provider.Checkin;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.TextView;
import java.io.File;
/**
* A simple Activity that prompts the user for whether they want to update.
*/
public class PesterActivity extends Activity {
/** Class identifier for logging. */
private static final String TAG = "PesterActivity";
/** Alarm manager for setting prompt timeouts. */
private AlarmManager mAlarmManager;
/** Intent used to reboot and install an OTA package. */
private PendingIntent mInstallIntent;
/** Currently active prompt, if displayed. */
private static PesterActivity mActivityStarted;
/** @return whether a call is in progress (or the phone is ringing) */
private boolean inPhoneCall() {
TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
return (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE);
}
/**
* @param intent to get first prompt time and interval schedule from
* @param currentTime (in elapsedRealtime) to look for a prompt after
* @return elapsedRealtime of the next prompt, or 0 if there are no more
*/
private static long getNextPromptTime(Intent intent, long currentTime) {
int[] minutes = new int[] { 30 }; // Default: every 30 minutes forever
boolean forever = true;
String intervals = intent.getStringExtra("promptMinutes");
if (intervals != null) {
if (intervals.endsWith(",...")) {
forever = true;
intervals = intervals.substring(0, intervals.length() - 4);
} else {
forever = false;
}
try {
String[] split = intervals.split(",");
int[] parsed = new int[split.length];
for (int i = 0; i < split.length; i++) {
parsed[i] = Integer.parseInt(split[i]);
}
minutes = parsed; // Only after they've all been converted.
} catch (NumberFormatException e) {
Log.e(TAG, "Invalid prompt intervals: " + intervals, e);
forever = false;
// Use the defaults in this case.
}
}
// Run through the prompts until we find the next one after now.
long promptTime = intent.getLongExtra("firstPrompt", currentTime);
for (int i = 0; promptTime <= currentTime && i < minutes.length; i++) {
promptTime += minutes[i] * 60 * 1000;
}
// If the prompts go forever, just keep repeating the last interval.
while (forever && promptTime <= currentTime) {
if (minutes.length == 0 || minutes[minutes.length - 1] <= 0) {
Log.e(TAG, "Invalid infinite interval: " + intervals);
break;
}
promptTime += minutes[minutes.length - 1] * 60 * 1000;
}
return (promptTime <= currentTime) ? 0 : promptTime;
}
/**
* @param intent to get prompt delay settings from
* @return milliseconds to wait, 0 to wait forever
*/
private static long getPromptTimeout(Intent intent) {
long value = 0; // Default: wait forever (no timeout)
String seconds = intent.getStringExtra("timeoutSeconds");
try {
if (seconds != null) value = Long.parseLong(seconds) * 1000;
} catch (NumberFormatException e) {
Log.e(TAG, "Invalid timeout delay: " + seconds, e);
}
return value;
}
/** Install the update immediately and dismiss the dialog. */
private void installUpdate() {
if (mInstallIntent != null) {
try {
mInstallIntent.send();
} catch (PendingIntent.CanceledException e) {
throw new RuntimeException(e); // Should not happen!
}
}
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
requestWindowFeature(Window.FEATURE_LEFT_ICON);
setContentView(R.layout.pester);
getWindow().setFeatureDrawableResource(
Window.FEATURE_LEFT_ICON, android.R.drawable.ic_dialog_alert);
// Install now -> broadcast the intent to install!
findViewById(R.id.now).setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
Log.i(TAG, "OTA update accepted by user!");
Checkin.logEvent(getContentResolver(),
Checkin.Events.Tag.FOTA_PROMPT_ACCEPT, null);
installUpdate();
}
});
// Install later -> just go away, the alarm will trigger again.
findViewById(R.id.later).setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
Log.i(TAG, "OTA update dismissed by user");
finish();
}
});
}
@Override
public void onStart() {
super.onStart();
mActivityStarted = this;
onNewIntent(getIntent());
if (isFinishing()) return; // onNewIntent() can do this
if (inPhoneCall()) {
Log.i(TAG, "OTA update prompt postponed by phone call");
Checkin.logEvent(getContentResolver(),
Checkin.Events.Tag.FOTA_PROMPT_SKIPPED, null);
finish();
return;
}
Checkin.logEvent(getContentResolver(),
Checkin.Events.Tag.FOTA_PROMPT, null);
}
@Override
public void onResume() {
super.onResume();
// The server can override the default text in the resource.
CharSequence message = getIntent().getStringExtra("promptMessage");
if (message == null) message = getText(R.string.message);
((TextView) findViewById(R.id.message)).setText(message);
if (mInstallIntent != null) {
long delay = getPromptTimeout(getIntent());
if (delay > 0) {
Log.i(TAG, "OTA prompt timeout in " + delay / 1000 + " sec");
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + delay, mInstallIntent);
}
}
}
@Override
public void onPause() {
if (mInstallIntent != null) {
mAlarmManager.cancel(mInstallIntent);
if (isFinishing()) {
Log.i(TAG, "OTA update prompt dismissed");
Checkin.logEvent(getContentResolver(),
Checkin.Events.Tag.FOTA_PROMPT_REJECT, null);
}
}
super.onPause();
}
@Override
public void onStop() {
if (mActivityStarted == this) mActivityStarted = null;
super.onStop();
}
@Override
public void onNewIntent(Intent intent) {
// This activity is launched with SINGLE_TOP_LAUNCH, so this happens
// if the file being downloaded changes, or if the next alarm
// goes off while the prompt is still displayed. We also call
// this function directly from onStart() so all Intents come here.
// Regardless, this is only called with the activity paused.
super.onNewIntent(intent);
setIntent(intent);
// This Extra must always be set by the caller.
File update = new File(intent.getStringExtra("updateFile"));
if (!update.exists()) {
// The update has gone away, maybe replaced by something newer?
Log.i(TAG, "OTA update no longer exists: " + update);
finish();
return;
}
Log.i(TAG, "OTA update available: " + update);
Uri uri = Uri.fromFile(update);
Intent install = new Intent("android.server.checkin.FOTA_INSTALL", uri);
mInstallIntent = PendingIntent.getBroadcast(this, 0, install, 0);
long now = SystemClock.elapsedRealtime();
long nextPrompt = getNextPromptTime(intent, now);
if (nextPrompt == 0) {
Log.i(TAG, "Installing overdue OTA update without prompting");
installUpdate();
return;
}
// Use FLAG_CANCEL_CURRENT because the Intent differs only in extras.
Log.i(TAG, "Next OTA prompt in " + (nextPrompt - now) / 1000 + " sec");
mAlarmManager.set(
AlarmManager.ELAPSED_REALTIME_WAKEUP, nextPrompt,
PendingIntent.getActivity(this, 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT));
}
}