summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/SearchDialog.java11
-rw-r--r--core/java/android/app/SearchManager.java98
-rw-r--r--core/java/android/app/SuggestionsAdapter.java159
-rw-r--r--core/java/android/backup/BackupManager.java6
-rw-r--r--core/java/android/backup/IBackupManager.aidl16
-rw-r--r--core/java/android/database/sqlite/SQLiteContentHelper.java92
-rw-r--r--core/java/android/server/search/SearchableInfo.java2
-rw-r--r--core/java/com/android/internal/backup/AdbTransport.java28
-rw-r--r--core/java/com/android/internal/backup/GoogleTransport.java28
-rw-r--r--core/java/com/android/internal/backup/IBackupTransport.aidl1
-rw-r--r--services/java/com/android/server/BackupManagerService.java291
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java3
-rwxr-xr-xtelephony/java/com/android/internal/telephony/cdma/CDMAPhone.java17
13 files changed, 561 insertions, 191 deletions
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index bcb279151b35..0bb483b40d8b 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -1202,16 +1202,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
protected boolean launchSuggestion(int position, int actionKey, String actionMsg) {
Cursor c = mSuggestionsAdapter.getCursor();
if ((c != null) && c.moveToPosition(position)) {
- // let the cursor know which position was clicked
- final Bundle clickResponse = new Bundle(1);
- clickResponse.putInt(SearchManager.RESPOND_EXTRA_POSITION_CLICKED, position);
- final Bundle response = c.respond(clickResponse);
-
- // the convention is to send a position to select in response to a click (if applicable)
- final int posToSelect = response.getInt(
- SearchManager.RESPOND_EXTRA_POSITION_SELECTED,
- SuggestionsAdapter.NO_ITEM_TO_SELECT);
- mSuggestionsAdapter.setListItemToSelect(posToSelect);
+ mSuggestionsAdapter.callCursorOnClick(c, position);
// launch the intent
Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg);
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index b4a3a78eddeb..b0c248c52b11 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -1167,38 +1167,50 @@ public class SearchManager
*/
public final static String EXTRA_DATA_KEY = "intent_extra_data_key";
-
/**
- * Used by the search dialog to ask the global search provider whether there are any pending
- * sources that have yet to respond. Specifically, the search dialog will call
- * {@link Cursor#respond} with a bundle containing this extra as a key, and expect the same key
- * to be in the response, with a boolean value indicating whether there are pending sources.
+ * Defines the constants used in the communication between {@link android.app.SearchDialog} and
+ * the global search provider via {@link Cursor#respond(android.os.Bundle)}.
*
- * {@hide}
+ * @hide
*/
- public final static String RESPOND_EXTRA_PENDING_SOURCES = "respond_extra_pending_sources";
+ public static class DialogCursorProtocol {
- /**
- * Used by the search dialog to tell the cursor that supplied suggestions which item was clicked
- * before launching the intent. The search dialog will call {@link Cursor#respond} with a
- * bundle containing this extra as a key and the position that was clicked as the value.
- *
- * The response bundle will use {@link #RESPOND_EXTRA_POSITION_SELECTED} to return an int value
- * of the index that should be selected, if applicable.
- *
- * {@hide}
- */
- public final static String RESPOND_EXTRA_POSITION_CLICKED = "respond_extra_position_clicked";
+ /**
+ * The sent bundle will contain this integer key, with a value set to one of the events
+ * below.
+ */
+ public final static String METHOD = "DialogCursorProtocol.method";
- /**
- * Used as a key in the response bundle from a call to {@link Cursor#respond} that sends the
- * position that is clicked.
- *
- * @see #RESPOND_EXTRA_POSITION_CLICKED
- *
- * {@hide}
- */
- public final static String RESPOND_EXTRA_POSITION_SELECTED = "respond_extra_position_selected";
+ /**
+ * After data has been refreshed.
+ */
+ public final static int POST_REFRESH = 0;
+ public final static String POST_REFRESH_RECEIVE_ISPENDING
+ = "DialogCursorProtocol.POST_REFRESH.isPending";
+ public final static String POST_REFRESH_RECEIVE_DISPLAY_NOTIFY
+ = "DialogCursorProtocol.POST_REFRESH.displayNotify";
+
+ /**
+ * Just before closing the cursor.
+ */
+ public final static int PRE_CLOSE = 1;
+ public final static String PRE_CLOSE_SEND_MAX_DISPLAY_POS
+ = "DialogCursorProtocol.PRE_CLOSE.sendDisplayPosition";
+
+ /**
+ * When a position has been clicked.
+ */
+ public final static int CLICK = 2;
+ public final static String CLICK_SEND_POSITION
+ = "DialogCursorProtocol.CLICK.sendPosition";
+ public final static String CLICK_RECEIVE_SELECTED_POS
+ = "DialogCursorProtocol.CLICK.receiveSelectedPosition";
+
+ /**
+ * When the threshold received in {@link #POST_REFRESH_RECEIVE_DISPLAY_NOTIFY} is displayed.
+ */
+ public final static int THRESH_HIT = 3;
+ }
/**
* Intent extra data key: Use this key with Intent.ACTION_SEARCH and
@@ -1292,28 +1304,6 @@ public class SearchManager
*/
public final static String SUGGEST_COLUMN_ICON_2 = "suggest_icon_2";
/**
- * Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column,
- * then all suggestions will be provided in a format that includes space for two small icons,
- * one at the left and one at the right of each suggestion. The data in the column must
- * be a blob that contains a bitmap.
- *
- * This column overrides any icon provided in the {@link #SUGGEST_COLUMN_ICON_1} column.
- *
- * @hide
- */
- public final static String SUGGEST_COLUMN_ICON_1_BITMAP = "suggest_icon_1_bitmap";
- /**
- * Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column,
- * then all suggestions will be provided in a format that includes space for two small icons,
- * one at the left and one at the right of each suggestion. The data in the column must
- * be a blob that contains a bitmap.
- *
- * This column overrides any icon provided in the {@link #SUGGEST_COLUMN_ICON_2} column.
- *
- * @hide
- */
- public final static String SUGGEST_COLUMN_ICON_2_BITMAP = "suggest_icon_2_bitmap";
- /**
* Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i>
* this element exists at the given row, this is the action that will be used when
* forming the suggestion's intent. If the element is not provided, the action will be taken
@@ -1415,6 +1405,16 @@ public class SearchManager
*/
public final static String INTENT_ACTION_SEARCH_SETTINGS
= "android.search.action.SEARCH_SETTINGS";
+
+ /**
+ * Intent action for starting a web search provider's settings activity.
+ * Web search providers should handle this intent if they have provider-specific
+ * settings to implement.
+ *
+ * @hide Pending API council approval.
+ */
+ public final static String INTENT_ACTION_WEB_SEARCH_SETTINGS
+ = "android.search.action.WEB_SEARCH_SETTINGS";
/**
* If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java
index 747bec9944dc..e515594e5706 100644
--- a/core/java/android/app/SuggestionsAdapter.java
+++ b/core/java/android/app/SuggestionsAdapter.java
@@ -20,9 +20,6 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources.NotFoundException;
import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
@@ -36,6 +33,8 @@ import android.widget.ImageView;
import android.widget.ResourceCursorAdapter;
import android.widget.TextView;
+import static android.app.SearchManager.DialogCursorProtocol;
+
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -47,15 +46,7 @@ import java.util.WeakHashMap;
* @hide
*/
class SuggestionsAdapter extends ResourceCursorAdapter {
- // The value used to query a cursor whether it is still expecting more input,
- // 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";
@@ -71,17 +62,21 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
private int mText2Col;
private int mIconName1Col;
private int mIconName2Col;
- 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 NO_ITEM_TO_SELECT every time it
+ // ListView to scroll to the right line item. It gets reset to NONE every time it
// is consumed.
- private int mListItemToSelect = NO_ITEM_TO_SELECT;
- static final int NO_ITEM_TO_SELECT = -1;
+ private int mListItemToSelect = NONE;
+ static final int NONE = -1;
+
+ // holds the maximum position that has been displayed to the user
+ int mMaxDisplayed = NONE;
+
+ // holds the position that, when displayed, should result in notifying the cursor
+ int mDisplayNotifyPos = NONE;
public SuggestionsAdapter(Context context, SearchDialog searchDialog, SearchableInfo searchable,
WeakHashMap<String, Drawable> outsideDrawablesCache, boolean globalSearchMode) {
@@ -132,6 +127,11 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
@Override
public void changeCursor(Cursor c) {
if (DBG) Log.d(LOG_TAG, "changeCursor(" + c + ")");
+
+ if (mCursor != null) {
+ callCursorPreClose(mCursor);
+ }
+
super.changeCursor(c);
if (c != null) {
mFormatCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT);
@@ -139,42 +139,73 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
mText2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
- mIconBitmap1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1_BITMAP);
- mIconBitmap2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2_BITMAP);
}
- updateWorking();
}
-
+
+ /**
+ * Handle sending and receiving information associated with
+ * {@link DialogCursorProtocol#PRE_CLOSE}.
+ *
+ * @param cursor The cursor to call.
+ */
+ private void callCursorPreClose(Cursor cursor) {
+ if (!mGlobalSearchMode) return;
+ final Bundle request = new Bundle();
+ request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.PRE_CLOSE);
+ request.putInt(DialogCursorProtocol.PRE_CLOSE_SEND_MAX_DISPLAY_POS, mMaxDisplayed);
+ final Bundle response = cursor.respond(request);
+
+ mMaxDisplayed = -1;
+ }
+
@Override
public void notifyDataSetChanged() {
+ if (DBG) Log.d(LOG_TAG, "notifyDataSetChanged");
super.notifyDataSetChanged();
- updateWorking();
- if (mListItemToSelect != NO_ITEM_TO_SELECT) {
+
+ callCursorPostRefresh(mCursor);
+
+ // look out for the pending item we are supposed to scroll to
+ if (mListItemToSelect != NONE) {
mSearchDialog.setListSelection(mListItemToSelect);
- mListItemToSelect = NO_ITEM_TO_SELECT;
+ mListItemToSelect = NONE;
}
}
-
+
/**
- * 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.
+ * Handle sending and receiving information associated with
+ * {@link DialogCursorProtocol#POST_REFRESH}.
+ *
+ * @param cursor The cursor to call.
*/
- public void setListItemToSelect(int index) {
- mListItemToSelect = index;
+ private void callCursorPostRefresh(Cursor cursor) {
+ if (!mGlobalSearchMode) return;
+ final Bundle request = new Bundle();
+ request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.POST_REFRESH);
+ final Bundle response = cursor.respond(request);
+
+ mSearchDialog.setWorking(
+ response.getBoolean(DialogCursorProtocol.POST_REFRESH_RECEIVE_ISPENDING, false));
+
+ mDisplayNotifyPos =
+ response.getInt(DialogCursorProtocol.POST_REFRESH_RECEIVE_DISPLAY_NOTIFY, -1);
}
-
+
/**
- * Updates the search dialog according to the current working status of the cursor.
+ * Tell the cursor which position was clicked, handling sending and receiving information
+ * associated with {@link DialogCursorProtocol#CLICK}.
+ *
+ * @param cursor The cursor
+ * @param position The position that was clicked.
*/
- private void updateWorking() {
- if (!mGlobalSearchMode || mCursor == null) return;
-
- Bundle request = new Bundle();
- request.putString(SearchManager.RESPOND_EXTRA_PENDING_SOURCES, "DUMMY");
- Bundle response = mCursor.respond(request);
-
- mSearchDialog.setWorking(response.getBoolean(SearchManager.RESPOND_EXTRA_PENDING_SOURCES));
+ void callCursorOnClick(Cursor cursor, int position) {
+ if (!mGlobalSearchMode) return;
+ final Bundle request = new Bundle(1);
+ request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.CLICK);
+ request.putInt(DialogCursorProtocol.CLICK_SEND_POSITION, position);
+ final Bundle response = cursor.respond(request);
+ mListItemToSelect = response.getInt(
+ DialogCursorProtocol.CLICK_RECEIVE_SELECTED_POS, SuggestionsAdapter.NONE);
}
/**
@@ -186,7 +217,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
v.setTag(new ChildViewCache(v));
return v;
}
-
+
/**
* Cache of the child views of drop-drown list items, to avoid looking up the children
* each time the contents of a list item are changed.
@@ -208,15 +239,26 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
@Override
public void bindView(View view, Context context, Cursor cursor) {
ChildViewCache views = (ChildViewCache) view.getTag();
- boolean isHtml = false;
- if (mFormatCol >= 0) {
- String format = cursor.getString(mFormatCol);
- isHtml = "html".equals(format);
+ final int pos = cursor.getPosition();
+
+ // update the maximum position displayed since last refresh
+ if (pos > mMaxDisplayed) {
+ mMaxDisplayed = pos;
}
+
+ // if the cursor wishes to be notified about this position, send it
+ if (mGlobalSearchMode && mDisplayNotifyPos != NONE && pos == mDisplayNotifyPos) {
+ final Bundle request = new Bundle();
+ request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.THRESH_HIT);
+ mCursor.respond(request);
+ mDisplayNotifyPos = NONE; // only notify the first time
+ }
+
+ final boolean isHtml = mFormatCol > 0 && "html".equals(cursor.getString(mFormatCol));
setViewText(cursor, views.mText1, mText1Col, isHtml);
setViewText(cursor, views.mText2, mText2Col, isHtml);
- setViewIcon(cursor, views.mIcon1, mIconBitmap1Col, mIconName1Col);
- setViewIcon(cursor, views.mIcon2, mIconBitmap2Col, mIconName2Col);
+ setViewIcon(cursor, views.mIcon1, mIconName1Col);
+ setViewIcon(cursor, views.mIcon2, mIconName2Col);
}
private void setViewText(Cursor cursor, TextView v, int textCol, boolean isHtml) {
@@ -238,26 +280,15 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
}
}
- private void setViewIcon(Cursor cursor, ImageView v, int iconBitmapCol, int iconNameCol) {
+ private void setViewIcon(Cursor cursor, ImageView v, int iconNameCol) {
if (v == null) {
return;
}
- Drawable drawable = null;
- // First try the bitmap column
- if (iconBitmapCol >= 0) {
- byte[] data = cursor.getBlob(iconBitmapCol);
- if (data != null) {
- Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
- if (bitmap != null) {
- drawable = new BitmapDrawable(bitmap);
- }
- }
- }
- // If there was no bitmap, try the icon resource column.
- if (drawable == null && iconNameCol >= 0) {
- String value = cursor.getString(iconNameCol);
- drawable = getDrawableFromResourceValue(value);
+ if (iconNameCol < 0) {
+ return;
}
+ String value = cursor.getString(iconNameCol);
+ Drawable drawable = getDrawableFromResourceValue(value);
// Set the icon even if the drawable is null, since we need to clear any
// previous icon.
v.setImageDrawable(drawable);
@@ -418,7 +449,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
*/
public static String getColumnString(Cursor cursor, String columnName) {
int col = cursor.getColumnIndex(columnName);
- if (col == NO_ITEM_TO_SELECT) {
+ if (col == NONE) {
return null;
}
return cursor.getString(col);
diff --git a/core/java/android/backup/BackupManager.java b/core/java/android/backup/BackupManager.java
index 6f0b2eef603f..30f781e659e5 100644
--- a/core/java/android/backup/BackupManager.java
+++ b/core/java/android/backup/BackupManager.java
@@ -42,6 +42,12 @@ public class BackupManager {
private IBackupManager mService;
/**
+ * Defined backup transports understood by {@link IBackupManager.selectBackupTransport}.
+ */
+ public static final int TRANSPORT_ADB = 1;
+ public static final int TRANSPORT_GOOGLE = 2;
+
+ /**
* Constructs a BackupManager object through which the application can
* communicate with the Android backup system.
*
diff --git a/core/java/android/backup/IBackupManager.aidl b/core/java/android/backup/IBackupManager.aidl
index 3468d70962c2..f5b82fe57851 100644
--- a/core/java/android/backup/IBackupManager.aidl
+++ b/core/java/android/backup/IBackupManager.aidl
@@ -36,20 +36,28 @@ interface IBackupManager {
/**
* 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: permission
+ * Schedule a full backup of the given package. Callers must hold the
+ * android.permission.BACKUP permission to use this method.
*/
oneway void scheduleFullBackup(String packageName);
+
+ /**
+ * Specify a default backup transport. Callers must hold the
+ * android.permission.BACKUP permission to use this method.
+ *
+ * @param transportID The ID of the transport to select. This should be one
+ * of {@link BackupManager.TRANSPORT_GOOGLE} or {@link BackupManager.TRANSPORT_ADB}.
+ * @return The ID of the previously selected transport.
+ */
+ int selectBackupTransport(int transportID);
}
diff --git a/core/java/android/database/sqlite/SQLiteContentHelper.java b/core/java/android/database/sqlite/SQLiteContentHelper.java
new file mode 100644
index 000000000000..2800d86279b1
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteContentHelper.java
@@ -0,0 +1,92 @@
+/*
+ * 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.database.sqlite;
+
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.os.MemoryFile;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Some helper functions for using SQLite database to implement content providers.
+ *
+ * @hide
+ */
+public class SQLiteContentHelper {
+
+ /**
+ * Runs an SQLite query and returns an AssetFileDescriptor for the
+ * blob in column 0 of the first row. If the first column does
+ * not contain a blob, an unspecified exception is thrown.
+ *
+ * @param db Handle to a readable database.
+ * @param sql SQL query, possibly with query arguments.
+ * @param selectionArgs Query argument values, or {@code null} for no argument.
+ * @return If no exception is thrown, a non-null AssetFileDescriptor is returned.
+ * @throws FileNotFoundException If the query returns no results or the
+ * value of column 0 is NULL, or if there is an error creating the
+ * asset file descriptor.
+ */
+ public static AssetFileDescriptor getBlobColumnAsAssetFile(SQLiteDatabase db, String sql,
+ String[] selectionArgs) throws FileNotFoundException {
+ try {
+ MemoryFile file = simpleQueryForBlobMemoryFile(db, sql, selectionArgs);
+ if (file == null) {
+ throw new FileNotFoundException("No results.");
+ }
+ return AssetFileDescriptor.fromMemoryFile(file);
+ } catch (IOException ex) {
+ throw new FileNotFoundException(ex.toString());
+ }
+ }
+
+ /**
+ * Runs an SQLite query and returns a MemoryFile for the
+ * blob in column 0 of the first row. If the first column does
+ * not contain a blob, an unspecified exception is thrown.
+ *
+ * @return A memory file, or {@code null} if the query returns no results
+ * or the value column 0 is NULL.
+ * @throws IOException If there is an error creating the memory file.
+ */
+ // TODO: make this native and use the SQLite blob API to reduce copying
+ private static MemoryFile simpleQueryForBlobMemoryFile(SQLiteDatabase db, String sql,
+ String[] selectionArgs) throws IOException {
+ Cursor cursor = db.rawQuery(sql, selectionArgs);
+ if (cursor == null) {
+ return null;
+ }
+ try {
+ if (!cursor.moveToFirst()) {
+ return null;
+ }
+ byte[] bytes = cursor.getBlob(0);
+ if (bytes == null) {
+ return null;
+ }
+ MemoryFile file = new MemoryFile(null, bytes.length);
+ file.writeBytes(bytes, 0, 0, bytes.length);
+ file.deactivate();
+ return file;
+ } finally {
+ cursor.close();
+ }
+ }
+
+}
diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java
index 842fc7573aef..c08314272d50 100644
--- a/core/java/android/server/search/SearchableInfo.java
+++ b/core/java/android/server/search/SearchableInfo.java
@@ -40,7 +40,7 @@ import java.util.HashMap;
public final class SearchableInfo implements Parcelable {
// general debugging support
- private static final boolean DBG = true;
+ private static final boolean DBG = false;
private static final String LOG_TAG = "SearchableInfo";
// static strings used for XML lookups.
diff --git a/core/java/com/android/internal/backup/AdbTransport.java b/core/java/com/android/internal/backup/AdbTransport.java
new file mode 100644
index 000000000000..acb3273a77b4
--- /dev/null
+++ b/core/java/com/android/internal/backup/AdbTransport.java
@@ -0,0 +1,28 @@
+package com.android.internal.backup;
+
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+/**
+ * Backup transport for full backup over adb. This transport pipes everything to
+ * a file in a known location in /cache, which 'adb backup' then pulls to the desktop
+ * (deleting it afterwards).
+ */
+
+public class AdbTransport extends IBackupTransport.Stub {
+
+ public int startSession() throws RemoteException {
+ return 0;
+ }
+
+ public int endSession() throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public int performBackup(String packageName, ParcelFileDescriptor data)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+}
diff --git a/core/java/com/android/internal/backup/GoogleTransport.java b/core/java/com/android/internal/backup/GoogleTransport.java
new file mode 100644
index 000000000000..85ab21eac425
--- /dev/null
+++ b/core/java/com/android/internal/backup/GoogleTransport.java
@@ -0,0 +1,28 @@
+package com.android.internal.backup;
+
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+/**
+ * Backup transport for saving data to Google cloud storage.
+ */
+
+public class GoogleTransport extends IBackupTransport.Stub {
+
+ public int endSession() throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public int performBackup(String packageName, ParcelFileDescriptor data)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public int startSession() throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+}
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index ce39768137e2..2b44fe7c8393 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -16,6 +16,7 @@
package com.android.internal.backup;
+import android.os.Bundle;
import android.os.ParcelFileDescriptor;
/** {@hide} */
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 82ed1e39e0e5..b003e76c5cec 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -26,6 +26,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -34,11 +35,17 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
import android.backup.IBackupManager;
+import android.backup.BackupManager;
+
+import com.android.internal.backup.AdbTransport;
+import com.android.internal.backup.GoogleTransport;
+import com.android.internal.backup.IBackupTransport;
import java.io.File;
import java.io.FileDescriptor;
@@ -59,6 +66,7 @@ class BackupManagerService extends IBackupManager.Stub {
//private static final long COLLECTION_INTERVAL = 3 * 60 * 1000;
private static final int MSG_RUN_BACKUP = 1;
+ private static final int MSG_RUN_FULL_BACKUP = 2;
private Context mContext;
private PackageManager mPackageManager;
@@ -89,6 +97,16 @@ class BackupManagerService extends IBackupManager.Stub {
private ArrayList<BackupRequest> mBackupQueue;
private final Object mQueueLock = new Object();
+ // The thread performing the sequence of queued backups binds to each app's agent
+ // in succession. Bind notifications are asynchronously delivered through the
+ // Activity Manager; use this lock object to signal when a requested binding has
+ // completed.
+ private final Object mAgentConnectLock = new Object();
+ private IBackupAgent mConnectedAgent;
+ private volatile boolean mConnecting;
+
+ private int mTransportId;
+
private File mStateDir;
private File mDataDir;
@@ -101,6 +119,7 @@ class BackupManagerService extends IBackupManager.Stub {
mStateDir = new File(Environment.getDataDirectory(), "backup");
mStateDir.mkdirs();
mDataDir = Environment.getDownloadCacheDirectory();
+ mTransportId = BackupManager.TRANSPORT_GOOGLE;
// Build our mapping of uid to backup client services
synchronized (mBackupParticipants) {
@@ -130,6 +149,8 @@ class BackupManagerService extends IBackupManager.Stub {
return;
}
+ // !!! TODO: this is buggy right now; we wind up with duplicate participant entries
+ // after using 'adb install -r' of a participating app
String action = intent.getAction();
if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
synchronized (mBackupParticipants) {
@@ -166,7 +187,7 @@ class BackupManagerService extends IBackupManager.Stub {
// snapshot the pending-backup set and work on that
synchronized (mQueueLock) {
if (mBackupQueue == null) {
- mBackupQueue = new ArrayList();
+ mBackupQueue = new ArrayList<BackupRequest>();
for (BackupRequest b: mPendingBackups.values()) {
mBackupQueue.add(b);
}
@@ -176,7 +197,11 @@ class BackupManagerService extends IBackupManager.Stub {
// WARNING: If we crash after this line, anything in mPendingBackups will
// be lost. FIX THIS.
}
- startOneAgent();
+ //startOneAgent();
+ (new PerformBackupThread(mTransportId, mBackupQueue)).run();
+ break;
+
+ case MSG_RUN_FULL_BACKUP:
break;
}
}
@@ -221,23 +246,15 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
- void processOneBackup(String packageName, IBackupAgent bs) {
+ void processOneBackup(BackupRequest request, IBackupAgent agent, IBackupTransport transport) {
+ final String packageName = request.appInfo.packageName;
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);
- }
-
try {
- // !!! TODO right now these naming schemes limit applications to
- // one backup service per package
- 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");
+ // !!! TODO: get the state file dir from the transport
+ File savedStateName = new File(mStateDir, packageName);
+ File backupDataName = new File(mDataDir, packageName + ".data");
+ File newStateName = new File(mStateDir, packageName + ".new");
// In a full backup, we pass a null ParcelFileDescriptor as
// the saved-state "file"
@@ -259,9 +276,10 @@ class BackupManagerService extends IBackupManager.Stub {
ParcelFileDescriptor.MODE_CREATE);
// Run the target's backup pass
+ boolean success = false;
try {
- // TODO: Make this oneway
- bs.doBackup(savedState, backupData, newState);
+ agent.doBackup(savedState, backupData, newState);
+ success = true;
} finally {
if (savedState != null) {
savedState.close();
@@ -270,43 +288,34 @@ class BackupManagerService extends IBackupManager.Stub {
newState.close();
}
- // !!! TODO: Now propagate the newly-backed-up data to the transport
-
- // !!! TODO: After successful transport, delete the now-stale data
- // and juggle the files so that next time the new state is passed
- //backupDataName.delete();
- newStateName.renameTo(savedStateName);
-
+ // Now propagate the newly-backed-up data to the transport
+ if (success) {
+ backupData =
+ ParcelFileDescriptor.open(backupDataName, ParcelFileDescriptor.MODE_READ_ONLY);
+ int error = transport.performBackup(packageName, backupData);
+
+ // !!! TODO: After successful transport, delete the now-stale data
+ // and juggle the files so that next time the new state is passed
+ //backupDataName.delete();
+ newStateName.renameTo(savedStateName);
+ }
} catch (FileNotFoundException fnf) {
Log.d(TAG, "File not found on backup: ");
fnf.printStackTrace();
} catch (RemoteException e) {
- Log.d(TAG, "Remote target " + packageName + " threw during backup:");
+ Log.d(TAG, "Remote target " + request.appInfo.packageName + " threw during backup:");
e.printStackTrace();
} catch (Exception e) {
Log.w(TAG, "Final exception guard in backup: ");
e.printStackTrace();
}
- synchronized (mQueueLock) {
- mBackupQueue.remove(0);
- }
-
- if (request != null) {
- try {
- mActivityManager.unbindBackupAgent(request.appInfo);
- } catch (RemoteException e) {
- // can't happen
- }
- }
-
- // start the next one
- startOneAgent();
}
// 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) {
// Look for apps that define the android:backupAgent attribute
+ if (DEBUG) Log.v(TAG, "addPackageParticipantsLocked: " + packageName);
List<ApplicationInfo> targetApps = allAgentApps();
addPackageParticipantsLockedInner(packageName, targetApps);
}
@@ -336,6 +345,7 @@ 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) {
+ if (DEBUG) Log.v(TAG, "removePackageParticipantsLocked: " + packageName);
List<ApplicationInfo> allApps = null;
if (packageName != null) {
allApps = new ArrayList<ApplicationInfo>();
@@ -354,6 +364,13 @@ class BackupManagerService extends IBackupManager.Stub {
private void removePackageParticipantsLockedInner(String packageName,
List<ApplicationInfo> agents) {
+ if (DEBUG) {
+ Log.v(TAG, "removePackageParticipantsLockedInner (" + packageName
+ + ") removing " + agents.size() + " entries");
+ for (ApplicationInfo a : agents) {
+ Log.v(TAG, " - " + a);
+ }
+ }
for (ApplicationInfo app : agents) {
if (packageName == null || app.packageName.equals(packageName)) {
int uid = app.uid;
@@ -361,8 +378,7 @@ class BackupManagerService extends IBackupManager.Stub {
if (set != null) {
set.remove(app);
if (set.size() == 0) {
- mBackupParticipants.put(uid, null);
- }
+ mBackupParticipants.delete(uid); }
}
}
}
@@ -390,6 +406,7 @@ class BackupManagerService extends IBackupManager.Stub {
Log.e(TAG, "updatePackageParticipants called with null package name");
return;
}
+ if (DEBUG) Log.v(TAG, "updatePackageParticipantsLocked: " + packageName);
// brute force but small code size
List<ApplicationInfo> allApps = allAgentApps();
@@ -397,6 +414,128 @@ class BackupManagerService extends IBackupManager.Stub {
addPackageParticipantsLockedInner(packageName, allApps);
}
+ // ----- Back up a set of applications via a worker thread -----
+
+ class PerformBackupThread extends Thread {
+ private static final String TAG = "PerformBackupThread";
+ int mTransport;
+ ArrayList<BackupRequest> mQueue;
+
+ public PerformBackupThread(int transportId, ArrayList<BackupRequest> queue) {
+ mTransport = transportId;
+ mQueue = queue;
+ }
+
+ @Override
+ public void run() {
+ /*
+ * 1. start up the current transport
+ * 2. for each item in the queue:
+ * 2a. bind the agent [wait for async attach]
+ * 2b. set up the files and call doBackup()
+ * 2c. unbind the agent
+ * 3. tear down the transport
+ * 4. done!
+ */
+ if (DEBUG) Log.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
+
+ // stand up the current transport
+ IBackupTransport transport = null;
+ switch (mTransport) {
+ case BackupManager.TRANSPORT_ADB:
+ if (DEBUG) Log.v(TAG, "Initializing adb transport");
+ transport = new AdbTransport();
+ break;
+
+ case BackupManager.TRANSPORT_GOOGLE:
+ if (DEBUG) Log.v(TAG, "Initializing Google transport");
+ //!!! TODO: stand up the google backup transport here
+ transport = new GoogleTransport();
+ break;
+
+ default:
+ Log.e(TAG, "Perform backup with unknown transport " + mTransport);
+ // !!! TODO: re-enqueue the backup queue for later?
+ return;
+ }
+
+ try {
+ transport.startSession();
+ } catch (Exception e) {
+ Log.e(TAG, "Error starting backup session");
+ e.printStackTrace();
+ // !!! TODO: re-enqueue the backup queue for later?
+ return;
+ }
+
+ // The transport is up and running; now run all the backups in our queue
+ doQueuedBackups(transport);
+
+ // Finally, tear down the transport
+ try {
+ transport.endSession();
+ } catch (Exception e) {
+ Log.e(TAG, "Error ending transport");
+ e.printStackTrace();
+ }
+ }
+
+ private void doQueuedBackups(IBackupTransport transport) {
+ for (BackupRequest request : mQueue) {
+ Log.d(TAG, "starting agent for " + request);
+ // !!! TODO: need to handle the restore case?
+
+ IBackupAgent agent = null;
+ int mode = (request.fullBackup)
+ ? IApplicationThread.BACKUP_MODE_FULL
+ : IApplicationThread.BACKUP_MODE_INCREMENTAL;
+ try {
+ synchronized(mAgentConnectLock) {
+ mConnecting = true;
+ mConnectedAgent = null;
+ if (mActivityManager.bindBackupAgent(request.appInfo, mode)) {
+ Log.d(TAG, "awaiting agent for " + request);
+
+ // success; wait for the agent to arrive
+ while (mConnecting && mConnectedAgent == null) {
+ try {
+ mAgentConnectLock.wait(10000);
+ } catch (InterruptedException e) {
+ // just retry
+ continue;
+ }
+ }
+
+ // if we timed out with no connect, abort and move on
+ if (mConnecting == true) {
+ Log.w(TAG, "Timeout waiting for agent " + request);
+ continue;
+ }
+ agent = mConnectedAgent;
+ }
+ }
+ } 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);
+ }
+
+ // successful bind? run the backup for this agent
+ if (agent != null) {
+ processOneBackup(request, agent, transport);
+ }
+
+ // send the unbind even on timeout, just in case
+ try {
+ mActivityManager.unbindBackupAgent(request.appInfo);
+ } catch (RemoteException e) {
+ // can't happen
+ }
+ }
+ }
+ }
+
// ----- IBackupManager binder interface -----
public void dataChanged(String packageName) throws RemoteException {
@@ -432,45 +571,75 @@ class BackupManagerService extends IBackupManager.Stub {
// 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.
+ mBackupHandler.removeMessages(MSG_RUN_BACKUP);
mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL);
}
}
}
- // Schedule a backup pass for a given package, even if the caller is not part of
- // that uid or package itself.
+ // Schedule a backup pass for a given package. This method will schedule a
+ // full backup even for apps that do not declare an android:backupAgent, so
+ // use with care.
public void scheduleFullBackup(String packageName) throws RemoteException {
- // !!! TODO: protect with a signature-or-system permission?
+ mContext.enforceCallingPermission("android.permission.BACKUP", "scheduleFullBackup");
+
+ if (DEBUG) Log.v(TAG, "Scheduling immediate full backup for " + packageName);
synchronized (mQueueLock) {
- int numKeys = mBackupParticipants.size();
- for (int index = 0; index < numKeys; index++) {
- int uid = mBackupParticipants.keyAt(index);
- HashSet<ApplicationInfo> servicesAtUid = mBackupParticipants.get(uid);
- for (ApplicationInfo app: servicesAtUid) {
- if (app.packageName.equals(packageName)) {
- mPendingBackups.put(app, new BackupRequest(app, true));
- }
- }
+ try {
+ ApplicationInfo app = mPackageManager.getApplicationInfo(packageName, 0);
+ mPendingBackups.put(app, new BackupRequest(app, true));
+ mBackupHandler.sendEmptyMessage(MSG_RUN_FULL_BACKUP);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Could not find app for " + packageName + " to schedule full backup");
}
}
}
- // Callback: a requested backup agent has been instantiated
+ // Select which transport to use for the next backup operation
+ public int selectBackupTransport(int transportId) {
+ mContext.enforceCallingPermission("android.permission.BACKUP", "selectBackupTransport");
+
+ int prevTransport = mTransportId;
+ mTransportId = transportId;
+ return prevTransport;
+ }
+
+ // Callback: a requested backup agent has been instantiated. This should only
+ // be called from the Activity Manager.
public void agentConnected(String packageName, IBinder agentBinder) {
- Log.d(TAG, "agentConnected pkg=" + packageName + " agent=" + agentBinder);
- IBackupAgent bs = IBackupAgent.Stub.asInterface(agentBinder);
- processOneBackup(packageName, bs);
+ synchronized(mAgentConnectLock) {
+ if (Binder.getCallingUid() == Process.SYSTEM_UID) {
+ Log.d(TAG, "agentConnected pkg=" + packageName + " agent=" + agentBinder);
+ IBackupAgent agent = IBackupAgent.Stub.asInterface(agentBinder);
+ mConnectedAgent = agent;
+ mConnecting = false;
+ } else {
+ Log.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
+ + " claiming agent connected");
+ }
+ mAgentConnectLock.notifyAll();
+ }
}
// 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.
+ // will be null. This should only be called from the Activity Manager.
public void agentDisconnected(String packageName) {
// TODO: handle backup being interrupted
+ synchronized(mAgentConnectLock) {
+ if (Binder.getCallingUid() == Process.SYSTEM_UID) {
+ mConnectedAgent = null;
+ mConnecting = false;
+ } else {
+ Log.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
+ + " claiming agent disconnected");
+ }
+ mAgentConnectLock.notifyAll();
+ }
}
-
-
+
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mQueueLock) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 5202b6f1d160..736c0da67b40 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -10377,6 +10377,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
return;
}
+ long oldIdent = Binder.clearCallingIdentity();
try {
IBackupManager bm = IBackupManager.Stub.asInterface(
ServiceManager.getService(Context.BACKUP_SERVICE));
@@ -10386,6 +10387,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
} catch (Exception e) {
Log.w(TAG, "Exception trying to deliver BackupAgent binding: ");
e.printStackTrace();
+ } finally {
+ Binder.restoreCallingIdentity(oldIdent);
}
}
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
index 7ba99519f2df..0ebe5077ab98 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -38,6 +38,7 @@ import static com.android.internal.telephony.TelephonyProperties.PROPERTY_BASEBA
import static com.android.internal.telephony.TelephonyProperties.PROPERTY_INECM_MODE;
import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.DataConnection;
@@ -525,8 +526,20 @@ public class CDMAPhone extends PhoneBase {
public void
getNeighboringCids(Message response) {
- // WINK:TODO: implement after Cupcake merge
- mCM.getNeighboringCids(response); // workaround.
+ /*
+ * This is currently not implemented. At least as of June
+ * 2009, there is no neighbor cell information available for
+ * CDMA because some party is resisting making this
+ * information readily available. Consequently, calling this
+ * function can have no useful effect. This situation may
+ * (and hopefully will) change in the future.
+ */
+ if (response != null) {
+ CommandException ce = new CommandException(
+ CommandException.Error.REQUEST_NOT_SUPPORTED);
+ AsyncResult.forMessage(response).exception = ce;
+ response.sendToTarget();
+ }
}
public DataState getDataConnectionState() {