summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk2
-rw-r--r--api/current.xml48
-rw-r--r--core/java/android/app/ActivityManagerNative.java65
-rw-r--r--core/java/android/app/ActivityThread.java135
-rw-r--r--core/java/android/app/ApplicationThreadNative.java35
-rw-r--r--core/java/android/app/BackupAgent.java (renamed from core/java/android/backup/BackupService.java)75
-rw-r--r--core/java/android/app/IActivityManager.java9
-rw-r--r--core/java/android/app/IApplicationThread.java11
-rw-r--r--core/java/android/app/IBackupAgent.aidl (renamed from core/java/android/backup/IBackupService.aidl)6
-rw-r--r--core/java/android/app/SearchDialog.java19
-rw-r--r--core/java/android/app/SearchManager.java5
-rw-r--r--core/java/android/app/SuggestionsAdapter.java25
-rw-r--r--core/java/android/backup/IBackupManager.aidl16
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java25
-rw-r--r--core/java/android/content/pm/PackageParser.java15
-rw-r--r--core/java/android/os/Build.java2
-rw-r--r--core/java/android/provider/Settings.java4
-rw-r--r--core/java/android/provider/Telephony.java31
-rw-r--r--core/java/android/speech/IRecognitionListener.aidl19
-rw-r--r--core/java/android/speech/IRecognitionService.aidl3
-rw-r--r--core/java/android/speech/RecognitionResult.aidl19
-rw-r--r--core/java/android/speech/RecognitionResult.java151
-rw-r--r--core/java/android/speech/RecognitionServiceUtil.java12
-rwxr-xr-xcore/java/android/widget/AppSecurityPermissions.java31
-rw-r--r--core/java/android/widget/TabHost.java11
-rw-r--r--core/java/android/widget/ZoomButtonsController.java27
-rw-r--r--core/java/com/google/android/util/GoogleWebContentHelper.java9
-rw-r--r--core/res/AndroidManifest.xml11
-rw-r--r--core/res/res/values/attrs_manifest.xml11
-rw-r--r--core/res/res/values/public.xml4
-rw-r--r--core/res/res/values/strings.xml5
-rw-r--r--data/etc/platform.xml4
-rw-r--r--docs/html/guide/guide_toc.cs1
-rw-r--r--docs/html/guide/practices/ui_guidelines/activity_task_design.jd1223
-rw-r--r--docs/html/guide/practices/ui_guidelines/index.jd14
-rw-r--r--docs/html/images/activity_task_design/ActivityChooser.pngbin0 -> 82669 bytes
-rw-r--r--docs/html/images/activity_task_design/ContactNew.pngbin0 -> 82669 bytes
-rw-r--r--docs/html/images/activity_task_design/ContactView.pngbin0 -> 82669 bytes
-rw-r--r--docs/html/images/activity_task_design/ContactsDialer.pngbin0 -> 82669 bytes
-rw-r--r--docs/html/images/activity_task_design/ContactsList.pngbin0 -> 82669 bytes
-rw-r--r--docs/html/images/activity_task_design/HomeTaskBasics1a.pngbin0 -> 25905 bytes
-rw-r--r--docs/html/images/activity_task_design/HomeTaskBasics1b.pngbin0 -> 26166 bytes
-rw-r--r--docs/html/images/activity_task_design/HomeTaskBasics1c.pngbin0 -> 25576 bytes
-rw-r--r--docs/html/images/activity_task_design/HomeTaskBasics1d.pngbin0 -> 45696 bytes
-rw-r--r--docs/html/images/activity_task_design/HomeTaskBasics1e.pngbin0 -> 23046 bytes
-rw-r--r--docs/html/images/activity_task_design/HomeTaskSwitching1a.pngbin0 -> 20359 bytes
-rw-r--r--docs/html/images/activity_task_design/HomeTaskSwitching1b.pngbin0 -> 18376 bytes
-rw-r--r--docs/html/images/activity_task_design/HomeTaskSwitching1c.pngbin0 -> 24597 bytes
-rw-r--r--docs/html/images/activity_task_design/HomeTaskSwitching2.pngbin0 -> 41605 bytes
-rw-r--r--docs/html/images/activity_task_design/HomeTaskSwitching3a.pngbin0 -> 26125 bytes
-rw-r--r--docs/html/images/activity_task_design/HomeTaskSwitching3b.pngbin0 -> 21663 bytes
-rw-r--r--docs/html/images/activity_task_design/HomeTaskSwitching3c.pngbin0 -> 16329 bytes
-rw-r--r--docs/html/images/activity_task_design/IntentsDiagram.pngbin0 -> 37600 bytes
-rw-r--r--docs/html/images/activity_task_design/PhoneActivitiesDiagram.pngbin0 -> 31067 bytes
-rw-r--r--docs/html/images/activity_task_design/ReplacingAnActivity.pngbin0 -> 45079 bytes
-rw-r--r--docs/html/images/activity_task_design/ReusingAnActivity1.pngbin0 -> 30077 bytes
-rw-r--r--docs/html/images/activity_task_design/ReusingAnActivity2.pngbin0 -> 27762 bytes
-rw-r--r--include/tts/TtsEngine.h3
-rw-r--r--media/jni/soundpool/SoundPool.cpp16
-rw-r--r--media/tests/MediaFrameworkTest/AndroidManifest.xml2
-rw-r--r--services/java/com/android/server/BackupManagerService.java246
-rw-r--r--services/java/com/android/server/PackageManagerService.java2
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java148
-rw-r--r--services/java/com/android/server/am/BackupRecord.java57
-rw-r--r--telephony/java/com/android/internal/telephony/CommandsInterface.java18
-rw-r--r--telephony/java/com/android/internal/telephony/DataConnectionTracker.java12
-rw-r--r--telephony/java/com/android/internal/telephony/RIL.java30
-rw-r--r--telephony/java/com/android/internal/telephony/RILConstants.java1
-rw-r--r--telephony/java/com/android/internal/telephony/SMSDispatcher.java79
-rw-r--r--telephony/java/com/android/internal/telephony/SmsMessageBase.java4
-rw-r--r--telephony/java/com/android/internal/telephony/WapPushOverSms.java27
-rw-r--r--telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java11
-rw-r--r--telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java20
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java14
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java29
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/SmsMessage.java40
-rw-r--r--telephony/java/com/android/internal/telephony/test/SimulatedCommands.java8
-rw-r--r--tests/CoreTests/com/android/internal/telephony/GsmAlphabetTest.java2
-rw-r--r--tests/DumpRenderTree/AndroidManifest.xml5
-rwxr-xr-xtests/DumpRenderTree/assets/run_reliability_tests.py76
-rw-r--r--tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java171
-rw-r--r--tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestActivity.java256
-rw-r--r--tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestsAutoTest.java209
-rw-r--r--tests/backup/AndroidManifest.xml7
-rw-r--r--tests/backup/src/com/android/backuptest/BackupTestAgent.java (renamed from tests/backup/src/com/android/backuptest/BackupTestService.java)6
-rwxr-xr-xtests/sketch/AndroidManifest.xml2
86 files changed, 2984 insertions, 600 deletions
diff --git a/Android.mk b/Android.mk
index 4df6f8e59be2..26bcb5c9482b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -70,6 +70,7 @@ LOCAL_SRC_FILES += \
core/java/android/app/IActivityPendingResult.aidl \
core/java/android/app/IActivityWatcher.aidl \
core/java/android/app/IAlarmManager.aidl \
+ core/java/android/app/IBackupAgent.aidl \
core/java/android/app/IInstrumentationWatcher.aidl \
core/java/android/app/IIntentReceiver.aidl \
core/java/android/app/IIntentSender.aidl \
@@ -82,7 +83,6 @@ LOCAL_SRC_FILES += \
core/java/android/app/IWallpaperService.aidl \
core/java/android/app/IWallpaperServiceCallback.aidl \
core/java/android/backup/IBackupManager.aidl \
- core/java/android/backup/IBackupService.aidl \
core/java/android/bluetooth/IBluetoothA2dp.aidl \
core/java/android/bluetooth/IBluetoothDevice.aidl \
core/java/android/bluetooth/IBluetoothDeviceCallback.aidl \
diff --git a/api/current.xml b/api/current.xml
index f572a4d23e94..d1d57adce1ce 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -1167,11 +1167,11 @@
visibility="public"
>
</field>
-<field name="WRITE_SDCARD"
+<field name="WRITE_EXTERNAL_STORAGE"
type="java.lang.String"
transient="false"
volatile="false"
- value="&quot;android.permission.WRITE_SDCARD&quot;"
+ value="&quot;android.permission.WRITE_EXTERNAL_STORAGE&quot;"
static="true"
final="true"
deprecated="not deprecated"
@@ -2077,6 +2077,17 @@
visibility="public"
>
</field>
+<field name="allowBackup"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843393"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="allowClearUserData"
type="int"
transient="false"
@@ -2308,6 +2319,17 @@
visibility="public"
>
</field>
+<field name="backupAgent"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843392"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="baselineAlignBottom"
type="int"
transient="false"
@@ -3507,28 +3529,6 @@
visibility="public"
>
</field>
-<field name="donut_resource_pad31"
- type="int"
- transient="false"
- volatile="false"
- value="16843393"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="donut_resource_pad32"
- type="int"
- transient="false"
- volatile="false"
- value="16843392"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
<field name="donut_resource_pad4"
type="int"
transient="false"
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 16f0a300eecb..3d3d7d5fc9f4 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -20,6 +20,7 @@ import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.res.Configuration;
@@ -1021,6 +1022,33 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeStrongBinder(binder);
return true;
}
+
+ case START_BACKUP_AGENT_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ ApplicationInfo info = ApplicationInfo.CREATOR.createFromParcel(data);
+ int backupRestoreMode = data.readInt();
+ boolean success = bindBackupAgent(info, backupRestoreMode);
+ reply.writeNoException();
+ reply.writeInt(success ? 1 : 0);
+ return true;
+ }
+
+ case BACKUP_AGENT_CREATED_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String packageName = data.readString();
+ IBinder agent = data.readStrongBinder();
+ backupAgentCreated(packageName, agent);
+ reply.writeNoException();
+ return true;
+ }
+
+ case UNBIND_BACKUP_AGENT_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ ApplicationInfo info = ApplicationInfo.CREATOR.createFromParcel(data);
+ unbindBackupAgent(info);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -1681,6 +1709,43 @@ class ActivityManagerProxy implements IActivityManager
return binder;
}
+ public boolean bindBackupAgent(ApplicationInfo app, int backupRestoreMode)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ app.writeToParcel(data, 0);
+ data.writeInt(backupRestoreMode);
+ mRemote.transact(START_BACKUP_AGENT_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean success = reply.readInt() != 0;
+ reply.recycle();
+ data.recycle();
+ return success;
+ }
+
+ public void backupAgentCreated(String packageName, IBinder agent) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(packageName);
+ data.writeStrongBinder(agent);
+ mRemote.transact(BACKUP_AGENT_CREATED_TRANSACTION, data, reply, 0);
+ reply.recycle();
+ data.recycle();
+ }
+
+ public void unbindBackupAgent(ApplicationInfo app) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ app.writeToParcel(data, 0);
+ mRemote.transact(UNBIND_BACKUP_AGENT_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+
public boolean startInstrumentation(ComponentName className, String profileFile,
int flags, Bundle arguments, IInstrumentationWatcher watcher)
throws RemoteException {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 06e0a453b232..29e57cd9d172 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -115,6 +115,7 @@ public final class ActivityThread {
private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
private static final boolean DEBUG_BROADCAST = false;
private static final boolean DEBUG_RESULTS = false;
+ private static final boolean DEBUG_BACKUP = true;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";");
private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;
@@ -499,7 +500,7 @@ public final class ActivityThread {
return mResources;
}
- public Application makeApplication() {
+ public Application makeApplication(boolean forceDefaultAppClass) {
if (mApplication != null) {
return mApplication;
}
@@ -507,7 +508,7 @@ public final class ActivityThread {
Application app = null;
String appClass = mApplicationInfo.className;
- if (appClass == null) {
+ if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
@@ -1199,6 +1200,16 @@ public final class ActivityThread {
}
}
+ private static final class CreateBackupAgentData {
+ ApplicationInfo appInfo;
+ int backupMode;
+ public String toString() {
+ return "CreateBackupAgentData{appInfo=" + appInfo
+ + " backupAgent=" + appInfo.backupAgentName
+ + " mode=" + backupMode + "}";
+ }
+ }
+
private static final class CreateServiceData {
IBinder token;
ServiceInfo info;
@@ -1239,6 +1250,7 @@ public final class ActivityThread {
Bundle instrumentationArgs;
IInstrumentationWatcher instrumentationWatcher;
int debugMode;
+ boolean restrictedBackupMode;
Configuration config;
boolean handlingProfiling;
public String toString() {
@@ -1374,6 +1386,21 @@ public final class ActivityThread {
queueOrSendMessage(H.RECEIVER, r);
}
+ public final void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode) {
+ CreateBackupAgentData d = new CreateBackupAgentData();
+ d.appInfo = app;
+ d.backupMode = backupMode;
+
+ queueOrSendMessage(H.CREATE_BACKUP_AGENT, d);
+ }
+
+ public final void scheduleDestroyBackupAgent(ApplicationInfo app) {
+ CreateBackupAgentData d = new CreateBackupAgentData();
+ d.appInfo = app;
+
+ queueOrSendMessage(H.DESTROY_BACKUP_AGENT, d);
+ }
+
public final void scheduleCreateService(IBinder token,
ServiceInfo info) {
CreateServiceData s = new CreateServiceData();
@@ -1419,7 +1446,7 @@ public final class ActivityThread {
ApplicationInfo appInfo, List<ProviderInfo> providers,
ComponentName instrumentationName, String profileFile,
Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
- int debugMode, Configuration config,
+ int debugMode, boolean isRestrictedBackupMode, Configuration config,
Map<String, IBinder> services) {
Process.setArgV0(processName);
@@ -1437,6 +1464,7 @@ public final class ActivityThread {
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
data.debugMode = debugMode;
+ data.restrictedBackupMode = isRestrictedBackupMode;
data.config = config;
queueOrSendMessage(H.BIND_APPLICATION, data);
}
@@ -1718,6 +1746,8 @@ public final class ActivityThread {
public static final int ACTIVITY_CONFIGURATION_CHANGED = 125;
public static final int RELAUNCH_ACTIVITY = 126;
public static final int PROFILER_CONTROL = 127;
+ public static final int CREATE_BACKUP_AGENT = 128;
+ public static final int DESTROY_BACKUP_AGENT = 129;
String codeToString(int code) {
if (localLOGV) {
switch (code) {
@@ -1749,6 +1779,8 @@ public final class ActivityThread {
case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED";
case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
case PROFILER_CONTROL: return "PROFILER_CONTROL";
+ case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT";
+ case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT";
}
}
return "(unknown)";
@@ -1851,6 +1883,12 @@ public final class ActivityThread {
case PROFILER_CONTROL:
handleProfilerControl(msg.arg1 != 0, (String)msg.obj);
break;
+ case CREATE_BACKUP_AGENT:
+ handleCreateBackupAgent((CreateBackupAgentData)msg.obj);
+ break;
+ case DESTROY_BACKUP_AGENT:
+ handleDestroyBackupAgent((CreateBackupAgentData)msg.obj);
+ break;
}
}
}
@@ -1908,6 +1946,8 @@ public final class ActivityThread {
Application mInitialApplication;
final ArrayList<Application> mAllApplications
= new ArrayList<Application>();
+ // set of instantiated backup agents, keyed by package name
+ final HashMap<String, BackupAgent> mBackupAgents = new HashMap<String, BackupAgent>();
static final ThreadLocal sThreadLocal = new ThreadLocal();
Instrumentation mInstrumentation;
String mInstrumentationAppDir = null;
@@ -2269,7 +2309,7 @@ public final class ActivityThread {
}
try {
- Application app = r.packageInfo.makeApplication();
+ Application app = r.packageInfo.makeApplication(false);
if (localLOGV) Log.v(TAG, "Performing launch of " + r);
if (localLOGV) Log.v(
@@ -2464,7 +2504,7 @@ public final class ActivityThread {
}
try {
- Application app = packageInfo.makeApplication();
+ Application app = packageInfo.makeApplication(false);
if (localLOGV) Log.v(
TAG, "Performing receive of " + data.intent
@@ -2507,6 +2547,85 @@ public final class ActivityThread {
}
}
+ // Instantiate a BackupAgent and tell it that it's alive
+ private final void handleCreateBackupAgent(CreateBackupAgentData data) {
+ if (DEBUG_BACKUP) Log.v(TAG, "handleCreateBackupAgent: " + data);
+
+ // no longer idle; we have backup work to do
+ unscheduleGcIdler();
+
+ // instantiate the BackupAgent class named in the manifest
+ PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo);
+ String packageName = packageInfo.mPackageName;
+ if (mBackupAgents.get(packageName) != null) {
+ Log.d(TAG, "BackupAgent " + " for " + packageName
+ + " already exists");
+ return;
+ }
+
+ BackupAgent agent = null;
+ String classname = data.appInfo.backupAgentName;
+ if (classname == null) {
+ if (data.backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL) {
+ Log.e(TAG, "Attempted incremental backup but no defined agent for "
+ + packageName);
+ return;
+ }
+ classname = "android.app.FullBackupAgent";
+ }
+ try {
+ java.lang.ClassLoader cl = packageInfo.getClassLoader();
+ agent = (BackupAgent) cl.loadClass(data.appInfo.backupAgentName).newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to instantiate backup agent "
+ + data.appInfo.backupAgentName + ": " + e.toString(), e);
+ }
+
+ // set up the agent's context
+ try {
+ if (DEBUG_BACKUP) Log.v(TAG, "Initializing BackupAgent "
+ + data.appInfo.backupAgentName);
+
+ ApplicationContext context = new ApplicationContext();
+ context.init(packageInfo, null, this);
+ context.setOuterContext(agent);
+ agent.attach(context);
+ agent.onCreate();
+
+ // tell the OS that we're live now
+ IBinder binder = agent.onBind();
+ try {
+ ActivityManagerNative.getDefault().backupAgentCreated(packageName, binder);
+ } catch (RemoteException e) {
+ // nothing to do.
+ }
+ mBackupAgents.put(packageName, agent);
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to create BackupAgent "
+ + data.appInfo.backupAgentName + ": " + e.toString(), e);
+ }
+ }
+
+ // Tear down a BackupAgent
+ private final void handleDestroyBackupAgent(CreateBackupAgentData data) {
+ if (DEBUG_BACKUP) Log.v(TAG, "handleDestroyBackupAgent: " + data);
+
+ PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo);
+ String packageName = packageInfo.mPackageName;
+ BackupAgent agent = mBackupAgents.get(packageName);
+ if (agent != null) {
+ try {
+ agent.onDestroy();
+ } catch (Exception e) {
+ Log.w(TAG, "Exception thrown in onDestroy by backup agent of " + data.appInfo);
+ e.printStackTrace();
+ }
+ mBackupAgents.remove(packageName);
+ } else {
+ Log.w(TAG, "Attempt to destroy unknown backup agent " + data);
+ }
+ }
+
private final void handleCreateService(CreateServiceData data) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
@@ -2532,7 +2651,7 @@ public final class ActivityThread {
ApplicationContext context = new ApplicationContext();
context.init(packageInfo, null, this);
- Application app = packageInfo.makeApplication();
+ Application app = packageInfo.makeApplication(false);
context.setOuterContext(service);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
@@ -3694,7 +3813,9 @@ public final class ActivityThread {
mInstrumentation = new Instrumentation();
}
- Application app = data.info.makeApplication();
+ // If the app is being launched for full backup or restore, bring it up in
+ // a restricted environment with the base application class.
+ Application app = data.info.makeApplication(data.restrictedBackupMode);
mInitialApplication = app;
List<ProviderInfo> providers = data.providers;
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index f24318585cb4..e28fd0f29070 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -230,11 +230,13 @@ public abstract class ApplicationThreadNative extends Binder
IBinder binder = data.readStrongBinder();
IInstrumentationWatcher testWatcher = IInstrumentationWatcher.Stub.asInterface(binder);
int testMode = data.readInt();
+ boolean restrictedBackupMode = (data.readInt() != 0);
Configuration config = Configuration.CREATOR.createFromParcel(data);
HashMap<String, IBinder> services = data.readHashMap(null);
bindApplication(packageName, info,
providers, testName, profileName,
- testArgs, testWatcher, testMode, config, services);
+ testArgs, testWatcher, testMode, restrictedBackupMode,
+ config, services);
return true;
}
@@ -339,6 +341,15 @@ public abstract class ApplicationThreadNative extends Binder
setSchedulingGroup(group);
return true;
}
+
+ case SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION:
+ {
+ data.enforceInterface(IApplicationThread.descriptor);
+ ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(data);
+ int backupMode = data.readInt();
+ scheduleCreateBackupAgent(appInfo, backupMode);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -492,6 +503,24 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
+ public final void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ app.writeToParcel(data, 0);
+ data.writeInt(backupMode);
+ mRemote.transact(SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION, data, null, 0);
+ data.recycle();
+ }
+
+ public final void scheduleDestroyBackupAgent(ApplicationInfo app) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ app.writeToParcel(data, 0);
+ mRemote.transact(SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION, data, null, 0);
+ data.recycle();
+ }
+
public final void scheduleCreateService(IBinder token, ServiceInfo info)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -551,7 +580,8 @@ class ApplicationThreadProxy implements IApplicationThread {
public final void bindApplication(String packageName, ApplicationInfo info,
List<ProviderInfo> providers, ComponentName testName,
String profileName, Bundle testArgs, IInstrumentationWatcher testWatcher, int debugMode,
- Configuration config, Map<String, IBinder> services) throws RemoteException {
+ boolean restrictedBackupMode, Configuration config,
+ Map<String, IBinder> services) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeString(packageName);
@@ -567,6 +597,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeBundle(testArgs);
data.writeStrongInterface(testWatcher);
data.writeInt(debugMode);
+ data.writeInt(restrictedBackupMode ? 1 : 0);
config.writeToParcel(data, 0);
data.writeMap(services);
mRemote.transact(BIND_APPLICATION_TRANSACTION, data, null,
diff --git a/core/java/android/backup/BackupService.java b/core/java/android/app/BackupAgent.java
index 50a5921c0421..997bfdc1965d 100644
--- a/core/java/android/backup/BackupService.java
+++ b/core/java/android/app/BackupAgent.java
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-package android.backup;
+package android.app;
-import android.annotation.SdkConstant;
-import android.annotation.SdkConstant.SdkConstantType;
-import android.app.Service;
-import android.backup.IBackupService;
-import android.content.Intent;
+import android.app.IBackupAgent;
+import android.backup.BackupDataOutput;
+import android.content.Context;
+import android.content.ContextWrapper;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -30,31 +29,18 @@ import android.util.Log;
* This is the central interface between an application and Android's
* settings backup mechanism.
*
- * In order to use the backup service, your application must implement a
- * subclass of BackupService, and declare an intent filter
- * in the application manifest specifying that your BackupService subclass
- * handles the {@link BackupService#SERVICE_ACTION} intent action. For example:
- *
- * <pre class="prettyprint">
- * &lt;!-- Use the class "MyBackupService" to perform backups for my app --&gt;
- * &lt;service android:name=".MyBackupService"&gt;
- * &lt;intent-filter&gt;
- * &lt;action android:name="android.backup.BackupService.SERVICE" /&gt;
- * &lt;/intent-filter&gt;
- * &lt;/service&gt;</pre>
- *
* @hide pending API solidification
*/
+public abstract class BackupAgent extends ContextWrapper {
+ public BackupAgent() {
+ super(null);
+ }
-public abstract class BackupService extends Service {
- /**
- * Service Action: Participate in the backup infrastructure. Applications
- * that wish to use the Android backup mechanism must provide an exported
- * subclass of BackupService and give it an {@link android.content.IntentFilter
- * IntentFilter} that accepts this action.
- */
- @SdkConstant(SdkConstantType.SERVICE_ACTION)
- public static final String SERVICE_ACTION = "android.backup.BackupService.SERVICE";
+ public void onCreate() {
+ }
+
+ public void onDestroy() {
+ }
/**
* The application is being asked to write any data changed since the
@@ -91,7 +77,8 @@ public abstract class BackupService extends Service {
* file. The application should record the final backup state
* here after restoring its data from dataFd.
*/
- public abstract void onRestore(ParcelFileDescriptor /* TODO: BackupDataInput */ data, ParcelFileDescriptor newState);
+ public abstract void onRestore(ParcelFileDescriptor /* TODO: BackupDataInput */ data,
+ ParcelFileDescriptor newState);
// ----- Core implementation -----
@@ -100,29 +87,33 @@ public abstract class BackupService extends Service {
* Returns the private interface called by the backup system. Applications will
* not typically override this.
*/
- public IBinder onBind(Intent intent) {
- if (intent.getAction().equals(SERVICE_ACTION)) {
- return mBinder;
- }
- return null;
+ public IBinder onBind() {
+ return mBinder;
}
private final IBinder mBinder = new BackupServiceBinder().asBinder();
+ /** @hide */
+ public void attach(Context context) {
+ attachBaseContext(context);
+ }
+
// ----- IBackupService binder interface -----
- private class BackupServiceBinder extends IBackupService.Stub {
+ private class BackupServiceBinder extends IBackupAgent.Stub {
+ private static final String TAG = "BackupServiceBinder";
+
public void doBackup(ParcelFileDescriptor oldState,
ParcelFileDescriptor data,
ParcelFileDescriptor newState) throws RemoteException {
// !!! TODO - real implementation; for now just invoke the callbacks directly
- Log.v("BackupServiceBinder", "doBackup() invoked");
- BackupDataOutput output = new BackupDataOutput(BackupService.this,
+ Log.v(TAG, "doBackup() invoked");
+ BackupDataOutput output = new BackupDataOutput(BackupAgent.this,
data.getFileDescriptor());
try {
- BackupService.this.onBackup(oldState, output, newState);
+ BackupAgent.this.onBackup(oldState, output, newState);
} catch (RuntimeException ex) {
- Log.d("BackupService", "onBackup ("
- + BackupService.this.getClass().getName() + ") threw", ex);
+ Log.d("BackupAgent", "onBackup ("
+ + BackupAgent.this.getClass().getName() + ") threw", ex);
throw ex;
}
}
@@ -130,8 +121,8 @@ public abstract class BackupService extends Service {
public void doRestore(ParcelFileDescriptor data,
ParcelFileDescriptor newState) throws RemoteException {
// !!! TODO - real implementation; for now just invoke the callbacks directly
- Log.v("BackupServiceBinder", "doRestore() invoked");
- BackupService.this.onRestore(data, newState);
+ Log.v(TAG, "doRestore() invoked");
+ BackupAgent.this.onRestore(data, newState);
}
}
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index d15a1544aef3..c948aec964e5 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -21,6 +21,7 @@ import android.content.ContentProviderNative;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.ProviderInfo;
@@ -149,6 +150,11 @@ public interface IActivityManager extends IInterface {
public void serviceDoneExecuting(IBinder token) throws RemoteException;
public IBinder peekService(Intent service, String resolvedType) throws RemoteException;
+ public boolean bindBackupAgent(ApplicationInfo appInfo, int backupRestoreMode)
+ throws RemoteException;
+ public void backupAgentCreated(String packageName, IBinder agent) throws RemoteException;
+ public void unbindBackupAgent(ApplicationInfo appInfo) throws RemoteException;
+
public boolean startInstrumentation(ComponentName className, String profileFile,
int flags, Bundle arguments, IInstrumentationWatcher watcher)
throws RemoteException;
@@ -397,4 +403,7 @@ public interface IActivityManager extends IInterface {
int SHUTDOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+86;
int STOP_APP_SWITCHES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+87;
int RESUME_APP_SWITCHES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+88;
+ int START_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+89;
+ int BACKUP_AGENT_CREATED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+90;
+ int UNBIND_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+91;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index ec03d3a1f1f5..bca1fea150d4 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -59,6 +59,11 @@ public interface IApplicationThread extends IInterface {
int configChanges) throws RemoteException;
void scheduleReceiver(Intent intent, ActivityInfo info, int resultCode,
String data, Bundle extras, boolean sync) throws RemoteException;
+ static final int BACKUP_MODE_INCREMENTAL = 0;
+ static final int BACKUP_MODE_FULL = 1;
+ static final int BACKUP_MODE_RESTORE = 2;
+ void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode) throws RemoteException;
+ void scheduleDestroyBackupAgent(ApplicationInfo app) throws RemoteException;
void scheduleCreateService(IBinder token, ServiceInfo info) throws RemoteException;
void scheduleBindService(IBinder token,
Intent intent, boolean rebind) throws RemoteException;
@@ -71,8 +76,8 @@ public interface IApplicationThread extends IInterface {
static final int DEBUG_WAIT = 2;
void bindApplication(String packageName, ApplicationInfo info, List<ProviderInfo> providers,
ComponentName testName, String profileName, Bundle testArguments,
- IInstrumentationWatcher testWatcher, int debugMode, Configuration config, Map<String,
- IBinder> services) throws RemoteException;
+ IInstrumentationWatcher testWatcher, int debugMode, boolean restrictedBackupMode,
+ Configuration config, Map<String, IBinder> services) throws RemoteException;
void scheduleExit() throws RemoteException;
void requestThumbnail(IBinder token) throws RemoteException;
void scheduleConfigurationChanged(Configuration config) throws RemoteException;
@@ -119,4 +124,6 @@ public interface IApplicationThread extends IInterface {
int REQUEST_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26;
int PROFILER_CONTROL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+27;
int SET_SCHEDULING_GROUP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+28;
+ int SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29;
+ int SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30;
}
diff --git a/core/java/android/backup/IBackupService.aidl b/core/java/android/app/IBackupAgent.aidl
index 1bde8eac9bdb..bb9f00830d8e 100644
--- a/core/java/android/backup/IBackupService.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-package android.backup;
+package android.app;
import android.os.ParcelFileDescriptor;
/**
* Interface presented by applications being asked to participate in the
* backup & restore mechanism. End user code does not typically implement
- * this interface; they subclass BackupService instead.
+ * this interface; they subclass BackupAgent instead.
*
* {@hide}
*/
-interface IBackupService {
+interface IBackupAgent {
/**
* Request that the app perform an incremental backup.
*
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index ff110c86231f..3675ec2c4684 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -1275,11 +1275,28 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private void handleCursorRespondIntent(Intent intent) {
Cursor c = mSuggestionsAdapter.getCursor();
if (c != null) {
- c.respond(intent.getExtras());
+ Bundle response = c.respond(intent.getExtras());
+
+ // The SHOW_CORPUS_SELECTORS command to the cursor also returns a value in
+ // its bundle, keyed by the same command string, which contains the index
+ // of the "More results..." list item, which we use to instruct the
+ // AutoCompleteTextView's list to scroll to that item when the item is
+ // clicked.
+ if (response.containsKey(SuggestionsAdapter.SHOW_CORPUS_SELECTORS)) {
+ int indexOfMore = response.getInt(SuggestionsAdapter.SHOW_CORPUS_SELECTORS);
+ mSuggestionsAdapter.setListItemToSelect(indexOfMore);
+ }
}
}
/**
+ * Sets the list item selection in the AutoCompleteTextView's ListView.
+ */
+ public void setListSelection(int index) {
+ mSearchAutoComplete.setListSelection(index);
+ }
+
+ /**
* Saves the previous component that was searched, so that we can go
* back to it.
*/
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 3bf37c3b2b01..be2f50f0d799 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -1302,9 +1302,10 @@ public class SearchManager
/**
* Column name for suggestions cursor. <i>Optional.</i> This column allows suggestions
* to provide additional arbitrary data which will be included as an extra under the key
- * {@link #EXTRA_DATA_KEY}.
+ * {@link #EXTRA_DATA_KEY}. For use by the global search system only - if other providers
+ * attempt to use this column, the value will be overwritten by global search.
*
- * @hide pending API council approval
+ * @hide
*/
public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data";
/**
diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java
index aeb96b4cbcba..4406f8e760fe 100644
--- a/core/java/android/app/SuggestionsAdapter.java
+++ b/core/java/android/app/SuggestionsAdapter.java
@@ -50,6 +50,11 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
// so we can correctly display (or not display) the 'working' spinner in the search dialog.
public static final String IS_WORKING = "isWorking";
+ // The value used to tell a cursor to display the corpus selectors, if this is global
+ // search. Also returns the index of the more results item to allow the SearchDialog
+ // to tell the ListView to scroll to that list item.
+ public static final String SHOW_CORPUS_SELECTORS = "showCorpusSelectors";
+
private static final boolean DBG = false;
private static final String LOG_TAG = "SuggestionsAdapter";
@@ -68,6 +73,13 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
private int mIconBitmap1Col;
private int mIconBitmap2Col;
+ // This value is stored in SuggestionsAdapter by the SearchDialog to indicate whether
+ // a particular list item should be selected upon the next call to notifyDataSetChanged.
+ // This is used to indicate the index of the "More results..." list item so that when
+ // the data set changes after a click of "More results...", we can correctly tell the
+ // ListView to scroll to the right line item. It gets reset to -1 every time it is consumed.
+ private int mListItemToSelect = -1;
+
public SuggestionsAdapter(Context context, SearchDialog searchDialog, SearchableInfo searchable,
WeakHashMap<String, Drawable> outsideDrawablesCache, boolean globalSearchMode) {
super(context,
@@ -134,6 +146,19 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
updateWorking();
+ if (mListItemToSelect != -1) {
+ mSearchDialog.setListSelection(mListItemToSelect);
+ mListItemToSelect = -1;
+ }
+ }
+
+ /**
+ * Specifies the list item to select upon next call of {@link #notifyDataSetChanged()},
+ * in order to let us scroll the "More results..." list item to the top of the screen
+ * (or as close as it can get) when clicked.
+ */
+ public void setListItemToSelect(int index) {
+ mListItemToSelect = index;
}
/**
diff --git a/core/java/android/backup/IBackupManager.aidl b/core/java/android/backup/IBackupManager.aidl
index cf22798507a8..3468d70962c2 100644
--- a/core/java/android/backup/IBackupManager.aidl
+++ b/core/java/android/backup/IBackupManager.aidl
@@ -34,8 +34,22 @@ interface IBackupManager {
oneway void dataChanged(String packageName);
/**
+ * Notifies the Backup Manager Service that an agent has become available. This
+ * method is only invoked by the Activity Manager.
+ * !!! TODO: permission
+ */
+ oneway void agentConnected(String packageName, IBinder agent);
+
+ /**
+ * Notify the Backup Manager Service that an agent has unexpectedly gone away.
+ * This method is only invoked by the Activity Manager.
+ * !!! TODO: permission
+ */
+ oneway void agentDisconnected(String packageName);
+
+ /**
* Schedule a full backup of the given package.
- * !!! TODO: protect with a signature-or-system permission?
+ * !!! TODO: permission
*/
oneway void scheduleFullBackup(String packageName);
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index ad022e760d3b..f16eb74b6ff1 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -58,11 +58,22 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* Class implementing the Application's manage space
* functionality. From the "manageSpaceActivity"
* attribute. This is an optional attribute and will be null if
- * application's dont specify it in their manifest
+ * applications don't specify it in their manifest
*/
public String manageSpaceActivityName;
/**
+ * Class implementing the Application's backup functionality. From
+ * the "backupAgent" attribute. This is an optional attribute and
+ * will be null if the application does not specify it in its manifest.
+ *
+ * <p>If android:allowBackup is set to false, this attribute is ignored.
+ *
+ * {@hide}
+ */
+ public String backupAgentName;
+
+ /**
* Value for {@link #flags}: if set, this application is installed in the
* device's system image.
*/
@@ -93,7 +104,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public static final int FLAG_PERSISTENT = 1<<3;
/**
- * Value for {@link #flags}: set to true iif this application holds the
+ * Value for {@link #flags}: set to true if this application holds the
* {@link android.Manifest.permission#FACTORY_TEST} permission and the
* device is running in factory test mode.
*/
@@ -126,6 +137,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public static final int FLAG_TEST_ONLY = 1<<8;
/**
+ * Value for {@link #flags}: this is false if the application has set
+ * its android:allowBackup to false, true otherwise.
+ *
+ * {@hide}
+ */
+ public static final int FLAG_ALLOW_BACKUP = 1<<10;
+
+ /**
* Flags associated with the application. Any combination of
* {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE},
* {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and
@@ -285,6 +304,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeInt(targetSdkVersion);
dest.writeInt(enabled ? 1 : 0);
dest.writeString(manageSpaceActivityName);
+ dest.writeString(backupAgentName);
dest.writeInt(descriptionRes);
dest.writeIntArray(supportsDensities);
}
@@ -315,6 +335,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
targetSdkVersion = source.readInt();
enabled = source.readInt() != 0;
manageSpaceActivityName = source.readString();
+ backupAgentName = source.readString();
descriptionRes = source.readInt();
supportsDensities = source.createIntArray();
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index ee20aeebb0a7..212b59017160 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -76,7 +76,7 @@ public class PackageParser {
*/
public static final PackageParser.NewPermissionInfo NEW_PERMISSIONS[] =
new PackageParser.NewPermissionInfo[] {
- new PackageParser.NewPermissionInfo(android.Manifest.permission.WRITE_SDCARD,
+ new PackageParser.NewPermissionInfo(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
android.os.Build.VERSION_CODES.DONUT, 0),
new PackageParser.NewPermissionInfo(android.Manifest.permission.READ_PHONE_STATE,
android.os.Build.VERSION_CODES.DONUT, 0)
@@ -1176,6 +1176,19 @@ public class PackageParser {
outError);
}
+ boolean allowBackup = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_allowBackup, true);
+ if (allowBackup) {
+ ai.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ String backupAgent = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestApplication_backupAgent);
+ if (backupAgent != null) {
+ ai.backupAgentName = buildClassName(pkgName, backupAgent, outError);
+ Log.v(TAG, "android:backupAgent = " + ai.backupAgentName
+ + " from " + pkgName + "+" + backupAgent);
+ }
+ }
+
TypedValue v = sa.peekValue(
com.android.internal.R.styleable.AndroidManifestApplication_label);
if (v != null && (ai.labelRes=v.resourceId) == 0) {
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 963875deca72..830b0bd7ceb1 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -117,7 +117,7 @@ public class Build {
* new changes in behavior:</p>
* <ul>
* <li> They must explicitly request the
- * {@link android.Manifest.permission#WRITE_SDCARD} permission to be
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission to be
* able to modify the contents of the SD card. (Apps targeting
* earlier versions will always request the permission.)
* </ul>
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 559f224d1263..5d106756ad79 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2838,12 +2838,12 @@ public final class Settings {
* out without asking for use permit, to limit the un-authorized SMS
* usage.
*/
- public static final String SMS_OUTGOING_CEHCK_INTERVAL_MS =
+ public static final String SMS_OUTGOING_CHECK_INTERVAL_MS =
"sms_outgoing_check_interval_ms";
/**
* The number of outgoing SMS sent without asking for user permit
- * (of {@link #SMS_OUTGOING_CEHCK_INTERVAL_MS}
+ * (of {@link #SMS_OUTGOING_CHECK_INTERVAL_MS}
*/
public static final String SMS_OUTGOING_CEHCK_MAX_COUNT =
"sms_outgoing_check_max_count";
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index a4145c487696..4078fa6d5a73 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -466,6 +466,24 @@ public final class Telephony {
*/
public static final class Intents {
/**
+ * Set by BroadcastReceiver. Indicates the message was handled
+ * successfully.
+ */
+ public static final int RESULT_SMS_HANDLED = 1;
+
+ /**
+ * Set by BroadcastReceiver. Indicates a generic error while
+ * processing the message.
+ */
+ public static final int RESULT_SMS_GENERIC_ERROR = 2;
+
+ /**
+ * Set by BroadcastReceiver. Indicates insufficient memory to store
+ * the message.
+ */
+ public static final int RESULT_SMS_OUT_OF_MEMORY = 3;
+
+ /**
* Broadcast Action: A new text based SMS message has been received
* by the device. The intent will have the following extra
* values:</p>
@@ -476,7 +494,10 @@ public final class Telephony {
* </ul>
*
* <p>The extra values can be extracted using
- * {@link #getMessagesFromIntent(Intent)}</p>
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String SMS_RECEIVED_ACTION =
@@ -493,7 +514,10 @@ public final class Telephony {
* </ul>
*
* <p>The extra values can be extracted using
- * {@link #getMessagesFromIntent(Intent)}</p>
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String DATA_SMS_RECEIVED_ACTION =
@@ -510,6 +534,9 @@ public final class Telephony {
* <li><em>pduType (Integer)</em> - The WAP PDU type</li>
* <li><em>data</em> - The data payload of the message</li>
* </ul>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String WAP_PUSH_RECEIVED_ACTION =
diff --git a/core/java/android/speech/IRecognitionListener.aidl b/core/java/android/speech/IRecognitionListener.aidl
index 6ed32b502b42..b4abfda30aed 100644
--- a/core/java/android/speech/IRecognitionListener.aidl
+++ b/core/java/android/speech/IRecognitionListener.aidl
@@ -17,6 +17,7 @@
package android.speech;
import android.os.Bundle;
+import android.speech.RecognitionResult;
/**
* Listener for speech recognition events, used with RecognitionService.
@@ -43,13 +44,19 @@ interface IRecognitionListener {
/** Called after the user stops speaking. */
void onEndOfSpeech();
- /** A network or recognition error occurred. */
- void onError(in String error);
+ /**
+ * A network or recognition error occurred.
+ * TODO: right now, the error code is given in voice search package
+ * (vendor/google/apps/src/com/google/android/voicesearch/speechservice/SpeechServiceListener.java)
+ * we need to find a place to define common error code.
+ */
+ void onError(in int error);
/**
- * Called when recognition transcripts are ready.
- * results: an ordered list of the most likely transcripts (N-best list).
- * @hide
+ * Called when recognition results are ready.
+ * @param results: an ordered list of the most likely results (N-best list).
+ * @param key: a key associated with the results. The same results can
+ * be retrieved asynchronously later using the key, if available.
*/
- void onResults(in List<String> results);
+ void onResults(in List<RecognitionResult> results, long key);
}
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index 36d12e9aae64..a18c380c9e70 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -18,6 +18,7 @@ package android.speech;
import android.content.Intent;
import android.speech.IRecognitionListener;
+import android.speech.RecognitionResult;
// A Service interface to speech recognition. Call startListening when
// you want to begin capturing audio; RecognitionService will automatically
@@ -29,6 +30,8 @@ interface IRecognitionService {
// see RecognizerIntent.java for constants used to specify the intent.
void startListening(in Intent recognizerIntent,
in IRecognitionListener listener);
+
+ List<RecognitionResult> getRecognitionResults(in long key);
void cancel();
}
diff --git a/core/java/android/speech/RecognitionResult.aidl b/core/java/android/speech/RecognitionResult.aidl
new file mode 100644
index 000000000000..59e53ab86fd1
--- /dev/null
+++ b/core/java/android/speech/RecognitionResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2009 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 android.speech;
+
+parcelable RecognitionResult;
diff --git a/core/java/android/speech/RecognitionResult.java b/core/java/android/speech/RecognitionResult.java
new file mode 100644
index 000000000000..c3ac48404f6e
--- /dev/null
+++ b/core/java/android/speech/RecognitionResult.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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 android.speech;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * RecognitionResult is a passive object that stores a single recognized
+ * query and its search result.
+ * TODO: revisit and improve. May be we should have a separate result
+ * object for each type, and put them (type/value) in bundle?
+ *
+ * {@hide}
+ */
+public class RecognitionResult implements Parcelable {
+
+ /**
+ * Type of the recognition results.
+ */
+ public static final int RAW_RECOGNITION_RESULT = 0;
+ public static final int WEB_SEARCH_RESULT = 1;
+ public static final int CONTACT_RESULT = 2;
+
+ /**
+ * A factory method to create a raw RecognitionResult
+ *
+ * @param sentence the recognized text.
+ */
+ public static RecognitionResult newRawRecognitionResult(String sentence) {
+ return new RecognitionResult(RAW_RECOGNITION_RESULT, sentence, null, null);
+ }
+
+ /**
+ * A factory method to create RecognitionResult for contacts.
+ *
+ * @param contact the contact name.
+ * @param phoneType the phone type.
+ */
+ public static RecognitionResult newContactResult(String contact, int phoneType) {
+ return new RecognitionResult(CONTACT_RESULT, contact, phoneType);
+ }
+
+ /**
+ * A factory method to create a RecognitionResult for Web Search Query.
+ *
+ * @param query the query string.
+ * @param html the html page of the search result.
+ * @param url the url that performs the search with the query.
+ */
+ public static RecognitionResult newWebResult(String query, String html, String url) {
+ return new RecognitionResult(WEB_SEARCH_RESULT, query, html, url);
+ }
+
+ public static final Parcelable.Creator<RecognitionResult> CREATOR
+ = new Parcelable.Creator<RecognitionResult>() {
+
+ public RecognitionResult createFromParcel(Parcel in) {
+ return new RecognitionResult(in);
+ }
+
+ public RecognitionResult[] newArray(int size) {
+ return new RecognitionResult[size];
+ }
+ };
+
+ /**
+ * Result type.
+ */
+ public final int mResultType;
+
+ /**
+ * The recognized string when mResultType is WEB_SEARCH_RESULT.
+ * The name of the contact when mResultType is CONTACT_RESULT.
+ */
+ public final String mText;
+
+ /**
+ * The HTML result page for the query. If this is null, then the
+ * application must use the url field to get the HTML result page.
+ */
+ public final String mHtml;
+
+ /**
+ * The url to get the result page for the query string. The
+ * application must use this url instead of performing the search
+ * with the query.
+ */
+ public final String mUrl;
+
+ /** Phone number type. This is valid only when mResultType == CONTACT_RESULT */
+ public final int mPhoneType;
+
+ private RecognitionResult(int type, String query, String html, String url) {
+ mResultType = type;
+ mText = query;
+ mHtml = html;
+ mUrl = url;
+ mPhoneType = -1;
+ }
+
+ private RecognitionResult(int type, String query, int at) {
+ mResultType = type;
+ mText = query;
+ mPhoneType = at;
+ mHtml = null;
+ mUrl = null;
+ }
+
+ private RecognitionResult(Parcel in) {
+ mResultType = in.readInt();
+ mText = in.readString();
+ mHtml= in.readString();
+ mUrl= in.readString();
+ mPhoneType = in.readInt();
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mResultType);
+ out.writeString(mText);
+ out.writeString(mHtml);
+ out.writeString(mUrl);
+ out.writeInt(mPhoneType);
+ }
+
+
+ @Override
+ public String toString() {
+ String resultType[] = { "RAW", "WEB", "CONTACT" };
+ return "[type=" + resultType[mResultType] +
+ ", text=" + mText+ ", mUrl=" + mUrl + ", html=" + mHtml + "]";
+ }
+
+ public int describeContents() {
+ // no special description
+ return 0;
+ }
+}
diff --git a/core/java/android/speech/RecognitionServiceUtil.java b/core/java/android/speech/RecognitionServiceUtil.java
index 650c0fd24881..a8c78684f6cd 100644
--- a/core/java/android/speech/RecognitionServiceUtil.java
+++ b/core/java/android/speech/RecognitionServiceUtil.java
@@ -21,6 +21,9 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.speech.RecognitionResult;
+import android.util.Log;
import java.util.List;
@@ -56,6 +59,11 @@ public class RecognitionServiceUtil {
public static final Intent sDefaultIntent = new Intent(
RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+ // Recognize request parameters
+ public static final String USE_LOCATION = "useLocation";
+ public static final String CONTACT_AUTH_TOKEN = "contactAuthToken";
+
+ // Bundles
public static final String NOISE_LEVEL = "NoiseLevel";
public static final String SIGNAL_NOISE_RATIO = "SignalNoiseRatio";
@@ -72,8 +80,8 @@ public class RecognitionServiceUtil {
public void onRmsChanged(float rmsdB) {}
public void onBufferReceived(byte[] buf) {}
public void onEndOfSpeech() {}
- public void onError(String error) {}
- public void onResults(List<String> results) {}
+ public void onError(int error) {}
+ public void onResults(List<RecognitionResult> results, long key) {}
}
/**
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index 5fa00e7d443a..c4b5ef891558 100755
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -124,25 +124,25 @@ public class AppSecurityPermissions implements View.OnClickListener {
if(pkg == null) {
return;
}
- // Extract shared user permissions if any
+ // Get requested permissions
+ if (pkg.requestedPermissions != null) {
+ ArrayList<String> strList = pkg.requestedPermissions;
+ int size = strList.size();
+ if (size > 0) {
+ extractPerms(strList.toArray(new String[size]), permSet);
+ }
+ }
+ // Get permissions related to shared user if any
if(pkg.mSharedUserId != null) {
int sharedUid;
try {
sharedUid = mPm.getUidForSharedUser(pkg.mSharedUserId);
+ getAllUsedPermissions(sharedUid, permSet);
} catch (NameNotFoundException e) {
Log.w(TAG, "Could'nt retrieve shared user id for:"+pkg.packageName);
- return;
}
- getAllUsedPermissions(sharedUid, permSet);
- } else {
- ArrayList<String> strList = pkg.requestedPermissions;
- int size;
- if((strList == null) || ((size = strList.size()) == 0)) {
- return;
- }
- // Extract permissions defined in current package
- extractPerms(strList.toArray(new String[size]), permSet);
}
+ // Retrieve list of permissions
for(PermissionInfo tmpInfo : permSet) {
mPermsList.add(tmpInfo);
}
@@ -176,14 +176,9 @@ public class AppSecurityPermissions implements View.OnClickListener {
Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName);
return;
}
- if(pkgInfo == null) {
- return;
- }
- String strList[] = pkgInfo.requestedPermissions;
- if(strList == null) {
- return;
+ if ((pkgInfo != null) && (pkgInfo.requestedPermissions != null)) {
+ extractPerms(pkgInfo.requestedPermissions, permSet);
}
- extractPerms(strList, permSet);
}
private void extractPerms(String strList[], Set<PermissionInfo> permSet) {
diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java
index dc2c70de92e4..5bf8035e8ccc 100644
--- a/core/java/android/widget/TabHost.java
+++ b/core/java/android/widget/TabHost.java
@@ -87,8 +87,9 @@ public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchMode
/**
- * <p>Call setup() before adding tabs if loading TabHost using findViewById(). <i><b>However</i></b>: You do
- * not need to call setup() after getTabHost() in {@link android.app.TabActivity TabActivity}.
+ * <p>Call setup() before adding tabs if loading TabHost using findViewById().
+ * <i><b>However</i></b>: You do not need to call setup() after getTabHost()
+ * in {@link android.app.TabActivity TabActivity}.
* Example:</p>
<pre>mTabHost = (TabHost)findViewById(R.id.tabhost);
mTabHost.setup();
@@ -363,14 +364,14 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
*
* @param tag
* Which tab was selected.
- * @return The view to distplay the contents of the selected tab.
+ * @return The view to display the contents of the selected tab.
*/
View createTabContent(String tag);
}
/**
- * A tab has a tab indictor, content, and a tag that is used to keep
+ * A tab has a tab indicator, content, and a tag that is used to keep
* track of it. This builder helps choose among these options.
*
* For the tab indicator, your choices are:
@@ -607,7 +608,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
}
mLaunchedView = wd;
- // XXX Set FOCUS_AFTER_DESCENDANTS on embedded activies for now so they can get
+ // XXX Set FOCUS_AFTER_DESCENDANTS on embedded activities for now so they can get
// focus if none of their children have it. They need focus to be able to
// display menu items.
//
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
index d9fb78b81d38..bae4dad5c01f 100644
--- a/core/java/android/widget/ZoomButtonsController.java
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -81,27 +81,27 @@ public class ZoomButtonsController implements View.OnTouchListener {
private static final int ZOOM_CONTROLS_TOUCH_PADDING = 20;
private int mTouchPaddingScaledSq;
- private Context mContext;
- private WindowManager mWindowManager;
+ private final Context mContext;
+ private final WindowManager mWindowManager;
private boolean mAutoDismissControls = true;
/**
* The view that is being zoomed by this zoom controller.
*/
- private View mOwnerView;
+ private final View mOwnerView;
/**
* The location of the owner view on the screen. This is recalculated
* each time the zoom controller is shown.
*/
- private int[] mOwnerViewRawLocation = new int[2];
+ private final int[] mOwnerViewRawLocation = new int[2];
/**
* The container that is added as a window.
*/
- private FrameLayout mContainer;
+ private final FrameLayout mContainer;
private LayoutParams mContainerLayoutParams;
- private int[] mContainerRawLocation = new int[2];
+ private final int[] mContainerRawLocation = new int[2];
private ZoomControls mControls;
@@ -113,7 +113,7 @@ public class ZoomButtonsController implements View.OnTouchListener {
/**
* The {@link #mTouchTargetView}'s location in window, set on touch down.
*/
- private int[] mTouchTargetWindowLocation = new int[2];
+ private final int[] mTouchTargetWindowLocation = new int[2];
/**
* If the zoom controller is dismissed but the user is still in a touch
@@ -128,8 +128,8 @@ public class ZoomButtonsController implements View.OnTouchListener {
/** Whether the container has been added to the window manager. */
private boolean mIsVisible;
- private Rect mTempRect = new Rect();
- private int[] mTempIntArray = new int[2];
+ private final Rect mTempRect = new Rect();
+ private final int[] mTempIntArray = new int[2];
private OnZoomListener mCallback;
@@ -141,13 +141,13 @@ public class ZoomButtonsController implements View.OnTouchListener {
*/
private Runnable mPostedVisibleInitializer;
- private IntentFilter mConfigurationChangedFilter =
+ private final IntentFilter mConfigurationChangedFilter =
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
/**
* Needed to reposition the zoom controls after configuration changes.
*/
- private BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!mIsVisible) return;
@@ -167,7 +167,7 @@ public class ZoomButtonsController implements View.OnTouchListener {
*/
private static final int MSG_POST_SET_VISIBLE = 4;
- private Handler mHandler = new Handler() {
+ private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -444,6 +444,9 @@ public class ZoomButtonsController implements View.OnTouchListener {
}
private void refreshPositioningVariables() {
+ // if the mOwnerView is detached from window then skip.
+ if (mOwnerView.getWindowToken() == null) return;
+
// Position the zoom controls on the bottom of the owner view.
int ownerHeight = mOwnerView.getHeight();
int ownerWidth = mOwnerView.getWidth();
diff --git a/core/java/com/google/android/util/GoogleWebContentHelper.java b/core/java/com/google/android/util/GoogleWebContentHelper.java
index 291142008934..3cdf85592970 100644
--- a/core/java/com/google/android/util/GoogleWebContentHelper.java
+++ b/core/java/com/google/android/util/GoogleWebContentHelper.java
@@ -130,7 +130,14 @@ public class GoogleWebContentHelper {
mWebView.loadUrl(mSecureUrl);
return this;
}
-
+
+ public GoogleWebContentHelper loadDataWithFailUrl(String base, String data,
+ String mimeType, String encoding, String failUrl) {
+ ensureViews();
+ mWebView.loadDataWithBaseURL(base, data, mimeType, encoding, failUrl);
+ return this;
+ }
+
/**
* Helper to handle the back key. Returns true if the back key was handled,
* otherwise returns false.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 36f7dfb6a49c..20cb34ae2277 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -382,8 +382,8 @@
android:label="@string/permgrouplab_storage"
android:description="@string/permgroupdesc_storage" />
- <!-- Allows an application to write to the SD card -->
- <permission android:name="android.permission.WRITE_SDCARD"
+ <!-- Allows an application to write to external storage -->
+ <permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:permissionGroup="android.permission-group.STORAGE"
android:label="@string/permlab_sdcardWrite"
android:description="@string/permdesc_sdcardWrite"
@@ -972,6 +972,13 @@
android:description="@string/permdesc_batteryStats"
android:protectionLevel="normal" />
+ <!-- Allows an application to control the backup and restore process
+ @hide pending API council -->
+ <permission android:name="android.permission.BACKUP"
+ android:label="@string/permlab_backup"
+ android:description="@string/permdesc_backup"
+ android:protectionLevel="signature" />
+
<!-- Allows an application to tell the AppWidget service which application
can access AppWidget's data. The normal user flow is that a user
picks an AppWidget to go into a particular host, thereby giving that
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 7b48267045fa..817a56637ea4 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -570,6 +570,15 @@
<!-- Application's requirement for five way navigation -->
<attr name="reqFiveWayNav" format="boolean" />
+ <!-- The name of the class implementing <code>BackupAgent</code> to manage
+ backup and restore of the application's settings to external storage. -->
+ <attr name="backupAgent" format="string" />
+
+ <!-- Whether the application allows its data to be backed up at all. This
+ attribute defaults to 'true': unless the application opts out, the
+ user will be able to back up its data to desktop storage. -->
+ <attr name="allowBackup" format="boolean" />
+
<!-- The <code>manifest</code> tag is the root of an
<code>AndroidManifest.xml</code> file,
describing the contents of an Android package (.apk) file. One
@@ -643,6 +652,8 @@
<attr name="manageSpaceActivity" />
<attr name="allowClearUserData" />
<attr name="testOnly" />
+ <attr name="backupAgent" />
+ <attr name="allowBackup" />
</declare-styleable>
<!-- The <code>permission</code> tag declares a security permission that can be
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 621270e8198e..c2c84d6526d3 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1113,7 +1113,9 @@
<public type="attr" name="gestureStrokeAngleThreshold" />
<public type="attr" name="eventsInterceptionEnabled" />
<public type="attr" name="fadeEnabled" />
-
+ <public type="attr" name="backupAgent" />
+ <public type="attr" name="allowBackup" />
+
<public-padding type="attr" name="donut_resource_pad" end="0x0101029f" />
<public-padding type="id" name="donut_resource_pad" end="0x01020040" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e311abdc2217..7078bbf0d65c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -540,6 +540,11 @@
collected battery statistics. Not for use by normal applications.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_backup">control system backup and restore</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_backup">Allows the application to control the system's backup and restore mechanism. Not for use by normal applications.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_internalSystemWindow">display unauthorized windows</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_internalSystemWindow">Allows the creation of
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 33d6b3bcf339..0bd32767cba9 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -54,7 +54,7 @@
<group gid="log" />
</permission>
- <permission name="android.permission.WRITE_SDCARD" >
+ <permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
<group gid="sdcard_rw" />
</permission>
@@ -85,7 +85,7 @@
interact with the system. -->
<!-- Standard permissions granted to the shell. -->
- <assign-permission name="android.permission.WRITE_SDCARD" uid="shell" />
+ <assign-permission name="android.permission.WRITE_EXTERNAL_STORAGE" uid="shell" />
<assign-permission name="android.permission.SEND_SMS" uid="shell" />
<assign-permission name="android.permission.CALL_PHONE" uid="shell" />
<assign-permission name="android.permission.READ_CONTACTS" uid="shell" />
diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs
index da5192a1091d..da4a2c33c5cf 100644
--- a/docs/html/guide/guide_toc.cs
+++ b/docs/html/guide/guide_toc.cs
@@ -148,6 +148,7 @@
<ul>
<li><a href="<?cs var:toroot ?>guide/practices/ui_guidelines/icon_design.html">Icon Design</a></li>
<li><a href="<?cs var:toroot ?>guide/practices/ui_guidelines/widget_design.html">App Widget Design</a></li>
+ <li><a href="<?cs var:toroot ?>guide/practices/ui_guidelines/activity_task_design.html">Activity and Task Design</a></li>
</ul>
</li>
<li><a href="<?cs var:toroot ?>guide/practices/design/performance.html">Designing for Performance</a></li>
diff --git a/docs/html/guide/practices/ui_guidelines/activity_task_design.jd b/docs/html/guide/practices/ui_guidelines/activity_task_design.jd
new file mode 100644
index 000000000000..bec0e436a7a5
--- /dev/null
+++ b/docs/html/guide/practices/ui_guidelines/activity_task_design.jd
@@ -0,0 +1,1223 @@
+page.title=Activity and Task Design Guidelines
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>Activity and task design quickview</h2>
+
+<ul>
+<li>Activities are the main building blocks of Android applications. </li>
+<li>In addition to writing your own activities, you are free to re-use activities from many other applications through intents.</li>
+<li>You can enable activities in your application to be started from intents in other applications.</li>
+<li>In nearly all cases, the activity stack just works as expected.</li>
+ In a couple of cases you might need to ensure the right thing happens by setting a string or flag.</li>
+</ul>
+
+<h2>In this document</h2>
+
+<ol>
+ <li><a href=#applications_activities>Applications, Activities, Activity Stack and Tasks</a>
+ </li>
+ <li><a href=#tour>A Tour of Activities and Tasks
+ <ol>
+ <li><a href=#starting_an_activity_from_home>Starting an Activity from Home</a></li>
+ <li><a href=#navigating_away_from_an_activity>Navigating Away from an Activity</a></li>
+ <li><a href=#reusing_an_activity>Re-using an Activity</a></li>
+ <li><a href=#replacing_an_activity>Replacing an Activity</a></li>
+ <li><a href=#multitasking>Multitasking</a></li>
+ <li><a href=#launching_from_two_entry_points>Launching from Two Entry Points</a></li>
+ <li><a href=#intents>Intents</a></li>
+ <li><a href=#switching_between_tasks>Switching Between Tasks</a></li>
+ </ol>
+ </li>
+ <li><a href=#tips>Design Tips
+ <ol>
+ <li><a href=#activity_not_reused_tip>Don't specify intent filters in an activity that won't be re-used</a></li>
+ <li><a href=#others_to_reuse_tip>Don't define your own URI schemes</a></li>
+ <li><a href=#reusing_tip>Handle where a re-used activity is missing</a></li>
+ <li><a href=#activity_launching_tip>Consider how to launch your activities</a></li>
+ <li><a href=#activities_added_to_task_tip>Allow activities to add to current task</a></li>
+ <li><a href=#notifications_return_tip>Notifications should be easy to return from</a></li>
+ <li><a href=#use_notification_tip>Use the notification system</a></li>
+ <li><a href=#taking_over_back_key>Don't take over BACK key unless you absolutely need to</a></li>
+ </ol>
+ </li>
+</ol>
+
+<h2>See also</h2>
+
+<ol>
+ <li><a href="{@docRoot}guide/topics/fundamentals.html">Application Fundamentals</a></li>
+ <li><a href="http://android-developers.blogspot.com/2009/05/activities-and-tasks.html">Activities and Tasks blog post</a></li>
+</ol>
+
+</div>
+</div>
+
+<p>
+ This document describes core principles of the Android application
+ framework, from a high-level, user-centric perspective useful to
+ interaction and application designers as well as application
+ developers.
+</p>
+
+<p>
+ It illustrates activities and tasks with examples, and describes some
+ of their underlying principles and mechanisms, such as navigation,
+ multitasking, activity re-use, intents, and the activity stack.
+ The document also highlights design decisions that are available to you
+ and what control they give you over the UI of your application.
+</p>
+
+<p>
+ This document draws examples from several Android applications,
+ including default applications (such as Dialer) and Google
+ applications (such as Maps). You can try out the examples yourself in
+ the Android emulator or on an Android-powered device. If you are using
+ a device, note that your device may not offer all of the example
+ applications used in this document.
+</p>
+
+<p>
+ Be sure to look at the <a href="#design_tips">Design Tips</a> section
+ for guidelines, tips, and things to avoid. This document is a
+ complement to <a href={@docRoot}guide/topics/fundamentals.html
+ title="Application Fundamentals">Application Fundamentals</a>,
+ which covers the underlying mechanics for programmers.
+</p>
+
+<h2 id="applications_activities">Applications, Activities, Activity Stack and Tasks</h2>
+
+<p>
+ Four fundamental concepts in the Android system that are helpful for you to understand are:
+</p>
+
+<ul>
+ <li>Applications
+ <li>Activities
+ <li>Activity Stack
+ <li>Tasks
+</ul>
+
+<h3 id=applications>Applications</h3>
+
+<p>
+ An Android <em>application</em> typically consists of one or more
+ related, loosely bound activities <!--(and possibly
+ <a href=#services_broadcast_receivers title="other components">other
+ components</a>)--> for the user to interact with, typically bundled up
+ in a single file (with an .apk suffix). Android ships with a rich set
+ of applications that may include email, calendar, browser, maps, text
+ messaging, contacts, camera, dialer, music player, settings and
+ others.
+</p>
+
+<p>
+ Android has an application launcher available at the Home screen,
+ typically in a sliding drawer which displays applications as icons,
+ which the user can pick to start an application.
+</p>
+
+
+<h3 id=activities>Activities</h3>
+
+<p>
+ <em>Activities</em> are the main building blocks of Android
+ applications. When you create an application, you can assemble it from
+ activities that you create and from activities you re-use from other
+ applications. These activities are bound at runtime, so that newly
+ installed applications can take advantage of already installed
+ activities. Once assembled, activities work together to form a
+ cohesive user interface. An activity has a distinct visual user
+ interface designed around a single, well-bounded purpose, such as
+ viewing, editing, dialing the phone, taking a photo, searching,
+ sending data, starting a voice command, or performing some other type
+ of user action. Any application that presents anything on the display
+ must have at least one activity responsible for that display.
+</p>
+
+<p>
+ When using an Android device, as the user moves through the user
+ interface they start activities one after the other, totally oblivious
+ to the underlying behavior &mdash; to them the experience should be
+ seamless, activity after activity, <a href="#tasks">task</a> after
+ task.
+</p>
+
+<p>
+ An activity handles a particular type of content (data) and accepts a
+ set of related user actions. In general, each activity has a
+ <a href={@docRoot}guide/topics/fundamentals.html#actlife
+ title=lifecycle>lifecycle</a> that is independent of the other
+ activities in its application or task &mdash; each activity is
+ launched (started) independently, and the user or system can start,
+ run, pause, resume, stop and restart it as needed. Because of this
+ independence, activities can be re-used and replaced by other
+ activities in a variety of ways.
+</p>
+
+<p>
+ The Dialer application is an example of an application that consists
+ basically of four activities: dialer, contacts list, view contact, and
+ new contact, as shown in the following screenshots:
+</p>
+
+ <table style="border: none;">
+ <tbody>
+ <tr>
+ <td style="border: none !important;">
+ <img src={@docRoot}images/activity_task_design/ContactsDialer.png>
+ <div style=TEXT-ALIGN:center>
+ Dialer
+ </div>
+ </td>
+ <td style="border: none !important;">
+ <img src={@docRoot}images/activity_task_design/ContactsList.png>
+ <div style=TEXT-ALIGN:center>
+ Contacts
+ </div>
+ </td>
+ <td style="border: none !important;">
+ <img src={@docRoot}images/activity_task_design/ContactView.png>
+ <div style=TEXT-ALIGN:center>
+ View Contact
+ </div>
+ </td>
+ <td style="border: none !important;">
+ <img src={@docRoot}images/activity_task_design/ContactNew.png>
+ <div style=TEXT-ALIGN:center>
+ New Contact
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+<p>
+ Here are other examples of applications and the activities they might contain:
+</p>
+
+ <ul>
+ <li>
+ Email - activities to view folders, view list of messages,
+ view a message, compose a message, and set up an account
+ </li>
+ <li>
+ Calendar - activities to view day, view week, view month, view
+ agenda, edit an event, edit preferences, and view an alert
+ </li>
+ <li>
+ Camera - activities for running the camera, viewing the list
+ of pictures, viewing a picture, cropping a picture, running
+ the camcorder, viewing the list of movies, and viewing a movie
+ </li>
+ <li>
+ Game - one activity to play the game, typically another for setup
+ </li>
+ <li>
+ Maps - one activity to view a location on a map, a second for lists
+ (such as turn list or friend list), and a third for details
+ (friend location, status, photo)
+ </li>
+ </ul>
+
+
+<p>
+ An activity is the most prominent of four <em>components</em> of an
+ application. The other components are service, content provider and
+ broadcast receiver. For more details on activities, see Activity in
+ <a href={@docRoot}guide/topics/fundamentals.html#appcomp
+ title="Application Components">Application Components</a>.
+</p>
+
+
+<h3 id="activity_stack">Activity Stack</h3>
+
+<p>
+ As the user moves from activity to activity, across applications, the
+ Android system keeps a linear navigation history of activities the
+ user has visited. This is the <em>activity stack</em>, also known as the
+ back stack. In general, when a user starts a new activity, it is added
+ to the activity stack, so that pressing BACK displays the previous
+ activity on the stack. However, the user cannot use the BACK key to go
+ back further than the last visit to Home. The adding of an activity to
+ the current stack happens whether or not that activity begins a new
+ <a href=#tasks title=task>task</a> (as long as that task was started
+ without going Home), so going back can let the user go back to
+ activities in previous tasks. The user can get to tasks earlier than
+ the most recent Home by selecting its root activity from the
+ application launcher, a shortcut, or the "Recent tasks" screen.
+</p>
+
+<p>
+ Activities are the only things that can be added to the activity stack
+ &mdash; views, windows, menus, and dialogs cannot. That is, when
+ designing the navigation, if you have screen A and you want the user
+ to be able go to a subsequent screen B and then use the BACK key to go
+ back to screen A, then the screen A needs to be implemented as an
+ activity. The one exception to this rule is if your application
+ <a href=#taking_over_back_key title="takes control of the BACK key"
+ takes control of the BACK key</a> and manages the navigation itself.
+</p>
+
+
+
+<h3 id=tasks>Tasks</h3>
+
+<p>
+ A <em>task</em> is the sequence of activities the user follows to
+ accomplish an objective, regardless of which applications the
+ activities belong to. Until a new task is explicitly specified (see
+ "Interrupting the Task"), all activities the user starts are
+ considered to be part of the current task. It's notable that these
+ activities can be in any application &mdash; that is, all in the same
+ application or in different ones. That is, a task that starts out in
+ contacts can continue, by choosing an email address, to an email
+ activity and then, by attaching a file, to a picture gallery to pick
+ from. Contacts, email and picture gallery are all separate
+ applications.
+</p>
+
+<p>
+ The activity that starts a task is called the <em>root activity</em>.
+ It is often, but not necessarily, started from the application
+ launcher, Home screen shortcut or "Recent tasks" switcher (a long
+ press on Home on some devices). The user can return to a task by
+ choosing the icon for its root activity the same way they started the
+ task. Once inside a task, the BACK key goes to previous activities in
+ that task. The activity stack is made up of one or more tasks.
+</p>
+
+<p>
+ Here are some examples of tasks:
+</p>
+
+ <ul>
+ <li>
+ Send a text message with an attachment
+ </li>
+ <li>
+ View a YouTube video and share it by email with someone else
+ </li>
+ </ul>
+
+<p>
+ <b>Interrupting the Task</b> - An important property of a task is that
+ the user can interrupt what they're doing (their task) to perform a
+ different task, then are able to return to where they left off to
+ complete the original task. The idea is that users can run multiple
+ tasks simultaneously and switch between them. There are two primary
+ ways to jump off to that other task &mdash; in both cases the user
+ should be able to return to where they were before the interruption:
+</p>
+
+
+ <ul>
+ <li>
+ User is interrupted by a notification – a notification appears and the user wants to act on it
+ </li>
+ <li>
+ User deciding to perform another task – user just presses Home and starts an application
+ </li>
+ </ul>
+
+<p>
+ Of course, there are exceptions to the rules. Beyond the two ways just
+ mentioned, there is a third way to start a task, and that is by
+ starting an activity that defines itself as a new task. Maps and
+ Browser are two applications that do this. For example, choosing an
+ address in an email starts the Maps activity as a new task, and
+ choosing a link in an email starts the Browser activity as a new
+ task. In these cases, the BACK key will return to the previous
+ activity in a different task (Email), because it was not started from
+ Home.
+</p>
+
+
+<h2 id="tour">A Tour of Activities and Tasks</h2>
+
+<p>
+ The following examples illustrate basic principles for applications,
+ activities, the activity stack, the BACK key, tasks and intents. It
+ shows how the system responds to user actions such as starting
+ activities and switching between tasks. With most of these examples
+ you can follow along, launching activities on your device as
+ indicated.
+</p>
+
+
+<h3 id=starting_an_activity_from_home>Starting an Activity from Home</h3>
+
+<p>
+ Home is the starting place for most applications. (Some applications
+ can be launched only from other applications.) When the user touches
+ an icon in the application launcher (or a shortcut on the Home
+ screen), the main activity for that application is launched into the
+ foreground where it has user focus. As shown in the following figure,
+ the user action of going Home and touching the Email icon launches the
+ List Messages activity of the Email application. The Home activity
+ remains stopped in the background, ready to restart when called on by
+ the user.
+</p>
+
+<p>
+ <img src={@docRoot}images/activity_task_design/HomeTaskBasics1a.png>
+</p>
+
+<h3 id=navigating_away_from_an_activity>Navigating Away from an Activity with BACK and HOME keys</h3>
+
+<p>
+ An activity can keep or lose its state depending on how the user
+ leaves the activity &mdash; by the HOME or BACK key.
+</p>
+
+<p>
+ By default, pressing the BACK key finishes (destroys) the current
+ activity and displays the previous activity to the user. In the
+ following figure, the user starts email by touching the Email icon in
+ the Home screen, which displays a list of email messages. The user
+ scrolls down the list (changing its initial state). Pressing BACK
+ destroys the List Messages activity and returns to the previous
+ activity, which is Home. If the user re-launches Email, it would
+ re-load the messages and display its initial, non-scrolled state.
+</p>
+
+<p>
+ <img src={@docRoot}images/activity_task_design/HomeTaskBasics1b.png>
+</p>
+
+<p>
+ In the above example, pressing BACK goes to Home because it was the
+ last activity the user was viewing. But if the user had gotten to List
+ Message from some other activity, then pressing BACK would have
+ returned there.
+</p>
+
+<p>
+ By contrast, the next figure shows the user leaving List Messages by
+ pressing HOME instead of BACK &mdash; the List Messages activity is
+ stopped and moved to the background rather than being
+ destroyed. Starting Email again from its icon would simply bring the
+ List Messages activity to the foreground (changing it from stopped to
+ running) in the same scrolled state the user last left it.
+</p>
+
+<p>
+ <img src={@docRoot}images/activity_task_design/HomeTaskBasics1c.png>
+</p>
+
+<p>
+ <b>Exceptions.</b> Some background activities return to their initial
+ screen (they lose any state, such as scrolling) when they are brought
+ to the foreground. This is true for Contacts and Gallery. If the user
+ chooses Home &gt; Contacts then chooses a contact, they are viewing
+ the details of a contact. If they start again by choosing Home &gt;
+ Contacts, they are presented with the initial list of contacts rather
+ than the contact they were last viewing. Contacts is designed this way
+ because this initial screen is the main entry point for the
+ application with four tabs for accessing the full range of features.
+</p>
+
+<p>
+ In addition, not all activities have the behavior that they are
+ destroyed when BACK is pressed. When the user starts playing music in
+ the Music application and then presses BACK, the application overrides
+ the normal back behavior, preventing the player activity from being
+ destroyed, and continues playing music, even though its activity is no
+ longer visible &mdash; as a visual substitute, the Music application
+ places a notification in the status bar so the user still has an easy
+ way to get to the application to stop or control the music. Note that
+ you can write an activity to stop when its screen is no longer
+ visible, or to continue running in the background &mdash; the latter
+ was chosen for the music player.
+</p>
+
+
+<h3 id=reusing_an_activity>Re-using an Activity</h3>
+
+<p>
+ When activity A starts activity B in a different application, activity
+ B is said to be <em>re-used</em>. This use case normally takes place
+ because activity A is lacking a capability and can find it in activity B.
+</p>
+
+<p>
+ <b>Contacts Re-Uses Gallery to Get a Picture</b> - The Contacts
+ activity has a field for a picture of a contact, but the Gallery is
+ normally where pictures are kept. So Contacts can re-use the Gallery
+ activity to get a picture. This is a good example of re-use of the
+ Gallery activity. The following figure illustrates the sequence of
+ activities to do this (up to crop). This is how it's done: The user
+ chooses Contacts, selects the contact for viewing, chooses MENU &gt;
+ Edit contact and touches the picture field, which launches the Gallery
+ activity. The user then chooses the picture they want, crops and saves
+ it. Saving it causes the picture to be inserted into the picture field
+ in the contact.
+</p>
+
+<p>
+ Notice the Gallery returns a picture to the Contacts application that
+ started it. The next example illustrates re-use of an activity that
+ does not return a result. Also notice that the following figure is
+ illustrates the navigation history through the activities, or the
+ activity stack &mdash; the user can back up through each activity all
+ the way to Home.
+</p>
+
+<p>
+ When designing an application, it's good to think about how it can
+ re-use activities in other applications, and how your activities might
+ be re-used by other applications. If you add an activity with the same
+ <a href=#intents title="intent filter">intent filter</a> as an
+ exisiting activity, then the system presents the user with a choice
+ between the activities.
+</p>
+
+<p>
+ <img src={@docRoot}images/activity_task_design/ReusingAnActivity1.png>
+</p>
+
+<p>
+ <b>Gallery Re-Uses Messaging for Sharing a Picture</b> - Sharing is
+ another good example of one application re-using an activity from a
+ different application. As shown in the following figure, the user
+ starts Gallery, picks a picture to view, chooses MENU &gt; Share, and
+ picks "Messaging". This starts the Messaging activity, creates a new
+ message and attaches the original picture to it. The user then fills
+ in the "To" field, writes a short message and sends it. User focus
+ remains in the Messaging program. If the user wants to go back to the
+ Gallery, they must press the BACK key. (The user can back up through
+ each activity all the way to Home.)
+</p>
+
+<p>
+ In contrast to the previous example, this re-use of the Messaging
+ activity does not return anything to the Gallery activity that started it.
+</p>
+
+<p>
+ <img src={@docRoot}images/activity_task_design/ReusingAnActivity2.png>
+</p>
+
+<p>
+ Both of these examples illustrate tasks &mdash; a sequence of
+ activities that accomplish an objective. Each case uses activities
+ from two different applications to get the job done.
+</p>
+
+
+<h3 id=replacing_an_activity>Replacing an Activity</h3>
+
+<p>
+ This is the use case where activity A replaces activity B in a
+ different application. This situation normally happens because
+ activity A is better at doing the job than activity B. In other words,
+ A and B are equivalent enough that A can replace B. This case stands
+ in contrast with re-using an activity, where A and B are quite
+ different activities and supplement each other.
+</p>
+
+<p>
+ In this example, the user has downloaded a replacement for the Phone
+ Ringtone activity, called Rings Extended. Now when they go to
+ Settings, Sound &amp; Display, Phone Ringtone, the system presents
+ them with a choice between the Android System's ringtone activity and
+ the new one. This dialog box has an option to remember their choice
+ "Use by default for this action". When they choose "Rings Extended",
+ that activity loads, replacing the original Android ringtone activity.
+</p>
+
+<p>
+ <img src={@docRoot}images/activity_task_design/ReplacingAnActivity.png>
+</p>
+
+<h3 id=multitasking>Multitasking</h3>
+
+<p>
+ As previously noted, when an activity has been launched, the user can
+ go to Home and launch a second activity without destroying the first
+ activity. This scenario demonstrates launching the Maps application.
+</p>
+
+ <ul>
+ <li>
+ State 1 - The user launches the View Map activity and searches
+ for a map location. Let's say the network is slow, so the map is
+ taking an unusually long taking time to draw.
+ </li>
+ </ul>
+ <ul>
+ <li>
+ State 2 - The user wants to do something else while they're
+ waiting, so they press HOME, which does not interrupt the map's
+ network connection and allows the map to continue loading in the
+ background.
+
+ <p>
+ Note that when you write an activity, you can make it stop or
+ continue running when it is moved to the background (see
+ onStop() in <a href={@docRoot}guide/topics/fundamentals.html#actlife
+ title="Activity Lifecycle">Activity Lifecycle</a>).
+ For activities that download data from the network, it's recommended
+ to let them continue downloading so the user can multi-task.
+ </p>
+
+ </li>
+
+ <li>
+ State 3 - The map activity is now running in the background,
+ with Home in the foreground. The user then launches the Calendar
+ activity, which launches into the foreground, taking user focus,
+ where they view today's calendar (as indicated by the heavy
+ outline).
+ </li>
+ </ul>
+
+<p>
+<img src={@docRoot}images/activity_task_design/HomeTaskBasics1d.png>
+</p>
+ <ul>
+ <li>
+ State 4 - The user presses Home, then Maps to return to the map, which by now has fully loaded.
+ </li>
+ </ul>
+
+<p>
+ <img src={@docRoot}images/activity_task_design/HomeTaskBasics1e.png>
+</p>
+
+<p>
+ The application launcher at Home has launched "View Map" and "Day
+ View" activities into separate <em>tasks</em>, hence the system is
+ multitasking &mdash; running multiple <a href=#tasks
+ title=tasks>tasks</a>.
+</p>
+
+
+<h3 id=launching_from_two_entry_points>Launching from Two Entry Points</h3>
+
+<p>
+ Every application must have at least one entry point &mdash; a way
+ for the user or system to access activities inside the
+ application. Each icon in the application launcher at home
+ represents an entry point. Applications can also from another
+ application. Each activity is a potential entry point into the
+ application.&nbsp;
+</p>
+
+<p>
+ The phone application has two entry points: Contacts and Dialer. A
+ user entering from Contacts can choose a phone number to launch the
+ Dialer. As shown in the following figure, a user could choose the
+ Contacts icon to launch the Contacts activity, then pick a phone
+ number to launch the Dialer activity and dial the phone.
+</p>
+
+<p>
+ Once the user is inside the application, they can access other
+ activities, such as New Contact and Edit Contact, through tabs, menu
+ items, list items, onscreen buttons, or other user interface
+ controls.
+</p>
+
+<p>
+<img src={@docRoot}images/activity_task_design/PhoneActivitiesDiagram.png>
+</p>
+
+<h3 id=intents>Intents</h3>
+
+<p>
+ When the user takes an action on some data, such as touching a
+ mailto:info@example.com link, they are actually initiating an Intent
+ object which then gets resolved to a particular component (we will
+ consider only activity components here). So, the result of a user
+ touching a mailto: link is an Intent object that the system tries to
+ match to an activity. If that Intent object was written explicitly
+ naming an activity (an <em>explicit intent</em>), then the system
+ immediately launches that activity in response to the user
+ action. However, if that Intent object was written without naming an
+ activity (an <em>implicit intent</em>), the system compares the Intent
+ object to the <em>intent filters</em> of available activities. If more
+ than one activity can handle the action and data, the system
+ displays an activity chooser for the user to choose from.
+</p>
+
+<p>
+ This example of touching the mailto: link is shown in the following
+ figure. If the device has two email applications set up, when a user
+ touches a mailto: email address on a web page, the result is an
+ Intent object which displays a dialog box with a choice between the
+ two activities to compose an email (Gmail and Email).
+</p>
+
+<p>
+ <img src={@docRoot}images/activity_task_design/IntentsDiagram.png>
+</p>
+
+<p>
+ Here are some examples of Intent objects and the activities they resolve to:
+</p>
+
+ <ul>
+ <li>
+ View the list of contacts - resolves to a contact list viewer activity
+ </li>
+
+ <li>
+ View a particular contact - resolves to a contact viewer activity
+ </li>
+
+ <li>
+ Edit a particular contact - resolves to a contact editor activity
+ </li>
+
+ <li>
+ Send to a particular email - resolves to an email activity
+ </li>
+
+ <li>
+ Dial a phone number - resolves to a phone dialer activity
+ </li>
+
+ <li>
+ View the list of images - resolves to an image list viewer activity
+ </li>
+
+ <li>
+ View a particular image - resolves to an image viewer activity
+ </li>
+
+ <li>
+ Crop a particular image - resolves to an image cropper activity
+ </li>
+ </ul>
+
+<p>
+ Notice that an Intent object specifies two things, an action and data:
+</p>
+
+ <ul>
+ <li>
+ A generic action to be performed. In these examples: view, edit, dial or crop
+ </li>
+
+ <li>
+ The specific data to be acted on. In these examples: the list of contacts, a particular contact, a phone number, the list of images, or a particular image
+ </li>
+ </ul>
+
+ <p>
+ Note that any user action to start an activity from the
+ application launcher at Home is an explicit intent to a specific
+ activity. Likewise, some activities launch private activities
+ within their application as explicit intents so no other activity
+ can access them.
+ </p>
+
+ <p>
+ For more on intents, see {@link android.content.Intent Intent class} and
+ <a href={@docRoot}guide/topics/fundamentals.html#ifilters
+ title="intent filters">intent filters</a>.
+ </p>
+
+
+<h3 id=switching_between_tasks>Switching Between Tasks</h3>
+
+<p>
+ This scenario shows how the user can switch between two tasks. In
+ this example, the user writes a text message, attaches a picture,
+ but before they are done they glance at their calendar. They then
+ return to where they left off, attaching the picture and sending the
+ message.
+</p>
+
+ <ol>
+ <li>
+ <b>Start first task.</b> You want to send a text message and attach a photo. You would choose:
+
+ <p>
+ Home &gt; Messaging &gt; New message &gt; MENU &gt; Attach
+ &gt; Pictures. This last step launches the picture gallery
+ for picking a photo. Notice that picture gallery is an
+ activity in a separate application.
+ </p>
+
+
+ <table>
+ <tbody>
+ <tr>
+ <td valign=top style="border: none !important;">
+ <img src={@docRoot}images/activity_task_design/HomeTaskSwitching1a.png>
+ </td>
+ <td valign=top style="border: none !important;">
+ <img src={@docRoot}images/activity_task_design/HomeTaskSwitching1b.png>
+ </td>
+ <td valign=top style="border: none !important;">
+ <img src={@docRoot}images/activity_task_design/HomeTaskSwitching1c.png>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <p>
+ At this point, before you have picked a picture, you decide
+ to stop and glance at your calendar, which is a separate
+ task. Because the current activity has no button to go
+ directly to the Calendar, you need to start from Home.
+ </p>
+
+ </li>
+ <li>
+ <b>Start second task.</b> You choose Home &gt; Calendar to
+ look at a calendar event. Calendar launches from Home as a new
+ task because the application launcher creates a new task for
+ each application it launches.
+
+ <p>
+ <img src={@docRoot}images/activity_task_design/HomeTaskSwitching2.png>
+ </p>
+ </li>
+
+ <li>
+ <b>Switch to first task and complete it.</b> When done looking
+ at the Calendar, you can return to attaching the picture by
+ starting the root activity again for that task: choose Home
+ &gt; Messaging, which takes you not to Messaging, but directly
+ to the Picture gallery, where you left off. You can then pick
+ a photo, which is added to the message, you send the message
+ and you're done with the first task.
+
+ <table>
+ <tbody>
+ <tr>
+
+ <td valign=top style="border: none !important;">
+ <img src={@docRoot}images/activity_task_design/HomeTaskSwitching3a.png>
+ </td>
+
+ <td valign=top style="border: none !important;">
+ <img src={@docRoot}images/activity_task_design/HomeTaskSwitching3b.png>
+ </td>
+
+ <td valign=top style="border: none !important;">
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src={@docRoot}images/activity_task_design/HomeTaskSwitching3c.png>
+ </td>
+
+ </tr>
+ </tbody>
+ </table>
+ </li>
+ </ol>
+
+
+<h2 id="tips">Design Tips</h2>
+
+<p>
+ The following are tips and guidelines for application designers and developers.
+</p>
+
+<h3 id=activity_not_reused_tip>When writing an activity that won't be re-used, don't specify intent filters &mdash; use explicit intents</h3>
+
+<p>
+ If you're writing an activity that you don't want other activities
+ to use, be sure not to add any intent filters to that activity. This
+ applies to an activity that will be launched only from the
+ application launcher or from other activities inside your
+ application. Instead, just create intents specifying the explicit
+ component to launch &mdash; that is, explicit intents. In this case,
+ there's just no need for intent filters. Intent filters are
+ published to all other applications, so if you make an intent
+ filter, what you're doing is publishing access to your activity,
+ which means you can cause unintentional security holes.
+</p>
+
+<!--
+<h3 id="others_to_reuse_tip">When writing an activity for others to re-use, don't define your own URI schemes</h3>
+
+<p>
+ If publishing to others, don't define your own URI schemes in an
+ Intent type. Schemes (such as http: and mailto:) are an Internet
+ standard with a universal namespace outside of just Android, so you
+ aren't allowed to just make up your own. Instead, you should just
+ define your own actions. The action namespace is designed to not
+ have conflicts. Typically, your activity has one scheme with many
+ different actions.
+</p>
+
+<p>
+ Example: You want to show the user a bar code for some text. The
+ wrong way to do this is for the intent filter protocol to be
+ &lt;action android:name="android.intent.action.VIEW" /&gt; and
+ &lt;data android:scheme="barcode" /&gt;. Do not do this.
+</p>
+
+<p>
+ Instead you should define &lt;action
+ android:name="com.example.action.SHOW_BARCODE" /&gt; and have the
+ invoker supply the data as an extra field in the Intent object.
+</p>
+
+<p>
+ Be aware this intent filter protocol
+ ("com.example.action.SHOW_BARCODE", in this example) is a public API
+ that you can't change once it's defined. You must support it in the
+ future because others are going to be relying on it. If you want to
+ add new features that are incompatible with the current protocol,
+ just define a new protocol and continue to support the old one.
+</p>
+-->
+
+<h3 id="reusing_tip"> When reusing an activity owned by others, handle the case where no activity matches</h3>
+
+<p>
+ Your applications can re-use activities made available from other
+ applications. In doing so, you cannot presume that external activity
+ will always be present &mdash; you must handle the case that the
+ external activity is not installed. Do this in the way you find most
+ appropriate, such as dimming the user control that accesses it (such
+ as a button or menu item), or displaying a message to the user that
+ sends them to the location to download it, such as the Market.
+</p>
+
+<h3 id=activity_launching_tip>Consider how you want your activities to be launched or used by other applications</h3>
+
+<p>
+ As a designer or developer, it's up to you to determine how users
+ start your application and the activities in it. As an application
+ is a set of activities, the user can start these activities from
+ Home or from another application.
+</p>
+
+ <ul>
+ <li>
+ <b>Launch your main activity from an icon at Home </b>- If
+ your application can run standalone, it should probably be
+ started by the user touching an icon in <em>application
+ launcher</em> (typically implemented as a sliding drawer on the
+ Home screen), or from a shortcut icon on the Home screen, or
+ from the task switcher. (The mechanism for this is for the
+ activity to have an
+ <a href={@docRoot}guide/topics/fundamentals.html#ifilters
+ title="Intent filter">intent filter</a> with action MAIN and
+ category LAUNCHER.)
+ </li>
+ </ul>
+
+ <ul>
+ <li>
+ <b>Launch your activity from within another application</b> -
+ Perhaps your activities are meant for re-use. For example,
+ many applications have data they want to share with other
+ users. Activities that can share data with other users include
+ email, text messaging and uploading to a public website. <p>
+ If one or more of your activities can be an alternative to an
+ existing activity in another application, you can make it
+ available to users at the point they request that
+ activity.&nbsp;For example, if your activity can send data to
+ others (such as by email, text messaging, or uploading),
+ consider setting up that activity to appear as a choice to the
+ user. To give a specific example, Gallery enables a user to
+ view and share pictures. When the user chooses "Share" from
+ the menus, the system compares the "Share" request (an Intent
+ object) to available activities (by looking at their intent
+ filters) and displays choices to share. In this case, it
+ matches Email, Gmail, Messaging and Picassa. If your activity
+ can send a picture or upload it to a website, all it needs to
+ do is make itself available for sharing (by setting its intent
+ filter).
+ </p>
+<p>
+ Another activity can start your activity either with or without expecting a result back.&nbsp;
+</p>
+ </li>
+
+ <ul>
+ <li>
+ <b>Start an activity expecting a result</b> - This approach
+ is closed loop, where the activity being started must either
+ return a valid result or be canceled. In the previous
+ examples of sharing a photo from a Gallery, the user ends up
+ back in the Gallery after completing the send or upload
+ procedure. These are examples of starting an activity
+ external to the Gallery. (Such an activity is started with
+ {@link
+ android.app.Activity#startActivityForResult(android.content.Intent,
+ int) startActivityForResult()}.)
+ </li>
+
+ <li>
+ <b>Start an activity not expecting a result</b> - This
+ approach is open-ended. An example is choosing an house
+ address in an email message (or web page), where the Maps
+ activity is started to map the location. No result from maps
+ is expected to be returned to the email message; the user
+ can return by pressing the BACK key. (Such an activity is
+ started with {@link
+ android.content.Context#startActivity(android.content.Intent)
+ startActivity()}.)
+ </li>
+ </ul>
+
+ <li>
+ <b>Launch your activity <em>only</em> from within another
+ application</b> - The previous cases of sharing by way of
+ Email, Gmail, Messaging and Picassa (from within Gallery) are
+ all activities that can also be started from icons in the
+ application launcher at Home. In contrast, the activities for
+ cropping a picture and attaching a file cannot be started from
+ Home, because they do not stand alone and require a
+ context.&nbsp;
+ </li>
+
+<p>
+ In fact, not all applications have icons and can be started from
+ Home. Take for example a small app that is infrequently used and
+ replaces existing functionality, that already has a natural entry
+ point inside an existing application. For example, an Android phone
+ typically has a built-in ringtone picker that can be selected from
+ the sound settings of the Settings application. A custom ringtone
+ picker application that you write could be launched by an intent
+ identical to the built-in ringtone picker. At the point where the
+ user chooses "Phone ringtone", they are presented with a dialog
+ letting them choose between "Android System" and your ringtone
+ picker (and letting them save their choice) as shown in the
+ following figure. A ringtone is something you set infrequently, and
+ already has a well-defined starting point, so probably does not need
+ an application icon at Home.
+</p>
+
+<p>
+ <img src={@docRoot}images/activity_task_design/ActivityChooser.png>
+</p>
+
+ <li>
+ <b>Launch two or more main activities within a single
+ application from separate icon at Home</b> - As we have
+ defined it, all the code in a single .apk file is considered
+ to be one <em>application.</em> You can write an application
+ that contains two main activities launchable from Home.
+ </li>
+
+<p>
+ The Camera.apk application is a good example of an application that
+ contains two independent main activities &mdash; Camera and
+ Camcorder &mdash; that each have their own icons in application
+ launcher, that can be launched separately, and so appear to the user
+ as separate applications. They both share use of the same lens, and
+ both store their images (still and moving) in the Gallery.&nbsp;
+</p>
+
+<p>
+ In order for your application to contain two different, independent
+ activities launchable from Home, you must define them to be
+ associated with different tasks. (This means setting the main
+ activity for each task to a different <!--a href=#affinities
+ title=affinity-->task affinity<!--/a--> &mdash; in this case,
+ "com.android.camera" and "com.android.videocamera".)
+</p>
+
+<p>
+ Contacts and Dialer are another example of two main activities
+ launchable from Home that reside in the same application.
+</p>
+
+ <li>
+ <b>Making your application available as a widget</b> - An
+ application can also display a portion of itself as an <a
+ href={@docRoot}guide/topics/appwidgets/index.html title="app
+ widget">app widget</a>, embedded in Home or another
+ application, and receive periodic updates.
+ </li>
+
+ </ul>
+
+
+<h3 id=activities_added_to_task_tip>Allow your activities to be added to the current task</h3>
+
+<p>
+ If your activities can be started from another application, allow
+ them to be added to the current <a href=#tasks title=Tasks>task</a>
+ (or an existing task it has an affinity with). Having activities
+ added to a task enables the user to switch between a task that
+ contains your activities and other tasks. <!--See <a href=#tasks
+ title=Tasks>Tasks</a> for a fuller explanation.--> Exceptions are
+ your activities that have only one instance.&nbsp;
+</p>
+
+<p>
+ For this behavior, your activity should have a <!--a
+ href=launch_modes title="standard or singleTop"-->launch
+ mode<!--/a--> of standard or singleTop rather than singleTask or
+ singleInstance. These modes also enable multiple instances of your
+ activity to be run.
+</p>
+
+
+<h3 id="notifications_return_tip">Notifications should be easy for the user to return from</h3>
+
+<p>
+ Applications that are in the background or haven't been run can
+ send out notifications to the user letting them know about events
+ of interest. For example, Calendar can send out notifications of
+ upcoming events, and Email can send out notifications when new
+ messages arrive. One of the user interface rules is that when the
+ user is in activity A and gets a notification for activity B and
+ picks that notification, when they press the BACK key, they should
+ go back to activity A.&nbsp;
+</p>
+
+<p>
+ The following scenario shows how the activity stack should work
+ when the user responds to a notification.
+</p>
+
+<ol>
+ <li>
+ User is creating a new event in Calendar. They realize they
+ need to copy part of an email message into this event
+ </li>
+ <li>
+ The user chooses Home &gt; Gmail
+ </li>
+ <li>
+ While in Gmail, they receive a notification from Calendar for an upcoming meeting
+ </li>
+ <li>
+ So they choose that notification, which takes them to a
+ dedicated Calendar activity that displays brief details of the
+ upcoming meeting
+ </li>
+ <li>
+ The user chooses this short notice to view further details
+ </li>
+ <li>
+ When done viewing the event, the user presses the BACK
+ key. They should be taken to Gmail, which is where they were
+ when they took the notification
+ </li>
+</ol>
+
+<p>
+This behavior doesn't necessarily happen by default.
+</p>
+
+<p>
+Notifications generally happen primarily in one of two ways:
+</p>
+
+ <ul>
+ <li>
+ <b>The application has a dedicated activity for
+ notification</b> - For example, when the user receives a
+ Calendar notification, the act of selecting that
+ notification starts a special activity that displays a list
+ of upcoming calendar events &mdash; a view available only
+ from the notification, not through the Calendar's own user
+ interface. After viewing this upcoming event, to ensure that
+ the user pressing the BACK key will return to the activity
+ the user was in when they picked the notification, you would
+ make sure this dedicated activity does not have the same
+ task affinity as the Calendar or any other activity. (You do
+ this by setting task affinity to the empty string, which
+ means it has no affinity to anything.) The explanation for
+ this follows.
+
+ <p>
+ Because of the way tasks work, if the taskAffinity of the
+ dedicated activity is kept as its default, then pressing the
+ BACK key (in step 6, above) would go to Calendar, rather
+ than Gmail. The reason is that, by default, all activities
+ in a given application have the same task
+ affinity. Therefore, the task affinity of the dedicated
+ activity matches the Calendar task, which is already running
+ in step 1. This means in step 4, choosing the notification
+ brings the existing Calendar event (in step 1) forward and
+ starts the dedicated activity on top of it. This is not
+ what you want to have happen. Setting the dedicated
+ activity's taskAffinity to empty string fixes this.
+ </p>
+ </li>
+
+ <li>
+ <b>The user choosing the notification brings the activity to
+ the foreground in its initial state</b> - For example, in
+ response to a notification, the Gmail application is brought
+ to the foreground presenting the list of conversations. You
+ do this by having the user's response to the notification
+ trigger an intent to launch the activity with the clear top
+ flag set. (That is, you put {@link
+ android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP
+ FLAG_ACTIVITY_CLEAR_TOP} in the intent you pass to
+ startActivity()). This prevents Gmail from coming to the
+ foreground in whatever state the user last happened to be
+ viewing it.
+ </li>
+ </ul>
+
+<p>
+ There are other ways to handle notifications, such as bringing the
+ activity to the foreground set to display specific data, such as the
+ ongoing text message thread of a particular person.
+</p>
+
+<p>
+ A notification always starts an activity as a new task (that is, it
+ puts <font size=1>FLAG_ACTIVITY_NEW_TASK</font> in the intent it
+ passes to startActivity()). This is done because interruptions to a
+ task should not become part of that task.
+</p>
+
+<h3 id=use_notification_tip>Use the notification system &mdash; don't use dialog boxes in place of notifications</h3>
+
+<p>
+ If your background service needs to notify a user, use the standard
+ notification system &mdash; don't use a dialog or toast to notify
+ them. A dialog or toast would immediately take focus and interrupt
+ the user, taking focus away from what they were doing: the user
+ could be in the middle of typing text the moment the dialog appears
+ and could accidentally act on the dialog. Users are used to dealing
+ with notifications and can pull down the notification shade at their
+ convenience to respond to your message.
+</p>
+
+<h3 id=taking_over_back_key>Don't take over the BACK key unless you absolutely need to</h3>
+
+<p>
+ As a user navigates from one activity to the next, the system adds
+ them to the activity stack. This forms a navigation history that is
+ accessible with the BACK key. Most activities are relatively limited
+ in scope, with just one set of data, such as viewing a list of
+ contacts, composing an email, or taking a photo. But what if your
+ application is one big activity with several pages of content and
+ needs finer-grained control of the BACK key? Examples of such Google
+ applications are the Browser, which can have several web pages open
+ at once, and Maps, which can have several layers of geographic data
+ to switch between. Both of these applications take control of the
+ BACK key and maintain their own internal back stacks that operate
+ only when these applications have focus.
+</p>
+
+<p>
+ For example, Maps uses <em>layers</em> to present different
+ information on a map to the user: displaying the location of a
+ search result, displaying locations of friends, and displaying a
+ line for a street path providing direction between points. Maps
+ stores these layers in its own history so the BACK key can return to
+ a previous layer.
+</p>
+
+<p>
+ Similarly, Browser uses <em>browser windows</em> to present different
+ web pages to the user. Each window has its own navigation history,
+ equivalent to tabs in a browser in a desktop operating system (such
+ as Windows, Macintosh or Linux). For example, if you did a Google
+ web search in one window of the Android Browser, clicking on a link
+ in the search results displays a web page in that same window, and
+ then pressing BACK would to the search results page. Pressing
+ BACK goes to a previous window only if the current window was
+ launched from that previous window. If the user keeps pressing
+ back, they will eventually leave the browser activity and return
+ Home.
+</p>
+
diff --git a/docs/html/guide/practices/ui_guidelines/index.jd b/docs/html/guide/practices/ui_guidelines/index.jd
index 61e310a8ea67..0b9d2754d21a 100644
--- a/docs/html/guide/practices/ui_guidelines/index.jd
+++ b/docs/html/guide/practices/ui_guidelines/index.jd
@@ -22,12 +22,24 @@ The Icon Templates Pack is an archive of Photoshop and Illustrator templates and
filters that make it much simpler to create conforming icons.</dd>
</dl>
<dl>
- <dt><a href="widget_design.html">Widget Design Guidelines</a> </dt>
+ <dt><a href="{@docRoot}guide/practices/ui_guidelines/widget_design.html">Widget Design Guidelines</a> </dt>
<dd>A widget displays an application's most important or timely information
at a glance, on a user's Home screen. These design guidelines describe how to
design widgets that fit with others on the Home screen. They include links to
graphics files and templates that will make your designer's life easier.</dd>
</dl>
+ <dl>
+ <dt><a href="{@docRoot}guide/practices/ui_guidelines/activity_task_design.html">Activity and Task Design Guidelines</a> </dt>
+ <dd>Activities are the basic, independent building blocks of applications.
+ As you design your application's UI and feature set, you are free to
+ re-use activities from other applications as if they were yours,
+ to enrich and extend your application. These guidelines
+ describe how activities work, illustrates them with examples, and
+ describes important underlying principles and mechanisms, such as
+ multitasking, activity reuse, intents, the activity stack, and
+ tasks. It covers this all from a high-level design perspective.
+</dd>
+</dl>
diff --git a/docs/html/images/activity_task_design/ActivityChooser.png b/docs/html/images/activity_task_design/ActivityChooser.png
new file mode 100644
index 000000000000..6c20afbbd21c
--- /dev/null
+++ b/docs/html/images/activity_task_design/ActivityChooser.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/ContactNew.png b/docs/html/images/activity_task_design/ContactNew.png
new file mode 100644
index 000000000000..decaaeb0b1e4
--- /dev/null
+++ b/docs/html/images/activity_task_design/ContactNew.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/ContactView.png b/docs/html/images/activity_task_design/ContactView.png
new file mode 100644
index 000000000000..5eff2ba6d1fc
--- /dev/null
+++ b/docs/html/images/activity_task_design/ContactView.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/ContactsDialer.png b/docs/html/images/activity_task_design/ContactsDialer.png
new file mode 100644
index 000000000000..28794b7f2576
--- /dev/null
+++ b/docs/html/images/activity_task_design/ContactsDialer.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/ContactsList.png b/docs/html/images/activity_task_design/ContactsList.png
new file mode 100644
index 000000000000..ef1b83f48db1
--- /dev/null
+++ b/docs/html/images/activity_task_design/ContactsList.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/HomeTaskBasics1a.png b/docs/html/images/activity_task_design/HomeTaskBasics1a.png
new file mode 100644
index 000000000000..eca480705b12
--- /dev/null
+++ b/docs/html/images/activity_task_design/HomeTaskBasics1a.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/HomeTaskBasics1b.png b/docs/html/images/activity_task_design/HomeTaskBasics1b.png
new file mode 100644
index 000000000000..ce76d634f341
--- /dev/null
+++ b/docs/html/images/activity_task_design/HomeTaskBasics1b.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/HomeTaskBasics1c.png b/docs/html/images/activity_task_design/HomeTaskBasics1c.png
new file mode 100644
index 000000000000..95f48c1084b4
--- /dev/null
+++ b/docs/html/images/activity_task_design/HomeTaskBasics1c.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/HomeTaskBasics1d.png b/docs/html/images/activity_task_design/HomeTaskBasics1d.png
new file mode 100644
index 000000000000..bbb96d9b33d9
--- /dev/null
+++ b/docs/html/images/activity_task_design/HomeTaskBasics1d.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/HomeTaskBasics1e.png b/docs/html/images/activity_task_design/HomeTaskBasics1e.png
new file mode 100644
index 000000000000..09dd491cf335
--- /dev/null
+++ b/docs/html/images/activity_task_design/HomeTaskBasics1e.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/HomeTaskSwitching1a.png b/docs/html/images/activity_task_design/HomeTaskSwitching1a.png
new file mode 100644
index 000000000000..de79aaf953ea
--- /dev/null
+++ b/docs/html/images/activity_task_design/HomeTaskSwitching1a.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/HomeTaskSwitching1b.png b/docs/html/images/activity_task_design/HomeTaskSwitching1b.png
new file mode 100644
index 000000000000..bce77724a8be
--- /dev/null
+++ b/docs/html/images/activity_task_design/HomeTaskSwitching1b.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/HomeTaskSwitching1c.png b/docs/html/images/activity_task_design/HomeTaskSwitching1c.png
new file mode 100644
index 000000000000..8209f2fbab0b
--- /dev/null
+++ b/docs/html/images/activity_task_design/HomeTaskSwitching1c.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/HomeTaskSwitching2.png b/docs/html/images/activity_task_design/HomeTaskSwitching2.png
new file mode 100644
index 000000000000..dee58a397a94
--- /dev/null
+++ b/docs/html/images/activity_task_design/HomeTaskSwitching2.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/HomeTaskSwitching3a.png b/docs/html/images/activity_task_design/HomeTaskSwitching3a.png
new file mode 100644
index 000000000000..0c90a86a1ddb
--- /dev/null
+++ b/docs/html/images/activity_task_design/HomeTaskSwitching3a.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/HomeTaskSwitching3b.png b/docs/html/images/activity_task_design/HomeTaskSwitching3b.png
new file mode 100644
index 000000000000..4a16e69ddb46
--- /dev/null
+++ b/docs/html/images/activity_task_design/HomeTaskSwitching3b.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/HomeTaskSwitching3c.png b/docs/html/images/activity_task_design/HomeTaskSwitching3c.png
new file mode 100644
index 000000000000..d7789aa8f31f
--- /dev/null
+++ b/docs/html/images/activity_task_design/HomeTaskSwitching3c.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/IntentsDiagram.png b/docs/html/images/activity_task_design/IntentsDiagram.png
new file mode 100644
index 000000000000..0ed366ff721d
--- /dev/null
+++ b/docs/html/images/activity_task_design/IntentsDiagram.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/PhoneActivitiesDiagram.png b/docs/html/images/activity_task_design/PhoneActivitiesDiagram.png
new file mode 100644
index 000000000000..8d346c178869
--- /dev/null
+++ b/docs/html/images/activity_task_design/PhoneActivitiesDiagram.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/ReplacingAnActivity.png b/docs/html/images/activity_task_design/ReplacingAnActivity.png
new file mode 100644
index 000000000000..03b4d929a747
--- /dev/null
+++ b/docs/html/images/activity_task_design/ReplacingAnActivity.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/ReusingAnActivity1.png b/docs/html/images/activity_task_design/ReusingAnActivity1.png
new file mode 100644
index 000000000000..01c1729bcbc8
--- /dev/null
+++ b/docs/html/images/activity_task_design/ReusingAnActivity1.png
Binary files differ
diff --git a/docs/html/images/activity_task_design/ReusingAnActivity2.png b/docs/html/images/activity_task_design/ReusingAnActivity2.png
new file mode 100644
index 000000000000..288d2da757d0
--- /dev/null
+++ b/docs/html/images/activity_task_design/ReusingAnActivity2.png
Binary files differ
diff --git a/include/tts/TtsEngine.h b/include/tts/TtsEngine.h
index 06f382003055..bf629958ba78 100644
--- a/include/tts/TtsEngine.h
+++ b/include/tts/TtsEngine.h
@@ -53,7 +53,8 @@ enum tts_result {
TTS_FEATURE_UNSUPPORTED = -2,
TTS_VALUE_INVALID = -3,
TTS_PROPERTY_UNSUPPORTED = -4,
- TTS_PROPERTY_SIZE_TOO_SMALL = -5
+ TTS_PROPERTY_SIZE_TOO_SMALL = -5,
+ TTS_MISSING_RESOURCES = -6
};
class TtsEngine
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 0812650452bc..ce80f92dbb0e 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -44,23 +44,27 @@ SoundPool::SoundPool(jobject soundPoolRef, int maxChannels, int streamType, int
LOGV("SoundPool constructor: maxChannels=%d, streamType=%d, srcQuality=%d",
maxChannels, streamType, srcQuality);
- if (maxChannels > 32) {
- LOGW("App requested %d channels, capped at 32", maxChannels);
- maxChannels = 32;
+ // check limits
+ mMaxChannels = maxChannels;
+ if (mMaxChannels < 1) {
+ mMaxChannels = 1;
+ }
+ else if (mMaxChannels > 32) {
+ mMaxChannels = 32;
}
+ LOGW_IF(maxChannels != mMaxChannels, "App requested %d channels", maxChannels);
mQuit = false;
mSoundPoolRef = soundPoolRef;
mDecodeThread = 0;
- mMaxChannels = maxChannels;
mStreamType = streamType;
mSrcQuality = srcQuality;
mAllocated = 0;
mNextSampleID = 0;
mNextChannelID = 0;
- mChannelPool = new SoundChannel[maxChannels];
- for (int i = 0; i < maxChannels; ++i) {
+ mChannelPool = new SoundChannel[mMaxChannels];
+ for (int i = 0; i < mMaxChannels; ++i) {
mChannelPool[i].init(this);
mChannels.push_back(&mChannelPool[i]);
}
diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml
index ac59799a2e10..690822013a68 100644
--- a/media/tests/MediaFrameworkTest/AndroidManifest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml
@@ -19,7 +19,7 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
- <uses-permission android:name="android.permission.WRITE_SDCARD" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application>
<uses-library android:name="android.test.runner" />
<activity android:label="@string/app_name"
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index e3fff81f3546..82ed1e39e0e5 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -16,17 +16,16 @@
package com.android.server;
-import android.backup.BackupService;
-import android.backup.IBackupService;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.IApplicationThread;
+import android.app.IBackupAgent;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -49,6 +48,7 @@ import java.lang.String;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
class BackupManagerService extends IBackupManager.Stub {
@@ -62,22 +62,28 @@ class BackupManagerService extends IBackupManager.Stub {
private Context mContext;
private PackageManager mPackageManager;
+ private final IActivityManager mActivityManager;
private final BackupHandler mBackupHandler = new BackupHandler();
// map UIDs to the set of backup client services within that UID's app set
- private SparseArray<HashSet<ServiceInfo>> mBackupParticipants
- = new SparseArray<HashSet<ServiceInfo>>();
+ private SparseArray<HashSet<ApplicationInfo>> mBackupParticipants
+ = new SparseArray<HashSet<ApplicationInfo>>();
// set of backup services that have pending changes
private class BackupRequest {
- public ServiceInfo service;
+ public ApplicationInfo appInfo;
public boolean fullBackup;
- BackupRequest(ServiceInfo svc, boolean isFull) {
- service = svc;
+ BackupRequest(ApplicationInfo app, boolean isFull) {
+ appInfo = app;
fullBackup = isFull;
}
+
+ public String toString() {
+ return "BackupRequest{app=" + appInfo + " full=" + fullBackup + "}";
+ }
}
// Backups that we haven't started yet.
- private HashMap<ComponentName,BackupRequest> mPendingBackups = new HashMap();
+ private HashMap<ApplicationInfo,BackupRequest> mPendingBackups
+ = new HashMap<ApplicationInfo,BackupRequest>();
// Backups that we have started. These are separate to prevent starvation
// if an app keeps re-enqueuing itself.
private ArrayList<BackupRequest> mBackupQueue;
@@ -89,6 +95,7 @@ class BackupManagerService extends IBackupManager.Stub {
public BackupManagerService(Context context) {
mContext = context;
mPackageManager = context.getPackageManager();
+ mActivityManager = ActivityManagerNative.getDefault();
// Set up our bookkeeping
mStateDir = new File(Environment.getDataDirectory(), "backup");
@@ -151,7 +158,7 @@ class BackupManagerService extends IBackupManager.Stub {
// ----- Run the actual backup process asynchronously -----
- private class BackupHandler extends Handler implements ServiceConnection {
+ private class BackupHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -163,31 +170,20 @@ class BackupManagerService extends IBackupManager.Stub {
for (BackupRequest b: mPendingBackups.values()) {
mBackupQueue.add(b);
}
- mPendingBackups = new HashMap<ComponentName,BackupRequest>();
+ mPendingBackups = new HashMap<ApplicationInfo,BackupRequest>();
}
// !!! TODO: start a new backup-queue journal file too
// WARNING: If we crash after this line, anything in mPendingBackups will
// be lost. FIX THIS.
}
- startOneService();
+ startOneAgent();
break;
}
}
-
- public void onServiceConnected(ComponentName name, IBinder service) {
- Log.d(TAG, "onServiceConnected name=" + name + " service=" + service);
- IBackupService bs = IBackupService.Stub.asInterface(service);
- processOneBackup(name, bs);
- }
-
- public void onServiceDisconnected(ComponentName name) {
- // TODO: handle backup being interrupted
- }
}
- void startOneService() {
+ void startOneAgent() {
// Loop until we find someone to start or the queue empties out.
- Intent intent = new Intent(BackupService.SERVICE_ACTION);
while (true) {
BackupRequest request;
synchronized (mQueueLock) {
@@ -205,14 +201,19 @@ class BackupManagerService extends IBackupManager.Stub {
// Take it off the queue when we're done.
}
- intent.setClassName(request.service.packageName, request.service.name);
- Log.d(TAG, "binding to " + intent);
+ Log.d(TAG, "starting agent for " + request);
+ // !!! TODO: need to handle the restore case?
+ int mode = (request.fullBackup)
+ ? IApplicationThread.BACKUP_MODE_FULL
+ : IApplicationThread.BACKUP_MODE_INCREMENTAL;
try {
- if (mContext.bindService(intent, mBackupHandler, Context.BIND_AUTO_CREATE)) {
- Log.d(TAG, "awaiting service object for " + intent);
+ if (mActivityManager.bindBackupAgent(request.appInfo, mode)) {
+ Log.d(TAG, "awaiting agent for " + request);
// success
return;
}
+ } catch (RemoteException e) {
+ // can't happen; activity manager is local
} catch (SecurityException ex) {
// Try for the next one.
Log.d(TAG, "error in bind", ex);
@@ -220,23 +221,23 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
- void processOneBackup(ComponentName name, IBackupService bs) {
- try {
- Log.d(TAG, "processOneBackup doBackup() on " + name);
+ void processOneBackup(String packageName, IBackupAgent bs) {
+ Log.d(TAG, "processOneBackup doBackup() on " + packageName);
- BackupRequest request;
- synchronized (mQueueLock) {
- if (mBackupQueue == null) {
- Log.d(TAG, "mBackupQueue is null. WHY?");
- }
- request = mBackupQueue.get(0);
+ BackupRequest request;
+ synchronized (mQueueLock) {
+ if (mBackupQueue == null) {
+ Log.d(TAG, "mBackupQueue is null. WHY?");
}
+ request = mBackupQueue.get(0);
+ }
+ try {
// !!! TODO right now these naming schemes limit applications to
// one backup service per package
- File savedStateName = new File(mStateDir, request.service.packageName);
- File backupDataName = new File(mDataDir, request.service.packageName + ".data");
- File newStateName = new File(mStateDir, request.service.packageName + ".new");
+ File savedStateName = new File(mStateDir, request.appInfo.packageName);
+ File backupDataName = new File(mDataDir, request.appInfo.packageName + ".data");
+ File newStateName = new File(mStateDir, request.appInfo.packageName + ".new");
// In a full backup, we pass a null ParcelFileDescriptor as
// the saved-state "file"
@@ -280,7 +281,7 @@ class BackupManagerService extends IBackupManager.Stub {
Log.d(TAG, "File not found on backup: ");
fnf.printStackTrace();
} catch (RemoteException e) {
- Log.d(TAG, "Remote target " + name + " threw during backup:");
+ Log.d(TAG, "Remote target " + packageName + " threw during backup:");
e.printStackTrace();
} catch (Exception e) {
Log.w(TAG, "Final exception guard in backup: ");
@@ -289,37 +290,45 @@ class BackupManagerService extends IBackupManager.Stub {
synchronized (mQueueLock) {
mBackupQueue.remove(0);
}
- mContext.unbindService(mBackupHandler);
+
+ if (request != null) {
+ try {
+ mActivityManager.unbindBackupAgent(request.appInfo);
+ } catch (RemoteException e) {
+ // can't happen
+ }
+ }
// start the next one
- startOneService();
+ startOneAgent();
}
- // Add the backup services in the given package to our set of known backup participants.
- // If 'packageName' is null, adds all backup services in the system.
+ // Add the backup agents in the given package to our set of known backup participants.
+ // If 'packageName' is null, adds all backup agents in the whole system.
void addPackageParticipantsLocked(String packageName) {
- List<ResolveInfo> services = mPackageManager.queryIntentServices(
- new Intent(BackupService.SERVICE_ACTION), 0);
- addPackageParticipantsLockedInner(packageName, services);
+ // Look for apps that define the android:backupAgent attribute
+ List<ApplicationInfo> targetApps = allAgentApps();
+ addPackageParticipantsLockedInner(packageName, targetApps);
}
- private void addPackageParticipantsLockedInner(String packageName, List<ResolveInfo> services) {
- for (ResolveInfo ri : services) {
- if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) {
- int uid = ri.serviceInfo.applicationInfo.uid;
- HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
+ private void addPackageParticipantsLockedInner(String packageName,
+ List<ApplicationInfo> targetApps) {
+ if (DEBUG) {
+ Log.v(TAG, "Adding " + targetApps.size() + " backup participants:");
+ for (ApplicationInfo a : targetApps) {
+ Log.v(TAG, " " + a + " agent=" + a.backupAgentName);
+ }
+ }
+
+ for (ApplicationInfo app : targetApps) {
+ if (packageName == null || app.packageName.equals(packageName)) {
+ int uid = app.uid;
+ HashSet<ApplicationInfo> set = mBackupParticipants.get(uid);
if (set == null) {
- set = new HashSet<ServiceInfo>();
+ set = new HashSet<ApplicationInfo>();
mBackupParticipants.put(uid, set);
}
- if (DEBUG) {
- Log.v(TAG, "Adding " + services.size() + " backup participants:");
- for (ResolveInfo svc : services) {
- Log.v(TAG, " " + svc + " : " + svc.filter);
- }
- }
-
- set.add(ri.serviceInfo);
+ set.add(app);
}
}
}
@@ -327,19 +336,30 @@ class BackupManagerService extends IBackupManager.Stub {
// Remove the given package's backup services from our known active set. If
// 'packageName' is null, *all* backup services will be removed.
void removePackageParticipantsLocked(String packageName) {
- List<ResolveInfo> services = mPackageManager.queryIntentServices(
- new Intent(BackupService.SERVICE_ACTION), 0);
- removePackageParticipantsLockedInner(packageName, services);
+ List<ApplicationInfo> allApps = null;
+ if (packageName != null) {
+ allApps = new ArrayList<ApplicationInfo>();
+ try {
+ ApplicationInfo app = mPackageManager.getApplicationInfo(packageName, 0);
+ allApps.add(app);
+ } catch (Exception e) {
+ // just skip it
+ }
+ } else {
+ // all apps with agents
+ allApps = allAgentApps();
+ }
+ removePackageParticipantsLockedInner(packageName, allApps);
}
private void removePackageParticipantsLockedInner(String packageName,
- List<ResolveInfo> services) {
- for (ResolveInfo ri : services) {
- if (packageName == null || ri.serviceInfo.packageName.equals(packageName)) {
- int uid = ri.serviceInfo.applicationInfo.uid;
- HashSet<ServiceInfo> set = mBackupParticipants.get(uid);
+ List<ApplicationInfo> agents) {
+ for (ApplicationInfo app : agents) {
+ if (packageName == null || app.packageName.equals(packageName)) {
+ int uid = app.uid;
+ HashSet<ApplicationInfo> set = mBackupParticipants.get(uid);
if (set != null) {
- set.remove(ri.serviceInfo);
+ set.remove(app);
if (set.size() == 0) {
mBackupParticipants.put(uid, null);
}
@@ -348,6 +368,21 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
+ // Returns the set of all applications that define an android:backupAgent attribute
+ private List<ApplicationInfo> allAgentApps() {
+ List<ApplicationInfo> allApps = mPackageManager.getInstalledApplications(0);
+ int N = allApps.size();
+ if (N > 0) {
+ for (int a = N-1; a >= 0; a--) {
+ ApplicationInfo app = allApps.get(a);
+ if (app.backupAgentName == null) {
+ allApps.remove(a);
+ }
+ }
+ }
+ return allApps;
+ }
+
// Reset the given package's known backup participants. Unlike add/remove, the update
// action cannot be passed a null package name.
void updatePackageParticipantsLocked(String packageName) {
@@ -357,10 +392,9 @@ class BackupManagerService extends IBackupManager.Stub {
}
// brute force but small code size
- List<ResolveInfo> services = mPackageManager.queryIntentServices(
- new Intent(BackupService.SERVICE_ACTION), 0);
- removePackageParticipantsLockedInner(packageName, services);
- addPackageParticipantsLockedInner(packageName, services);
+ List<ApplicationInfo> allApps = allAgentApps();
+ removePackageParticipantsLockedInner(packageName, allApps);
+ addPackageParticipantsLockedInner(packageName, allApps);
}
// ----- IBackupManager binder interface -----
@@ -372,24 +406,29 @@ class BackupManagerService extends IBackupManager.Stub {
Log.d(TAG, "dataChanged packageName=" + packageName);
- HashSet<ServiceInfo> targets = mBackupParticipants.get(Binder.getCallingUid());
- Log.d(TAG, "targets=" + targets);
+ HashSet<ApplicationInfo> targets = mBackupParticipants.get(Binder.getCallingUid());
if (targets != null) {
synchronized (mQueueLock) {
// Note that this client has made data changes that need to be backed up
- for (ServiceInfo service : targets) {
+ for (ApplicationInfo app : targets) {
// validate the caller-supplied package name against the known set of
// packages associated with this uid
- if (service.packageName.equals(packageName)) {
+ if (app.packageName.equals(packageName)) {
// Add the caller to the set of pending backups. If there is
// one already there, then overwrite it, but no harm done.
- mPendingBackups.put(new ComponentName(service.packageName, service.name),
- new BackupRequest(service, true));
+ BackupRequest req = new BackupRequest(app, false);
+ mPendingBackups.put(app, req);
// !!! TODO: write to the pending-backup journal file in case of crash
}
}
- Log.d(TAG, "Scheduling backup for " + mPendingBackups.size() + " participants");
+ if (DEBUG) {
+ int numKeys = mPendingBackups.size();
+ Log.d(TAG, "Scheduling backup for " + numKeys + " participants:");
+ for (BackupRequest b : mPendingBackups.values()) {
+ Log.d(TAG, " + " + b + " agent=" + b.appInfo.backupAgentName);
+ }
+ }
// Schedule a backup pass in a few minutes. As backup-eligible data
// keeps changing, continue to defer the backup pass until things
// settle down, to avoid extra overhead.
@@ -402,22 +441,35 @@ class BackupManagerService extends IBackupManager.Stub {
// that uid or package itself.
public void scheduleFullBackup(String packageName) throws RemoteException {
// !!! TODO: protect with a signature-or-system permission?
- HashSet<ServiceInfo> targets = new HashSet<ServiceInfo>();
synchronized (mQueueLock) {
int numKeys = mBackupParticipants.size();
for (int index = 0; index < numKeys; index++) {
int uid = mBackupParticipants.keyAt(index);
- HashSet<ServiceInfo> servicesAtUid = mBackupParticipants.get(uid);
- for (ServiceInfo service: servicesAtUid) {
- if (service.packageName.equals(packageName)) {
- mPendingBackups.put(new ComponentName(service.packageName, service.name),
- new BackupRequest(service, true));
+ HashSet<ApplicationInfo> servicesAtUid = mBackupParticipants.get(uid);
+ for (ApplicationInfo app: servicesAtUid) {
+ if (app.packageName.equals(packageName)) {
+ mPendingBackups.put(app, new BackupRequest(app, true));
}
}
}
}
}
+ // Callback: a requested backup agent has been instantiated
+ public void agentConnected(String packageName, IBinder agentBinder) {
+ Log.d(TAG, "agentConnected pkg=" + packageName + " agent=" + agentBinder);
+ IBackupAgent bs = IBackupAgent.Stub.asInterface(agentBinder);
+ processOneBackup(packageName, bs);
+ }
+
+ // Callback: a backup agent has failed to come up, or has unexpectedly quit.
+ // If the agent failed to come up in the first place, the agentBinder argument
+ // will be null.
+ public void agentDisconnected(String packageName) {
+ // TODO: handle backup being interrupted
+ }
+
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -428,12 +480,18 @@ class BackupManagerService extends IBackupManager.Stub {
int uid = mBackupParticipants.keyAt(i);
pw.print(" uid: ");
pw.println(uid);
- HashSet<ServiceInfo> services = mBackupParticipants.valueAt(i);
- for (ServiceInfo s: services) {
+ HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i);
+ for (ApplicationInfo app: participants) {
pw.print(" ");
- pw.println(s.toString());
+ pw.println(app.toString());
}
}
+ pw.println("Pending:");
+ Iterator<BackupRequest> br = mPendingBackups.values().iterator();
+ while (br.hasNext()) {
+ pw.print(" ");
+ pw.println(br);
+ }
}
}
}
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 04e0253a02ee..c9bdd3c7ebef 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -2843,7 +2843,7 @@ class PackageManagerService extends IPackageManager.Stub {
if (npi.name.equals(perm)
&& pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) {
allowed = true;
- Log.i(TAG, "Auto-granting WRITE_SDCARD to old pkg "
+ Log.i(TAG, "Auto-granting WRITE_EXTERNAL_STORAGE to old pkg "
+ pkg.packageName);
break;
}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index d1c40b436040..3b26cb78d721 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -42,6 +42,7 @@ import android.app.IThumbnailReceiver;
import android.app.Instrumentation;
import android.app.PendingIntent;
import android.app.ResultInfo;
+import android.backup.IBackupManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -131,6 +132,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
static final boolean DEBUG_PROCESSES = localLOGV || false;
static final boolean DEBUG_USER_LEAVING = localLOGV || false;
static final boolean DEBUG_RESULTS = localLOGV || false;
+ static final boolean DEBUG_BACKUP = localLOGV || true;
static final boolean VALIDATE_TOKENS = false;
static final boolean SHOW_ACTIVITY_START_TIME = true;
@@ -633,6 +635,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
= new ArrayList<ServiceRecord>();
/**
+ * Backup/restore process management
+ */
+ String mBackupAppName = null;
+ BackupRecord mBackupTarget = null;
+
+ /**
* List of PendingThumbnailsRecord objects of clients who are still
* waiting to receive all of the thumbnails for a task.
*/
@@ -4669,6 +4677,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
mPendingBroadcast = null;
scheduleBroadcastsLocked();
}
+ if (mBackupTarget != null && mBackupTarget.app.pid == pid) {
+ Log.w(TAG, "Unattached app died before backup, skipping");
+ try {
+ IBackupManager bm = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ bm.agentDisconnected(app.info.packageName);
+ } catch (RemoteException e) {
+ // Can't happen; the backup manager is local
+ }
+ }
} else {
Log.w(TAG, "Spurious process start timeout - pid not known for " + app);
}
@@ -4757,11 +4775,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
mWaitForDebugger = mOrigWaitForDebugger;
}
}
+ // If the app is being launched for restore or full backup, set it up specially
+ boolean isRestrictedBackupMode = false;
+ if (mBackupTarget != null && mBackupAppName.equals(processName)) {
+ isRestrictedBackupMode = (mBackupTarget.backupMode == BackupRecord.RESTORE)
+ || (mBackupTarget.backupMode == BackupRecord.BACKUP_FULL);
+ }
thread.bindApplication(processName, app.instrumentationInfo != null
? app.instrumentationInfo : app.info, providers,
app.instrumentationClass, app.instrumentationProfileFile,
app.instrumentationArguments, app.instrumentationWatcher, testMode,
- mConfiguration, getCommonServicesLocked());
+ isRestrictedBackupMode, mConfiguration, getCommonServicesLocked());
updateLRUListLocked(app, false);
app.lastRequestedGc = SystemClock.uptimeMillis();
} catch (Exception e) {
@@ -4842,6 +4866,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
}
}
+ // Check whether the next backup agent is in this process...
+ if (!badApp && mBackupTarget != null && mBackupTarget.appInfo.uid == app.info.uid) {
+ if (DEBUG_BACKUP) Log.v(TAG, "New app is backup target, launching agent for " + app);
+ try {
+ thread.scheduleCreateBackupAgent(mBackupTarget.appInfo, mBackupTarget.backupMode);
+ } catch (Exception e) {
+ Log.w(TAG, "Exception scheduling backup agent creation: ");
+ e.printStackTrace();
+ }
+ }
+
if (badApp) {
// todo: Also need to kill application to deal with all
// kinds of exceptions.
@@ -9118,6 +9153,18 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
app.receivers.clear();
}
+ // If the app is undergoing backup, tell the backup manager about it
+ if (mBackupTarget != null && app.pid == mBackupTarget.app.pid) {
+ if (DEBUG_BACKUP) Log.d(TAG, "App " + mBackupTarget.appInfo + " died during backup");
+ try {
+ IBackupManager bm = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ bm.agentDisconnected(app.info.packageName);
+ } catch (RemoteException e) {
+ // can't happen; backup manager is local
+ }
+ }
+
// If the caller is restarting this app, then leave it in its
// current lists and let the caller take care of it.
if (restarting) {
@@ -10234,6 +10281,105 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
}
// =========================================================
+ // BACKUP AND RESTORE
+ // =========================================================
+
+ // Cause the target app to be launched if necessary and its backup agent
+ // instantiated. The backup agent will invoke backupAgentCreated() on the
+ // activity manager to announce its creation.
+ public boolean bindBackupAgent(ApplicationInfo app, int backupMode) {
+ if (DEBUG_BACKUP) Log.v(TAG, "startBackupAgent: app=" + app + " mode=" + backupMode);
+ enforceCallingPermission("android.permission.BACKUP", "startBackupAgent");
+
+ synchronized(this) {
+ // !!! TODO: currently no check here that we're already bound
+ BatteryStatsImpl.Uid.Pkg.Serv ss = null;
+ BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+ synchronized (stats) {
+ ss = stats.getServiceStatsLocked(app.uid, app.packageName, app.name);
+ }
+
+ BackupRecord r = new BackupRecord(ss, app, backupMode);
+ ComponentName hostingName = new ComponentName(app.packageName, app.backupAgentName);
+ // startProcessLocked() returns existing proc's record if it's already running
+ ProcessRecord proc = startProcessLocked(app.processName, app,
+ false, 0, "backup", hostingName);
+ if (proc == null) {
+ Log.e(TAG, "Unable to start backup agent process " + r);
+ return false;
+ }
+
+ r.app = proc;
+ mBackupTarget = r;
+ mBackupAppName = app.packageName;
+
+ // If the process is already attached, schedule the creation of the backup agent now.
+ // If it is not yet live, this will be done when it attaches to the framework.
+ if (proc.thread != null) {
+ if (DEBUG_BACKUP) Log.v(TAG, "Agent proc already running: " + proc);
+ try {
+ proc.thread.scheduleCreateBackupAgent(app, backupMode);
+ } catch (RemoteException e) {
+ // !!! TODO: notify the backup manager that we crashed, or rely on
+ // death notices, or...?
+ }
+ } else {
+ if (DEBUG_BACKUP) Log.v(TAG, "Agent proc not running, waiting for attach");
+ }
+ // Invariants: at this point, the target app process exists and the application
+ // is either already running or in the process of coming up. mBackupTarget and
+ // mBackupAppName describe the app, so that when it binds back to the AM we
+ // know that it's scheduled for a backup-agent operation.
+ }
+
+ return true;
+ }
+
+ // A backup agent has just come up
+ public void backupAgentCreated(String agentPackageName, IBinder agent) {
+ if (DEBUG_BACKUP) Log.v(TAG, "backupAgentCreated: " + agentPackageName
+ + " = " + agent);
+
+ synchronized(this) {
+ if (!agentPackageName.equals(mBackupAppName)) {
+ Log.e(TAG, "Backup agent created for " + agentPackageName + " but not requested!");
+ return;
+ }
+
+ try {
+ IBackupManager bm = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ bm.agentConnected(agentPackageName, agent);
+ } catch (RemoteException e) {
+ // can't happen; the backup manager service is local
+ } catch (Exception e) {
+ Log.w(TAG, "Exception trying to deliver BackupAgent binding: ");
+ e.printStackTrace();
+ }
+ }
+ }
+
+ // done with this agent
+ public void unbindBackupAgent(ApplicationInfo appInfo) {
+ if (DEBUG_BACKUP) Log.v(TAG, "unbindBackupAgent: " + appInfo);
+
+ synchronized(this) {
+ if (!mBackupAppName.equals(appInfo.packageName)) {
+ Log.e(TAG, "Unbind of " + appInfo + " but is not the current backup target");
+ return;
+ }
+
+ try {
+ mBackupTarget.app.thread.scheduleDestroyBackupAgent(appInfo);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception when unbinding backup agent:");
+ e.printStackTrace();
+ }
+ mBackupTarget = null;
+ mBackupAppName = null;
+ }
+ }
+ // =========================================================
// BROADCASTS
// =========================================================
diff --git a/services/java/com/android/server/am/BackupRecord.java b/services/java/com/android/server/am/BackupRecord.java
new file mode 100644
index 000000000000..5ac8e0d802ff
--- /dev/null
+++ b/services/java/com/android/server/am/BackupRecord.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2009 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.server.am;
+
+import com.android.internal.os.BatteryStatsImpl;
+
+import android.content.pm.ApplicationInfo;
+
+/** @hide */
+class BackupRecord {
+ // backup/restore modes
+ public static final int BACKUP_NORMAL = 0;
+ public static final int BACKUP_FULL = 1;
+ public static final int RESTORE = 2;
+
+ final BatteryStatsImpl.Uid.Pkg.Serv stats;
+ String stringName; // cached toString() output
+ final ApplicationInfo appInfo; // information about BackupAgent's app
+ final int backupMode; // full backup / incremental / restore
+ ProcessRecord app; // where this agent is running or null
+
+ // ----- Implementation -----
+
+ BackupRecord(BatteryStatsImpl.Uid.Pkg.Serv _agentStats, ApplicationInfo _appInfo,
+ int _backupMode) {
+ stats = _agentStats;
+ appInfo = _appInfo;
+ backupMode = _backupMode;
+ }
+
+ public String toString() {
+ if (stringName != null) {
+ return stringName;
+ }
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("BackupRecord{")
+ .append(Integer.toHexString(System.identityHashCode(this)))
+ .append(' ').append(appInfo.packageName)
+ .append(' ').append(appInfo.name)
+ .append(' ').append(appInfo.backupAgentName).append('}');
+ return stringName = sb.toString();
+ }
+} \ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/CommandsInterface.java b/telephony/java/com/android/internal/telephony/CommandsInterface.java
index 94a1c13ab6bb..34a57a026d95 100644
--- a/telephony/java/com/android/internal/telephony/CommandsInterface.java
+++ b/telephony/java/com/android/internal/telephony/CommandsInterface.java
@@ -147,6 +147,14 @@ public interface CommandsInterface {
static final int SIM_REFRESH_INIT = 1; // SIM initialized; reload all
static final int SIM_REFRESH_RESET = 2; // SIM reset; may be locked
+ // GSM SMS fail cause for acknowledgeLastIncomingSMS. From TS 23.040, 9.2.3.22.
+ static final int GSM_SMS_FAIL_CAUSE_MEMORY_CAPACITY_EXCEEDED = 0xD3;
+ static final int GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR = 0xFF;
+
+ // CDMA SMS fail cause for acknowledgeLastIncomingCdmaSms. From TS N.S00005, 6.5.2.125.
+ static final int CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE = 35;
+ static final int CDMA_SMS_FAIL_CAUSE_OTHER_TERMINAL_PROBLEM = 39;
+
//***** Methods
RadioState getRadioState();
@@ -882,9 +890,9 @@ public interface CommandsInterface {
void setRadioPower(boolean on, Message response);
- void acknowledgeLastIncomingSMS(boolean success, Message response);
+ void acknowledgeLastIncomingGsmSms(boolean success, int cause, Message response);
- void acknowledgeLastIncomingCdmaSms(boolean success, Message response);
+ void acknowledgeLastIncomingCdmaSms(boolean success, int cause, Message response);
/**
* parameters equivilient to 27.007 AT+CRSM command
@@ -1087,6 +1095,12 @@ public interface CommandsInterface {
*/
void setSmscAddress(String address, Message result);
+ /**
+ * Indicates whether there is storage available for new SMS messages.
+ * @param available true if storage is available
+ * @param result callback message
+ */
+ void reportSmsMemoryStatus(boolean available, Message result);
void invokeOemRilRequestRaw(byte[] data, Message response);
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index d23af1fa9396..77755cefddf7 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -103,9 +103,9 @@ public abstract class DataConnectionTracker extends Handler {
/** Slow poll when attempting connection recovery. */
protected static final int POLL_NETSTAT_SLOW_MILLIS = 5000;
/** Default ping deadline, in seconds. */
- protected final int DEFAULT_PING_DEADLINE = 5;
+ protected static final int DEFAULT_PING_DEADLINE = 5;
/** Default max failure count before attempting to network re-registration. */
- protected final int DEFAULT_MAX_PDP_RESET_FAIL = 3;
+ protected static final int DEFAULT_MAX_PDP_RESET_FAIL = 3;
/**
* After detecting a potential connection problem, this is the max number
@@ -217,7 +217,7 @@ public abstract class DataConnectionTracker extends Handler {
}
// abstract handler methods
- protected abstract void onTrySetupData();
+ protected abstract void onTrySetupData(String reason);
protected abstract void onRoamingOff();
protected abstract void onRoamingOn();
protected abstract void onRadioAvailable();
@@ -232,7 +232,11 @@ public abstract class DataConnectionTracker extends Handler {
switch (msg.what) {
case EVENT_TRY_SETUP_DATA:
- onTrySetupData();
+ String reason = null;
+ if (msg.obj instanceof String) {
+ reason = (String)msg.obj;
+ }
+ onTrySetupData(reason);
break;
case EVENT_ROAMING_OFF:
diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java
index 792e67fda7b5..ea84b09801e4 100644
--- a/telephony/java/com/android/internal/telephony/RIL.java
+++ b/telephony/java/com/android/internal/telephony/RIL.java
@@ -1339,28 +1339,32 @@ public final class RIL extends BaseCommands implements CommandsInterface {
}
public void
- acknowledgeLastIncomingSMS(boolean success, Message result) {
+ acknowledgeLastIncomingGsmSms(boolean success, int cause, Message result) {
RILRequest rr
= RILRequest.obtain(RIL_REQUEST_SMS_ACKNOWLEDGE, result);
- rr.mp.writeInt(1);
+ rr.mp.writeInt(2);
rr.mp.writeInt(success ? 1 : 0);
+ rr.mp.writeInt(cause);
- if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " " + success + " " + cause);
send(rr);
}
public void
- acknowledgeLastIncomingCdmaSms(boolean success, Message result) {
+ acknowledgeLastIncomingCdmaSms(boolean success, int cause, Message result) {
RILRequest rr
= RILRequest.obtain(RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE, result);
+ rr.mp.writeInt(2);
rr.mp.writeInt(success ? 0 : 1); //RIL_CDMA_SMS_ErrorClass
// cause code according to X.S004-550E
- rr.mp.writeInt(39); //39 means other terminal problem; is not interpreted for success.
+ rr.mp.writeInt(cause);
- if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " " + success + " " + cause);
send(rr);
}
@@ -1812,6 +1816,20 @@ public final class RIL extends BaseCommands implements CommandsInterface {
send(rr);
}
+ /**
+ * {@inheritDoc}
+ */
+ public void reportSmsMemoryStatus(boolean available, Message result) {
+ RILRequest rr = RILRequest.obtain(RIL_REQUEST_REPORT_SMS_MEMORY_STATUS, result);
+ rr.mp.writeInt(1);
+ rr.mp.writeInt(available ? 1 : 0);
+
+ if (RILJ_LOGD) riljLog(rr.serialString() + "> "
+ + requestToString(rr.mRequest) + ": " + available);
+
+ send(rr);
+ }
+
//***** Private Methods
private void sendScreenState(boolean on) {
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 44c863b90aea..26995ef24062 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -222,6 +222,7 @@ cat include/telephony/ril.h | \
int RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE = 99;
int RIL_REQUEST_GET_SMSC_ADDRESS = 100;
int RIL_REQUEST_SET_SMSC_ADDRESS = 101;
+ int RIL_REQUEST_REPORT_SMS_MEMORY_STATUS = 102;
int RIL_UNSOL_RESPONSE_BASE = 1000;
int RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED = 1000;
int RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED = 1001;
diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
index d055c3114f1f..12808ceacd36 100644
--- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
@@ -20,6 +20,7 @@ import android.app.Activity;
import android.app.PendingIntent;
import android.app.AlertDialog;
import android.app.PendingIntent.CanceledException;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -32,6 +33,7 @@ import android.net.Uri;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
+import android.os.PowerManager;
import android.provider.Telephony;
import android.provider.Telephony.Sms.Intents;
import android.provider.Settings;
@@ -128,6 +130,15 @@ public abstract class SMSDispatcher extends Handler {
private SmsTracker mSTracker;
+ /** Wake lock to ensure device stays awake while dispatching the SMS intent. */
+ private PowerManager.WakeLock mWakeLock;
+
+ /**
+ * Hold the wake lock for 5 seconds, which should be enough time for
+ * any receiver(s) to grab its own wake lock.
+ */
+ private final int WAKE_LOCK_TIMEOUT = 5000;
+
private static SmsMessage mSmsMessage;
private static SmsMessageBase mSmsMessageBase;
private SmsMessageBase.SubmitPduBase mSubmitPduBase;
@@ -196,14 +207,16 @@ public abstract class SMSDispatcher extends Handler {
protected SMSDispatcher(PhoneBase phone) {
mPhone = phone;
- mWapPush = new WapPushOverSms(phone);
+ mWapPush = new WapPushOverSms(phone, this);
mContext = phone.getContext();
mResolver = mContext.getContentResolver();
mCm = phone.mCM;
mSTracker = null;
+ createWakelock();
+
int check_period = Settings.Gservices.getInt(mResolver,
- Settings.Gservices.SMS_OUTGOING_CEHCK_INTERVAL_MS,
+ Settings.Gservices.SMS_OUTGOING_CHECK_INTERVAL_MS,
DEFAULT_SMS_CHECK_PERIOD);
int max_count = Settings.Gservices.getInt(mResolver,
Settings.Gservices.SMS_OUTGOING_CEHCK_MAX_COUNT,
@@ -257,16 +270,17 @@ public abstract class SMSDispatcher extends Handler {
ar = (AsyncResult) msg.obj;
- // FIXME only acknowledge on store
- acknowledgeLastIncomingSms(true, null);
-
if (ar.exception != null) {
Log.e(TAG, "Exception processing incoming SMS. Exception:" + ar.exception);
return;
}
sms = (SmsMessage) ar.result;
- dispatchMessage(sms.mWrappedSmsMessage);
+ try {
+ dispatchMessage(sms.mWrappedSmsMessage);
+ } catch (RuntimeException ex) {
+ acknowledgeLastIncomingSms(false, Intents.RESULT_SMS_GENERIC_ERROR, null);
+ }
break;
@@ -310,6 +324,28 @@ public abstract class SMSDispatcher extends Handler {
}
}
+ private void createWakelock() {
+ PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SMSDispatcher");
+ mWakeLock.setReferenceCounted(true);
+ }
+
+ /**
+ * Grabs a wake lock and sends intent as an ordered broadcast.
+ * The resultReceiver will check for errors and ACK/NACK back
+ * to the RIL.
+ *
+ * @param intent intent to broadcast
+ * @param permission Receivers are required to have this permission
+ */
+ void dispatch(Intent intent, String permission) {
+ // Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any
+ // receivers time to take their own wake locks.
+ mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
+ mContext.sendOrderedBroadcast(intent, permission, mResultReceiver,
+ this, Activity.RESULT_OK, null, null);
+ }
+
/**
* Called when SIM_FULL message is received from the RIL. Notifies interested
* parties that SIM storage for SMS messages is full.
@@ -317,7 +353,8 @@ public abstract class SMSDispatcher extends Handler {
private void handleIccFull(){
// broadcast SIM_FULL intent
Intent intent = new Intent(Intents.SIM_FULL_ACTION);
- mPhone.getContext().sendBroadcast(intent, "android.permission.RECEIVE_SMS");
+ mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
+ mContext.sendBroadcast(intent, "android.permission.RECEIVE_SMS");
}
/**
@@ -454,6 +491,7 @@ public abstract class SMSDispatcher extends Handler {
values.put("destination_port", portAddrs.destPort);
}
mResolver.insert(mRawUri, values);
+ acknowledgeLastIncomingSms(true, Intents.RESULT_SMS_HANDLED, null);
return;
}
@@ -475,7 +513,9 @@ public abstract class SMSDispatcher extends Handler {
mResolver.delete(mRawUri, where.toString(), whereArgs);
} catch (SQLException e) {
Log.e(TAG, "Can't access multipart SMS database", e);
- return; // TODO: NACK the message or something, don't just discard.
+ // TODO: Would OUT_OF_MEMORY be more appropriate?
+ acknowledgeLastIncomingSms(false, Intents.RESULT_SMS_GENERIC_ERROR, null);
+ return;
} finally {
if (cursor != null) cursor.close();
}
@@ -519,8 +559,7 @@ public abstract class SMSDispatcher extends Handler {
protected void dispatchPdus(byte[][] pdus) {
Intent intent = new Intent(Intents.SMS_RECEIVED_ACTION);
intent.putExtra("pdus", pdus);
- mPhone.getContext().sendBroadcast(
- intent, "android.permission.RECEIVE_SMS");
+ dispatch(intent, "android.permission.RECEIVE_SMS");
}
/**
@@ -533,8 +572,7 @@ public abstract class SMSDispatcher extends Handler {
Uri uri = Uri.parse("sms://localhost:" + port);
Intent intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri);
intent.putExtra("pdus", pdus);
- mPhone.getContext().sendBroadcast(
- intent, "android.permission.RECEIVE_SMS");
+ dispatch(intent, "android.permission.RECEIVE_SMS");
}
@@ -698,9 +736,11 @@ public abstract class SMSDispatcher extends Handler {
/**
* Send an acknowledge message.
* @param success indicates that last message was successfully received.
+ * @param result result code indicating any error
* @param response callback message sent when operation completes.
*/
- protected abstract void acknowledgeLastIncomingSms(boolean success, Message response);
+ protected abstract void acknowledgeLastIncomingSms(boolean success,
+ int result, Message response);
/**
* Check if a SmsTracker holds multi-part Sms
@@ -751,4 +791,17 @@ public abstract class SMSDispatcher extends Handler {
}
}
};
+
+ private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int rc = getResultCode();
+ boolean success = (rc == Activity.RESULT_OK) || (rc == Intents.RESULT_SMS_HANDLED);
+
+ // For a multi-part message, this only ACKs the last part.
+ // Previous parts were ACK'd as they were received.
+ acknowledgeLastIncomingSms(success, rc, null);
+ }
+
+ };
}
diff --git a/telephony/java/com/android/internal/telephony/SmsMessageBase.java b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
index 31bb652ab0d2..4d32c3502dbc 100644
--- a/telephony/java/com/android/internal/telephony/SmsMessageBase.java
+++ b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
@@ -312,7 +312,9 @@ public abstract class SmsMessageBase {
}
protected void parseMessageBody() {
- if (originatingAddress.couldBeEmailGateway()) {
+ // originatingAddress could be null if this message is from a status
+ // report.
+ if (originatingAddress != null && originatingAddress.couldBeEmailGateway()) {
extractEmailAddressFromMessageBody();
}
}
diff --git a/telephony/java/com/android/internal/telephony/WapPushOverSms.java b/telephony/java/com/android/internal/telephony/WapPushOverSms.java
index 98899c903684..c851bc1b0fde 100644
--- a/telephony/java/com/android/internal/telephony/WapPushOverSms.java
+++ b/telephony/java/com/android/internal/telephony/WapPushOverSms.java
@@ -18,7 +18,6 @@ package com.android.internal.telephony;
import android.content.Context;
import android.content.Intent;
-import android.os.PowerManager;
import android.provider.Telephony.Sms.Intents;
import android.util.Config;
import android.util.Log;
@@ -34,7 +33,7 @@ public class WapPushOverSms {
private final Context mContext;
private WspTypeDecoder pduDecoder;
- private PowerManager.WakeLock mWakeLock;
+ private SMSDispatcher mSmsDispatcher;
/**
* Hold the wake lock for 5 seconds, which should be enough time for
@@ -42,10 +41,9 @@ public class WapPushOverSms {
*/
private final int WAKE_LOCK_TIMEOUT = 5000;
- public WapPushOverSms(Phone phone) {
-
+ public WapPushOverSms(Phone phone, SMSDispatcher smsDispatcher) {
+ mSmsDispatcher = smsDispatcher;
mContext = phone.getContext();
- createWakelock();
}
/**
@@ -184,7 +182,7 @@ public class WapPushOverSms {
intent.putExtra("pduType", pduType);
intent.putExtra("data", data);
- sendBroadcast(intent, "android.permission.RECEIVE_WAP_PUSH");
+ mSmsDispatcher.dispatch(intent, "android.permission.RECEIVE_WAP_PUSH");
}
private void dispatchWapPdu_PushCO(byte[] pdu, int transactionId, int pduType) {
@@ -194,7 +192,7 @@ public class WapPushOverSms {
intent.putExtra("pduType", pduType);
intent.putExtra("data", pdu);
- sendBroadcast(intent, "android.permission.RECEIVE_WAP_PUSH");
+ mSmsDispatcher.dispatch(intent, "android.permission.RECEIVE_WAP_PUSH");
}
private void dispatchWapPdu_MMS(byte[] pdu, int transactionId, int pduType, int dataIndex) {
@@ -209,20 +207,7 @@ public class WapPushOverSms {
intent.putExtra("pduType", pduType);
intent.putExtra("data", data);
- sendBroadcast(intent, "android.permission.RECEIVE_MMS");
- }
-
- private void createWakelock() {
- PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WapPushOverSms");
- mWakeLock.setReferenceCounted(true);
- }
-
- private void sendBroadcast(Intent intent, String permission) {
- // Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any
- // receivers time to take their own wake locks.
- mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
- mContext.sendBroadcast(intent, permission);
+ mSmsDispatcher.dispatch(intent, "android.permission.RECEIVE_MMS");
}
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
index bf58ab7ad073..d8a6a5027e65 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -675,7 +675,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker {
if (state == State.FAILED) {
cleanUpConnection(false, null);
}
- sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA));
+ sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA, Phone.REASON_SIM_LOADED));
}
protected void onNVReady() {
@@ -688,8 +688,8 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker {
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
- protected void onTrySetupData() {
- trySetupData(null);
+ protected void onTrySetupData(String reason) {
+ trySetupData(reason);
}
/**
@@ -769,7 +769,10 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker {
}
if (tryAgain(cause)) {
- trySetupData(reason);
+ // Wait a bit before trying again, so that
+ // we're not tying up the RIL command channel
+ sendMessageDelayed(obtainMessage(EVENT_TRY_SETUP_DATA, reason),
+ RECONNECT_DELAY_INITIAL_MILLIS);
} else {
startDelayedRetry(cause, reason);
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index a3d00d73db02..f12e7e399e3a 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -17,15 +17,18 @@
package com.android.internal.telephony.cdma;
+import android.app.Activity;
import android.app.PendingIntent;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.SQLException;
import android.os.AsyncResult;
import android.os.Message;
+import android.provider.Telephony.Sms.Intents;
import android.util.Config;
import android.util.Log;
+import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.SMSDispatcher;
@@ -330,10 +333,10 @@ final class CdmaSMSDispatcher extends SMSDispatcher {
}
/** {@inheritDoc} */
- protected void acknowledgeLastIncomingSms(boolean success, Message response){
+ protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
// FIXME unit test leaves cm == null. this should change
if (mCm != null) {
- mCm.acknowledgeLastIncomingCdmaSms(success, response);
+ mCm.acknowledgeLastIncomingCdmaSms(success, resultToCause(result), response);
}
}
@@ -352,4 +355,17 @@ final class CdmaSMSDispatcher extends SMSDispatcher {
mCm.setCdmaBroadcastConfig(configValuesArray, response);
}
+ private int resultToCause(int rc) {
+ switch (rc) {
+ case Activity.RESULT_OK:
+ case Intents.RESULT_SMS_HANDLED:
+ // Cause code is ignored on success.
+ return 0;
+ case Intents.RESULT_SMS_OUT_OF_MEMORY:
+ return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
+ case Intents.RESULT_SMS_GENERIC_ERROR:
+ default:
+ return CommandsInterface.CDMA_SMS_FAIL_CAUSE_OTHER_TERMINAL_PROBLEM;
+ }
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 49e2daff0f48..1d918a31f44e 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -585,8 +585,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker {
if ((state == State.IDLE || state == State.SCANNING)
&& (gprsState == ServiceState.STATE_IN_SERVICE || noAutoAttach)
&& ((GSMPhone) phone).mSIMRecords.getRecordsLoaded()
- && ( ((GSMPhone) phone).mSST.isConcurrentVoiceAndData() ||
- phone.getState() == Phone.State.IDLE )
+ && phone.getState() == Phone.State.IDLE
&& isDataAllowed()
&& !mIsPsRestricted
&& desiredPowerState ) {
@@ -1221,7 +1220,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker {
if (state == State.FAILED) {
cleanUpConnection(false, null);
}
- sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA));
+ sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA, Phone.REASON_SIM_LOADED));
}
protected void onEnableNewApn() {
@@ -1230,8 +1229,8 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker {
cleanUpConnection(true, Phone.REASON_APN_SWITCHED);
}
- protected void onTrySetupData() {
- trySetupData(null);
+ protected void onTrySetupData(String reason) {
+ trySetupData(reason);
}
protected void onRestoreDefaultApn() {
@@ -1363,7 +1362,10 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker {
} else {
// we still have more apns to try
setState(State.SCANNING);
- trySetupData(reason);
+ // Wait a bit before trying the next APN, so that
+ // we're not tying up the RIL command channel
+ sendMessageDelayed(obtainMessage(EVENT_TRY_SETUP_DATA, reason),
+ RECONNECT_DELAY_INITIAL_MILLIS);
}
} else {
startDelayedRetry(cause, reason);
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 699142a28aca..8bf5d1903cb5 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -22,12 +22,14 @@ import android.app.PendingIntent.CanceledException;
import android.content.Intent;
import android.os.AsyncResult;
import android.os.Message;
+import android.provider.Telephony.Sms.Intents;
import android.telephony.ServiceState;
import android.util.Config;
import android.util.Log;
import com.android.internal.telephony.IccUtils;
import com.android.internal.telephony.gsm.SmsMessage;
+import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.SMSDispatcher;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
@@ -78,7 +80,7 @@ final class GsmSMSDispatcher extends SMSDispatcher {
}
if (mCm != null) {
- mCm.acknowledgeLastIncomingSMS(true, null);
+ mCm.acknowledgeLastIncomingGsmSms(true, Intents.RESULT_SMS_HANDLED, null);
}
}
@@ -150,7 +152,13 @@ final class GsmSMSDispatcher extends SMSDispatcher {
concatRef.refNumber = refNumber;
concatRef.seqNumber = i + 1; // 1-based sequence
concatRef.msgCount = msgCount;
- concatRef.isEightBits = false;
+ // TODO: We currently set this to true since our messaging app will never
+ // send more than 255 parts (it converts the message to MMS well before that).
+ // However, we should support 3rd party messaging apps that might need 16-bit
+ // references
+ // Note: It's not sufficient to just flip this bit to true; it will have
+ // ripple effects (several calculations assume 8-bit ref).
+ concatRef.isEightBits = true;
SmsHeader smsHeader = new SmsHeader();
smsHeader.concatRef = concatRef;
@@ -284,10 +292,10 @@ final class GsmSMSDispatcher extends SMSDispatcher {
}
/** {@inheritDoc} */
- protected void acknowledgeLastIncomingSms(boolean success, Message response){
+ protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
// FIXME unit test leaves cm == null. this should change
if (mCm != null) {
- mCm.acknowledgeLastIncomingSMS(success, response);
+ mCm.acknowledgeLastIncomingGsmSms(success, resultToCause(result), response);
}
}
@@ -312,4 +320,17 @@ final class GsmSMSDispatcher extends SMSDispatcher {
response.recycle();
}
+ private int resultToCause(int rc) {
+ switch (rc) {
+ case Activity.RESULT_OK:
+ case Intents.RESULT_SMS_HANDLED:
+ // Cause code is ignored on success.
+ return 0;
+ case Intents.RESULT_SMS_OUT_OF_MEMORY:
+ return CommandsInterface.GSM_SMS_FAIL_CAUSE_MEMORY_CAPACITY_EXCEEDED;
+ case Intents.RESULT_SMS_GENERIC_ERROR:
+ default:
+ return CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR;
+ }
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index ed61c3f6adb7..bfdd8a71279f 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -250,6 +250,12 @@ public class SmsMessage extends SmsMessageBase{
// TP-Data-Coding-Scheme
// Default encoding, uncompressed
+ // To test writing messages to the SIM card, change this value 0x00
+ // to 0x12, which means "bits 1 and 0 contain message class, and the
+ // class is 2". Note that this takes effect for the sender. In other
+ // words, messages sent by the phone with this change will end up on
+ // the receiver's SIM card. You can then send messages to yourself
+ // (on a phone with this change) and they'll end up on the SIM card.
bo.write(0x00);
// (no TP-Validity-Period)
@@ -558,9 +564,10 @@ public class SmsMessage extends SmsMessageBase{
int offset = cur;
int userDataLength = pdu[offset++] & 0xff;
int headerSeptets = 0;
+ int userDataHeaderLength = 0;
if (hasUserDataHeader) {
- int userDataHeaderLength = pdu[offset++] & 0xff;
+ userDataHeaderLength = pdu[offset++] & 0xff;
byte[] udh = new byte[userDataHeaderLength];
System.arraycopy(pdu, offset, udh, 0, userDataHeaderLength);
@@ -573,19 +580,34 @@ public class SmsMessage extends SmsMessageBase{
mUserDataSeptetPadding = (headerSeptets * 7) - headerBits;
}
- /*
- * Here we just create the user data length to be the remainder of
- * the pdu minus the user data hearder. This is because the count
- * could mean the number of uncompressed sepets if the userdata is
- * encoded in 7-bit.
- */
- userData = new byte[pdu.length - offset];
+ int bufferLen;
+ if (dataInSeptets) {
+ /*
+ * Here we just create the user data length to be the remainder of
+ * the pdu minus the user data header, since userDataLength means
+ * the number of uncompressed sepets.
+ */
+ bufferLen = pdu.length - offset;
+ } else {
+ /*
+ * userDataLength is the count of octets, so just subtract the
+ * user data header.
+ */
+ bufferLen = userDataLength - (hasUserDataHeader ? (userDataHeaderLength + 1) : 0);
+ if (bufferLen < 0) {
+ bufferLen = 0;
+ }
+ }
+
+ userData = new byte[bufferLen];
System.arraycopy(pdu, offset, userData, 0, userData.length);
cur = offset;
if (dataInSeptets) {
// Return the number of septets
- return userDataLength - headerSeptets;
+ int count = userDataLength - headerSeptets;
+ // If count < 0, return 0 (means UDL was probably incorrect)
+ return count < 0 ? 0 : count;
} else {
// Return the number of octets
return userData.length;
diff --git a/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java b/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java
index 19679aa15602..9fb9be82b460 100644
--- a/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java
@@ -1018,6 +1018,10 @@ public final class SimulatedCommands extends BaseCommands
unimplemented(result);
}
+ public void reportSmsMemoryStatus(boolean available, Message result) {
+ unimplemented(result);
+ }
+
private boolean isSimLocked() {
if (mSimLockedState != SimLockState.NONE) {
return true;
@@ -1041,11 +1045,11 @@ public final class SimulatedCommands extends BaseCommands
}
- public void acknowledgeLastIncomingSMS(boolean success, Message result) {
+ public void acknowledgeLastIncomingGsmSms(boolean success, int cause, Message result) {
unimplemented(result);
}
- public void acknowledgeLastIncomingCdmaSms(boolean success, Message result) {
+ public void acknowledgeLastIncomingCdmaSms(boolean success, int cause, Message result) {
unimplemented(result);
}
diff --git a/tests/CoreTests/com/android/internal/telephony/GsmAlphabetTest.java b/tests/CoreTests/com/android/internal/telephony/GsmAlphabetTest.java
index e2336f8832c9..3a9c5116f160 100644
--- a/tests/CoreTests/com/android/internal/telephony/GsmAlphabetTest.java
+++ b/tests/CoreTests/com/android/internal/telephony/GsmAlphabetTest.java
@@ -41,7 +41,7 @@ public class GsmAlphabetTest extends TestCase {
SmsHeader.toByteArray(header));
int septetCount = GsmAlphabet.countGsmSeptets(message, false);
String parsedMessage = GsmAlphabet.gsm7BitPackedToString(
- userData, SmsHeader.toByteArray(header).length+1, septetCount, 1);
+ userData, SmsHeader.toByteArray(header).length+2, septetCount, 1);
assertEquals(message, parsedMessage);
}
diff --git a/tests/DumpRenderTree/AndroidManifest.xml b/tests/DumpRenderTree/AndroidManifest.xml
index 5442ec9ace90..efa41131be31 100644
--- a/tests/DumpRenderTree/AndroidManifest.xml
+++ b/tests/DumpRenderTree/AndroidManifest.xml
@@ -23,8 +23,8 @@
<category android:name="android.intent.category.TEST" />
</intent-filter>
</activity>
- <activity android:name="TestShellActivity" android:launchMode="singleTop">
- </activity>
+ <activity android:name="TestShellActivity" android:launchMode="singleTop" />
+ <activity android:name="ReliabilityTestActivity" />
</application>
<instrumentation android:name=".LayoutTestsAutoRunner"
@@ -33,4 +33,5 @@
/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_SDCARD" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>
diff --git a/tests/DumpRenderTree/assets/run_reliability_tests.py b/tests/DumpRenderTree/assets/run_reliability_tests.py
index a2422938d68d..c12c783060c4 100755
--- a/tests/DumpRenderTree/assets/run_reliability_tests.py
+++ b/tests/DumpRenderTree/assets/run_reliability_tests.py
@@ -10,10 +10,8 @@
import logging
import optparse
-import random
import subprocess
import sys
-import time
TEST_LIST_FILE = "/sdcard/android/reliability_tests_list.txt"
TEST_STATUS_FILE = "/sdcard/android/reliability_running_test.txt"
@@ -41,40 +39,16 @@ def DumpRenderTreeFinished(adb_cmd):
return adb_output.strip() == "#DONE"
-def RandomPick(file_name, approx_size, num_needed):
- """Randomly pick lines from the text file specifed.
-
- Args:
- file_name: the text file where lines should be picked from
- approx_size: an approximate size of the text file
- num_needed: how many lines are needed from the file
-
- Returns:
- an array of string
- """
- p = float(num_needed) / approx_size
- num_picked = 0
- lines = []
- random.seed()
-
- while num_picked < num_needed:
- file_handle = open(file_name, "r")
- for line in file_handle:
- line = line.strip()
- if float(random.randint(0, approx_size)) / approx_size < p:
- lines.append(line)
- num_picked += 1
- if num_picked == num_needed:
- break
- file_handle.close()
- return lines
+def RemoveDeviceFile(adb_cmd, file_name):
+ shell_cmd_str = adb_cmd + " shell rm " + file_name
+ subprocess.Popen(shell_cmd_str,
+ shell=True, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
def main(options, args):
"""Send the url list to device and start testing, restart if crashed."""
- generate_url = False
-
# Set up logging format.
log_level = logging.INFO
if options.verbose:
@@ -84,34 +58,23 @@ def main(options, args):
# Include all tests if none are specified.
if not args:
- path = "/tmp/url_list_%d.txt" % time.time()
- generate_url = True
- logging.info("A URL list is not provided, will be automatically generated.")
+ print "Missing URL list file"
+ sys.exit(1)
else:
path = args[0]
if not options.crash_file:
- print "missing crash file name, use --crash-file to specify"
+ print "Missing crash file name, use --crash-file to specify"
sys.exit(1)
else:
crashed_file = options.crash_file
if not options.timeout_file:
- print "missing timeout file, use --timeout-file to specify"
+ print "Missing timeout file, use --timeout-file to specify"
sys.exit(1)
else:
timedout_file = options.timeout_file
- http = RandomPick(HTTP_URL_FILE, 500000, NUM_URLS)
- https = RandomPick(HTTPS_URL_FILE, 45000, NUM_URLS)
-
- if generate_url:
- file_handle = open(path, "w")
- for i in range(0, NUM_URLS):
- file_handle.write(http[i] + "\n")
- file_handle.write(https[i] + "\n")
- file_handle.close()
-
adb_cmd = "adb "
if options.adb_options:
adb_cmd += options.adb_options + " "
@@ -128,6 +91,10 @@ def main(options, args):
logging.error(adb_error)
sys.exit(1)
+ # clean up previous results
+ RemoveDeviceFile(adb_cmd, TEST_STATUS_FILE)
+ RemoveDeviceFile(adb_cmd, TEST_TIMEOUT_FILE)
+
logging.info("Running the test ...")
# Count crashed tests.
@@ -142,11 +109,10 @@ def main(options, args):
# Call ReliabilityTestsAutoTest#startReliabilityTests
test_cmd = (test_cmd_prefix + " -e class "
- "com.android.dumprendertree.ReliabilityTestsAutoTest#"
- "startReliabilityTests -e timeout " + timeout_ms
- + test_cmd_postfix)
+ "com.android.dumprendertree.ReliabilityTest#"
+ "runTest -e timeout %d %s" %
+ (timeout_ms, test_cmd_postfix))
- time_start = time.time()
adb_output = subprocess.Popen(test_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()[0]
@@ -160,16 +126,12 @@ def main(options, args):
logging.info("Resuming reliability test runner...")
test_cmd = (test_cmd_prefix + " -e class "
- "com.android.dumprendertree.ReliabilityTestsAutoTest#"
- "resumeReliabilityTests -e timeout " + timeout_ms
- + test_cmd_postfix)
+ "com.android.dumprendertree.ReliabilityTest#"
+ "runTest -e timeout %d %s" %
+ (timeout_ms, test_cmd_postfix))
adb_output = subprocess.Popen(test_cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()[0]
- time_end = time.time()
- fp = open("time_stat", "a")
- fp.writelines("%.2f\n" % ((time_end - time_start) / NUM_URLS / 2))
- fp.close()
if (adb_output.find("INSTRUMENTATION_FAILED") != -1 or
adb_output.find("Process crashed.") != -1):
logging.error("Error happened : " + adb_output)
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java
new file mode 100644
index 000000000000..081ddafa24ec
--- /dev/null
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTest.java
@@ -0,0 +1,171 @@
+package com.android.dumprendertree;
+
+import android.os.Handler;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class ReliabilityTest extends ActivityInstrumentationTestCase2<ReliabilityTestActivity> {
+
+ private static final String LOGTAG = "ReliabilityTest";
+ private static final String PKG_NAME = "com.android.dumprendertree";
+ private static final String TEST_LIST_FILE = "/sdcard/android/reliability_tests_list.txt";
+ private static final String TEST_STATUS_FILE = "/sdcard/android/reliability_running_test.txt";
+ private static final String TEST_TIMEOUT_FILE = "/sdcard/android/reliability_timeout_test.txt";
+ private static final String TEST_DONE = "#DONE";
+ static final String RELIABILITY_TEST_RUNNER_FILES[] = {
+ "run_reliability_tests.py"
+ };
+
+ public ReliabilityTest() {
+ super(PKG_NAME, ReliabilityTestActivity.class);
+ }
+
+ @Override
+ protected void runTest() throws Throwable {
+ ReliabilityTestActivity activity = getActivity();
+ LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner)getInstrumentation();
+
+ File testListFile = new File(TEST_LIST_FILE);
+ if(!testListFile.exists())
+ throw new FileNotFoundException("test list file not found.");
+
+ BufferedReader listReader = new BufferedReader(
+ new FileReader(testListFile));
+
+ //always try to resume first, hence cleaning up status will be the
+ //responsibility of driver scripts
+ String lastUrl = readTestStatus();
+ if(lastUrl != null && !TEST_DONE.equals(lastUrl))
+ fastForward(listReader, lastUrl);
+
+ String url = null;
+ Handler handler = null;
+ boolean timeoutFlag = false;
+ long start, elapsed;
+ //read from BufferedReader instead of populating a list in advance,
+ //this will avoid excessive memory usage in case of a large list
+ while((url = listReader.readLine()) != null) {
+ start = System.currentTimeMillis();
+ Log.v(LOGTAG, "Testing URL: " + url);
+ updateTestStatus(url);
+ activity.reset();
+ //use message to send new URL to avoid interacting with
+ //WebView in non-UI thread
+ handler = activity.getHandler();
+ handler.sendMessage(handler.obtainMessage(
+ ReliabilityTestActivity.MSG_NAVIGATE,
+ runner.mTimeoutInMillis, 0, url));
+ timeoutFlag = activity.waitUntilDone();
+ elapsed = System.currentTimeMillis() - start;
+ if(elapsed < 1000) {
+ Log.w(LOGTAG, "Page load finished in " + elapsed
+ + "ms, too soon?");
+ } else {
+ Log.v(LOGTAG, "Page load finished in " + elapsed + "ms");
+ }
+ if(timeoutFlag) {
+ writeTimeoutFile(url);
+ }
+ System.runFinalization();
+ System.gc();
+ System.gc();
+ }
+ updateTestStatus(TEST_DONE);
+ activity.finish();
+ listReader.close();
+ }
+
+ public void copyRunnerAssetsToCache() {
+ try {
+ String out_dir = getActivity().getApplicationContext()
+ .getCacheDir().getPath() + "/";
+
+ for( int i=0; i< RELIABILITY_TEST_RUNNER_FILES.length; i++) {
+ InputStream in = getActivity().getAssets().open(
+ RELIABILITY_TEST_RUNNER_FILES[i]);
+ OutputStream out = new FileOutputStream(
+ out_dir + RELIABILITY_TEST_RUNNER_FILES[i]);
+
+ byte[] buf = new byte[2048];
+ int len;
+
+ while ((len = in.read(buf)) >= 0 ) {
+ out.write(buf, 0, len);
+ }
+ out.close();
+ in.close();
+ }
+ }catch (IOException e) {
+ Log.e(LOGTAG, "Cannot extract scripts for testing.", e);
+ }
+ }
+
+ private void updateTestStatus(String s) {
+ // write last tested url into status file
+ try {
+ BufferedOutputStream bos = new BufferedOutputStream(
+ new FileOutputStream(TEST_STATUS_FILE));
+ bos.write(s.getBytes());
+ bos.close();
+ } catch (IOException e) {
+ Log.e(LOGTAG, "Cannot update file " + TEST_STATUS_FILE, e);
+ }
+ }
+
+ private String readTestStatus() {
+ // read out the test name it stopped last time.
+ String status = null;
+ File testStatusFile = new File(TEST_STATUS_FILE);
+ if(testStatusFile.exists()) {
+ try {
+ BufferedReader inReader = new BufferedReader(
+ new FileReader(testStatusFile));
+ status = inReader.readLine();
+ inReader.close();
+ } catch (IOException e) {
+ Log.e(LOGTAG, "Error reading test status.", e);
+ }
+ }
+ return status;
+ }
+
+ private void fastForward(BufferedReader testListReader, String lastUrl) {
+ //fastforward the BufferedReader to the position right after last url
+ if(lastUrl == null)
+ return;
+
+ String line = null;
+ try {
+ while((line = testListReader.readLine()) != null) {
+ if(lastUrl.equals(line))
+ return;
+ }
+ } catch (IOException ioe) {
+ Log.e(LOGTAG, "Error while reading test list.", ioe);
+ return;
+ }
+ }
+
+ private void writeTimeoutFile(String s) {
+ //append to the file containing the list of timeout urls
+ try {
+ BufferedOutputStream bos = new BufferedOutputStream(
+ new FileOutputStream(TEST_TIMEOUT_FILE, true));
+ bos.write(s.getBytes());
+ bos.write('\n');
+ bos.close();
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Cannot update file " + TEST_TIMEOUT_FILE, e);
+ }
+ }
+}
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestActivity.java
new file mode 100644
index 000000000000..75f14008f82c
--- /dev/null
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestActivity.java
@@ -0,0 +1,256 @@
+package com.android.dumprendertree;
+
+import android.app.Activity;
+import android.app.ActivityThread;
+import android.graphics.Bitmap;
+import android.net.http.SslError;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.ViewGroup;
+import android.webkit.HttpAuthHandler;
+import android.webkit.JsPromptResult;
+import android.webkit.JsResult;
+import android.webkit.SslErrorHandler;
+import android.webkit.WebChromeClient;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.webkit.WebSettings.LayoutAlgorithm;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+
+public class ReliabilityTestActivity extends Activity {
+
+ public static final String TEST_URL_ACTION = "com.andrdoid.dumprendertree.TestUrlAction";
+ public static final String PARAM_URL = "URL";
+ public static final String PARAM_TIMEOUT = "Timeout";
+ public static final int RESULT_TIMEOUT = 0xDEAD;
+ public static final int MSG_TIMEOUT = 0xC001;
+ public static final int MSG_NAVIGATE = 0xC002;
+
+ private static final String LOGTAG = "ReliabilityTestActivity";
+
+ private WebView webView;
+ private SimpleWebViewClient webViewClient;
+ private SimpleChromeClient chromeClient;
+ private Handler handler;
+ private boolean timeoutFlag;
+ private boolean pageDone;
+ private Object pageDoneLock;
+ private int pageStartCount;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Log.v(LOGTAG, "onCreate, inst=" + Integer.toHexString(hashCode()));
+
+ LinearLayout contentView = new LinearLayout(this);
+ contentView.setOrientation(LinearLayout.VERTICAL);
+ setContentView(contentView);
+ setTitle("Idle");
+
+ webView = new WebView(this);
+ webView.getSettings().setJavaScriptEnabled(true);
+ webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(false);
+ webView.getSettings().setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
+
+ webViewClient = new SimpleWebViewClient();
+ chromeClient = new SimpleChromeClient();
+ webView.setWebViewClient(webViewClient);
+ webView.setWebChromeClient(chromeClient);
+
+ contentView.addView(webView, new LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.FILL_PARENT, 0.0f));
+
+ handler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_TIMEOUT:
+ handleTimeout();
+ return;
+ case MSG_NAVIGATE:
+ navigate((String)msg.obj, msg.arg1);
+ return;
+ }
+ }
+ };
+
+ pageDoneLock = new Object();
+ }
+
+ public void reset() {
+ synchronized (pageDoneLock) {
+ pageDone = false;
+ }
+ timeoutFlag = false;
+ pageStartCount = 0;
+ chromeClient.resetJsTimeout();
+ }
+
+ private void navigate(String url, int timeout) {
+ if(url == null) {
+ Log.v(LOGTAG, "URL is null, cancelling...");
+ finish();
+ }
+ webView.stopLoading();
+ Log.v(LOGTAG, "Navigating to URL: " + url);
+ webView.loadUrl(url);
+
+ if(timeout != 0) {
+ //set a timer with specified timeout (in ms)
+ handler.sendMessageDelayed(handler.obtainMessage(MSG_TIMEOUT),
+ timeout);
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.v(LOGTAG, "onDestroy, inst=" + Integer.toHexString(hashCode()));
+ super.onDestroy();
+ }
+
+ private boolean isPageDone() {
+ synchronized (pageDoneLock) {
+ return pageDone;
+ }
+ }
+
+ private void setPageDone(boolean pageDone) {
+ synchronized (pageDoneLock) {
+ this.pageDone = pageDone;
+ pageDoneLock.notifyAll();
+ }
+ }
+
+ private void handleTimeout() {
+ int progress = webView.getProgress();
+ webView.stopLoading();
+ Log.v(LOGTAG, "Page timeout triggered, progress = " + progress);
+ timeoutFlag = true;
+ }
+
+ public boolean waitUntilDone() {
+ validateNotAppThread();
+ synchronized (pageDoneLock) {
+ while(!isPageDone()) {
+ try {
+ pageDoneLock.wait();
+ } catch (InterruptedException ie) {
+ //no-op
+ }
+ }
+ }
+ return timeoutFlag;
+ }
+
+ public Handler getHandler() {
+ return handler;
+ }
+
+ private final void validateNotAppThread() {
+ if (ActivityThread.currentActivityThread() != null) {
+ throw new RuntimeException(
+ "This method can not be called from the main application thread");
+ }
+ }
+
+ class SimpleWebViewClient extends WebViewClient {
+
+ @Override
+ public void onReceivedError(WebView view, int errorCode, String description,
+ String failingUrl) {
+ Log.v(LOGTAG, "Received WebCore error: code=" + errorCode
+ + ", description=" + description
+ + ", url=" + failingUrl);
+ }
+
+ @Override
+ public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+ //ignore certificate error
+ Log.v(LOGTAG, "Received SSL error: " + error.toString());
+ handler.proceed();
+ }
+
+ @Override
+ public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host,
+ String realm) {
+ //cancel http auth request
+ handler.cancel();
+ }
+
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ pageStartCount++;
+ Log.v(LOGTAG, "onPageStarted: " + url);
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ Log.v(LOGTAG, "onPageFinished: " + url);
+ handler.postDelayed(new WebViewStatusChecker(), 500);
+ }
+ }
+
+ class SimpleChromeClient extends WebChromeClient {
+
+ private int timeoutCounter = 0;
+
+ @Override
+ public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
+ result.confirm();
+ return true;
+ }
+
+ @Override
+ public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
+ result.confirm();
+ return true;
+ }
+
+ @Override
+ public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
+ JsPromptResult result) {
+ result.confirm();
+ return true;
+ }
+
+ @Override
+ public boolean onJsTimeout() {
+ timeoutCounter++;
+ Log.v(LOGTAG, "JavaScript timeout, count=" + timeoutCounter);
+ return timeoutCounter > 2;
+ }
+
+ public void resetJsTimeout() {
+ timeoutCounter = 0;
+ }
+
+ @Override
+ public void onReceivedTitle(WebView view, String title) {
+ ReliabilityTestActivity.this.setTitle(title);
+ }
+ }
+
+ class WebViewStatusChecker implements Runnable {
+
+ private int initialStartCount;
+
+ public WebViewStatusChecker() {
+ initialStartCount = pageStartCount;
+ }
+
+ public void run() {
+ if (initialStartCount == pageStartCount) {
+ //perform cleanup
+ webView.stopLoading();
+ Log.v(LOGTAG, "Finishing URL: " + webView.getUrl());
+ handler.removeMessages(MSG_TIMEOUT);
+ setPageDone(true);
+ }
+ }
+ }
+}
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestsAutoTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestsAutoTest.java
deleted file mode 100644
index 347efde3f07f..000000000000
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/ReliabilityTestsAutoTest.java
+++ /dev/null
@@ -1,209 +0,0 @@
-package com.android.dumprendertree;
-
-import com.android.dumprendertree.TestShellActivity.DumpDataType;
-
-import android.content.Intent;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-
-import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.List;
-import java.util.Vector;
-
-public class ReliabilityTestsAutoTest extends ActivityInstrumentationTestCase2<TestShellActivity> {
-
- private static final String LOGTAG = "ReliabilityTests";
- private static final String TEST_LIST_FILE = "/sdcard/android/reliability_tests_list.txt";
- private static final String TEST_STATUS_FILE = "/sdcard/android/reliability_running_test.txt";
- private static final String TEST_TIMEOUT_FILE = "/sdcard/android/reliability_timeout_test.txt";
- static final String RELIABILITY_TEST_RUNNER_FILES[] = {
- "run_reliability_tests.py"
- };
-
- private boolean finished;
- private List<String> testList;
-
- public ReliabilityTestsAutoTest() {
- super("com.android.dumprendertree", TestShellActivity.class);
- }
-
- private void getTestList() {
- // Read test list.
- testList = new Vector<String>();
- try {
- BufferedReader inReader = new BufferedReader(new FileReader(TEST_LIST_FILE));
- String line;
- while ((line = inReader.readLine()) != null) {
- testList.add(line);
- }
- inReader.close();
- Log.v(LOGTAG, "Test list has " + testList.size() + " test(s).");
- } catch (Exception e) {
- Log.e(LOGTAG, "Error while reading test list : " + e.getMessage());
- }
- }
-
- private void resumeTestList() {
- // read out the test name it stopped last time.
- try {
- BufferedReader inReader = new BufferedReader(new FileReader(TEST_STATUS_FILE));
- String line = inReader.readLine();
- for (int i = 0; i < testList.size(); i++) {
- if (testList.get(i).equals(line)) {
- testList = new Vector<String>(testList.subList(i+1, testList.size()));
- break;
- }
- }
- inReader.close();
- } catch (Exception e) {
- Log.e(LOGTAG, "Error reading " + TEST_STATUS_FILE);
- }
- }
-
- private void clearTestStatus() {
- // Delete TEST_STATUS_FILE
- try {
- File f = new File(TEST_STATUS_FILE);
- if (f.delete())
- Log.v(LOGTAG, "Deleted " + TEST_STATUS_FILE);
- else
- Log.e(LOGTAG, "Fail to delete " + TEST_STATUS_FILE);
- } catch (Exception e) {
- Log.e(LOGTAG, "Fail to delete " + TEST_STATUS_FILE + " : " + e.getMessage());
- }
- }
-
- private void clearTestTimeout() {
- // Delete TEST_TIMEOUT_FILE
- try {
- File f = new File(TEST_TIMEOUT_FILE);
- if (f.delete())
- Log.v(LOGTAG, "Deleted " + TEST_TIMEOUT_FILE);
- else
- Log.e(LOGTAG, "Fail to delete " + TEST_TIMEOUT_FILE);
- } catch (Exception e) {
- Log.e(LOGTAG, "Fail to delete " + TEST_TIMEOUT_FILE + " : " + e.getMessage());
- }
- }
-
- private void updateTestStatus(String s) {
- // Write TEST_STATUS_FILE
- try {
- BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(TEST_STATUS_FILE));
- bos.write(s.getBytes());
- bos.close();
- } catch (Exception e) {
- Log.e(LOGTAG, "Cannot update file " + TEST_STATUS_FILE);
- }
- }
-
- private void writeTimeoutFile(String s) {
- // Write TEST_TIMEOUT_FILE
- try {
- BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(TEST_TIMEOUT_FILE, true));
- bos.write(s.getBytes());
- bos.write('\n');
- bos.close();
- } catch (Exception e) {
- Log.e(LOGTAG, "Cannot update file " + TEST_TIMEOUT_FILE);
- }
- }
-
- private void runReliabilityTest(boolean resume) {
- LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner) getInstrumentation();
-
- getTestList();
- if(!resume)
- clearTestStatus();
- else
- resumeTestList();
-
- TestShellActivity activity = getActivity();
- activity.setDefaultDumpDataType(DumpDataType.NO_OP);
- // Run tests.
- for (int i = 0; i < testList.size(); i++) {
- String s = testList.get(i);
- updateTestStatus(s);
- // Run tests
- runTestAndWaitUntilDone(activity, s, runner.mTimeoutInMillis);
- }
-
- updateTestStatus("#DONE");
-
- activity.finish();
- }
-
- private void runTestAndWaitUntilDone(TestShellActivity activity, String url, int timeout) {
- activity.setCallback(new TestShellCallback() {
- public void finished() {
- synchronized (ReliabilityTestsAutoTest.this) {
- finished = true;
- ReliabilityTestsAutoTest.this.notifyAll();
- }
- }
-
- public void timedOut(String url) {
- writeTimeoutFile(url);
- }
- });
-
- finished = false;
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setClass(activity, TestShellActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
- intent.putExtra(TestShellActivity.TEST_URL, url);
- intent.putExtra(TestShellActivity.TIMEOUT_IN_MILLIS, timeout);
- activity.startActivity(intent);
-
- // Wait until done.
- synchronized (this) {
- while(!finished){
- try {
- this.wait();
- } catch (InterruptedException e) { }
- }
- }
- }
-
- public void startReliabilityTests() {
- clearTestTimeout();
- runReliabilityTest(false);
- }
-
- public void resumeReliabilityTests() {
- runReliabilityTest(true);
- }
-
- public void copyRunnerAssetsToCache() {
- try {
- String out_dir = getActivity().getApplicationContext()
- .getCacheDir().getPath() + "/";
-
- for( int i=0; i< RELIABILITY_TEST_RUNNER_FILES.length; i++) {
- InputStream in = getActivity().getAssets().open(
- RELIABILITY_TEST_RUNNER_FILES[i]);
- OutputStream out = new FileOutputStream(
- out_dir + RELIABILITY_TEST_RUNNER_FILES[i]);
-
- byte[] buf = new byte[2048];
- int len;
-
- while ((len = in.read(buf)) >= 0 ) {
- out.write(buf, 0, len);
- }
- out.close();
- in.close();
- }
- }catch (IOException e) {
- e.printStackTrace();
- }
-
- }
-}
diff --git a/tests/backup/AndroidManifest.xml b/tests/backup/AndroidManifest.xml
index eaeb5b72da94..3778742e105e 100644
--- a/tests/backup/AndroidManifest.xml
+++ b/tests/backup/AndroidManifest.xml
@@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.backuptest">
- <application>
+ <application android:backupAgent="BackupTestAgent">
<activity android:name="BackupTestActivity" android:label="_BackupTest">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -8,10 +8,5 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <service android:name="BackupTestService">
- <intent-filter>
- <action android:name="android.backup.BackupService.SERVICE" />
- </intent-filter>
- </service>
</application>
</manifest>
diff --git a/tests/backup/src/com/android/backuptest/BackupTestService.java b/tests/backup/src/com/android/backuptest/BackupTestAgent.java
index 00eb86e47a74..11e520e67c55 100644
--- a/tests/backup/src/com/android/backuptest/BackupTestService.java
+++ b/tests/backup/src/com/android/backuptest/BackupTestAgent.java
@@ -16,15 +16,15 @@
package com.android.backuptest;
-import android.backup.BackupService;
+import android.app.BackupAgent;
import android.backup.BackupDataOutput;
import android.backup.FileBackupHelper;
import android.os.ParcelFileDescriptor;
import android.util.Log;
-public class BackupTestService extends BackupService
+public class BackupTestAgent extends BackupAgent
{
- static final String TAG = "BackupTestService";
+ static final String TAG = "BackupTestAgent";
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
diff --git a/tests/sketch/AndroidManifest.xml b/tests/sketch/AndroidManifest.xml
index fbf3a09bd224..b8a13f6a7368 100755
--- a/tests/sketch/AndroidManifest.xml
+++ b/tests/sketch/AndroidManifest.xml
@@ -17,7 +17,7 @@
package="com.android.gesture.example">
<uses-permission android:name="android.permission.READ_CONTACTS" />
- <uses-permission android:name="android.permission.WRITE_SDCARD" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:icon="@drawable/icon" android:label="@string/app_name">