summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/app/Activity.java52
-rw-r--r--core/java/android/app/FullBackupAgent.java5
-rw-r--r--core/java/android/app/ISearchManager.aidl14
-rw-r--r--core/java/android/app/ISearchManagerCallback.aidl23
-rw-r--r--core/java/android/app/SearchDialog.java55
-rw-r--r--core/java/android/app/SearchManager.java199
-rw-r--r--core/java/android/backup/BackupDataInput.java103
-rw-r--r--core/java/android/backup/BackupDataOutput.java22
-rw-r--r--core/java/android/backup/FileBackupHelper.java43
-rw-r--r--core/java/android/backup/RestoreHelper.java23
-rw-r--r--core/java/android/backup/RestoreHelperDistributor.java28
-rw-r--r--core/java/android/backup/SharedPreferencesBackupHelper.java23
-rw-r--r--core/java/android/server/search/SearchManagerService.java195
-rw-r--r--core/java/android/server/search/SearchableInfo.java20
-rw-r--r--core/java/android/text/format/Formatter.java10
-rw-r--r--core/java/com/android/internal/backup/LocalTransport.java86
16 files changed, 734 insertions, 167 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index f9b3d05..7fb3449 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -628,6 +628,8 @@ public class Activity extends ContextThemeWrapper
boolean mStartedActivity;
/*package*/ int mConfigChangeFlags;
/*package*/ Configuration mCurrentConfig;
+ private SearchManager mSearchManager;
+ private Bundle mSearchDialogState = null;
private Window mWindow;
@@ -788,6 +790,9 @@ public class Activity extends ContextThemeWrapper
protected void onCreate(Bundle savedInstanceState) {
mVisibleFromClient = mWindow.getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, true);
+ // uses super.getSystemService() since this.getSystemService() looks at the
+ // mSearchManager field.
+ mSearchManager = (SearchManager) super.getSystemService(Context.SEARCH_SERVICE);
mCalled = true;
}
@@ -805,9 +810,10 @@ public class Activity extends ContextThemeWrapper
// Also restore the state of a search dialog (if any)
// TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.restoreSearchDialog(savedInstanceState, SAVED_SEARCH_DIALOG_KEY);
+ Bundle searchState = savedInstanceState.getBundle(SAVED_SEARCH_DIALOG_KEY);
+ if (searchState != null) {
+ mSearchManager.restoreSearchDialog(searchState);
+ }
}
/**
@@ -1013,9 +1019,11 @@ public class Activity extends ContextThemeWrapper
// Also save the state of a search dialog (if any)
// TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.saveSearchDialog(outState, SAVED_SEARCH_DIALOG_KEY);
+ // onPause() should always be called before this method, so mSearchManagerState
+ // should be up to date.
+ if (mSearchDialogState != null) {
+ outState.putBundle(SAVED_SEARCH_DIALOG_KEY, mSearchDialogState);
+ }
}
/**
@@ -1286,12 +1294,6 @@ public class Activity extends ContextThemeWrapper
}
}
}
-
- // also dismiss search dialog if showing
- // TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.stopSearch();
// close any cursors we are managing.
int numCursors = mManagedCursors.size();
@@ -1301,6 +1303,10 @@ public class Activity extends ContextThemeWrapper
c.mCursor.close();
}
}
+
+ // Clear any search state saved in performPause(). If the state may be needed in the
+ // future, it will have been saved by performSaveInstanceState()
+ mSearchDialogState = null;
}
/**
@@ -1324,9 +1330,7 @@ public class Activity extends ContextThemeWrapper
// also update search dialog if showing
// TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.onConfigurationChanged(newConfig);
+ mSearchManager.onConfigurationChanged(newConfig);
if (mWindow != null) {
// Pass the configuration changed event to the window
@@ -2543,10 +2547,7 @@ public class Activity extends ContextThemeWrapper
*/
public void startSearch(String initialQuery, boolean selectInitialQuery,
Bundle appSearchData, boolean globalSearch) {
- // activate the search manager and start it up!
- SearchManager searchManager = (SearchManager)
- getSystemService(Context.SEARCH_SERVICE);
- searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
+ mSearchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
appSearchData, globalSearch);
}
@@ -3265,6 +3266,8 @@ public class Activity extends ContextThemeWrapper
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
+ } else if (SEARCH_SERVICE.equals(name)) {
+ return mSearchManager;
}
return super.getSystemService(name);
}
@@ -3563,10 +3566,21 @@ public class Activity extends ContextThemeWrapper
"Activity " + mComponent.toShortString() +
" did not call through to super.onPostResume()");
}
+
+ // restore search dialog, if any
+ if (mSearchDialogState != null) {
+ mSearchManager.restoreSearchDialog(mSearchDialogState);
+ }
+ mSearchDialogState = null;
}
final void performPause() {
onPause();
+
+ // save search dialog state if the search dialog is open,
+ // and then dismiss the search dialog
+ mSearchDialogState = mSearchManager.saveSearchDialog();
+ mSearchManager.stopSearch();
}
final void performUserLeaving() {
diff --git a/core/java/android/app/FullBackupAgent.java b/core/java/android/app/FullBackupAgent.java
index 18d62e3..bf5cb5d 100644
--- a/core/java/android/app/FullBackupAgent.java
+++ b/core/java/android/app/FullBackupAgent.java
@@ -47,12 +47,11 @@ public class FullBackupAgent extends BackupAgent {
}
// That's the file set; now back it all up
- FileBackupHelper.performBackup(this, oldState, data, newState,
- (String[]) allFiles.toArray());
+ FileBackupHelper helper = new FileBackupHelper(this);
+ helper.performBackup(oldState, data, newState, (String[])allFiles.toArray());
}
@Override
public void onRestore(ParcelFileDescriptor data, ParcelFileDescriptor newState) {
}
-
}
diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl
index 374423e..e8bd60a 100644
--- a/core/java/android/app/ISearchManager.aidl
+++ b/core/java/android/app/ISearchManager.aidl
@@ -16,7 +16,10 @@
package android.app;
+import android.app.ISearchManagerCallback;
import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.os.Bundle;
import android.server.search.SearchableInfo;
/** @hide */
@@ -26,4 +29,15 @@ interface ISearchManager {
List<SearchableInfo> getSearchablesForWebSearch();
SearchableInfo getDefaultSearchableForWebSearch();
void setDefaultWebSearch(in ComponentName component);
+ void startSearch(in String initialQuery,
+ boolean selectInitialQuery,
+ in ComponentName launchActivity,
+ in Bundle appSearchData,
+ boolean globalSearch,
+ ISearchManagerCallback searchManagerCallback);
+ void stopSearch();
+ boolean isVisible();
+ Bundle onSaveInstanceState();
+ void onRestoreInstanceState(in Bundle savedInstanceState);
+ void onConfigurationChanged(in Configuration newConfig);
}
diff --git a/core/java/android/app/ISearchManagerCallback.aidl b/core/java/android/app/ISearchManagerCallback.aidl
new file mode 100644
index 0000000..bdfb2ba
--- /dev/null
+++ b/core/java/android/app/ISearchManagerCallback.aidl
@@ -0,0 +1,23 @@
+/**
+ * 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.app;
+
+/** @hide */
+oneway interface ISearchManagerCallback {
+ void onDismiss();
+ void onCancel();
+}
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 7de6572..9141c4c 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -76,8 +76,8 @@ import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
- * In-application-process implementation of Search Bar. This is still controlled by the
- * SearchManager, but it runs in the current activity's process to keep things lighter weight.
+ * System search dialog. This is controlled by the
+ * SearchManagerService and runs in the system process.
*
* @hide
*/
@@ -179,17 +179,17 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Window theWindow = getWindow();
- theWindow.setGravity(Gravity.TOP|Gravity.FILL_HORIZONTAL);
-
setContentView(com.android.internal.R.layout.search_bar);
- theWindow.setLayout(ViewGroup.LayoutParams.FILL_PARENT,
- // taking up the whole window (even when transparent) is less than ideal,
- // but necessary to show the popup window until the window manager supports
- // having windows anchored by their parent but not clipped by them.
- ViewGroup.LayoutParams.FILL_PARENT);
+ Window theWindow = getWindow();
WindowManager.LayoutParams lp = theWindow.getAttributes();
+ lp.type = WindowManager.LayoutParams.TYPE_SEARCH_BAR;
+ lp.width = ViewGroup.LayoutParams.FILL_PARENT;
+ // taking up the whole window (even when transparent) is less than ideal,
+ // but necessary to show the popup window until the window manager supports
+ // having windows anchored by their parent but not clipped by them.
+ lp.height = ViewGroup.LayoutParams.FILL_PARENT;
+ lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
theWindow.setAttributes(lp);
@@ -234,10 +234,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// Save voice intent for later queries/launching
mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+ mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mLocationManager =
(LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE);
@@ -278,12 +280,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
*/
public boolean show(String initialQuery, boolean selectInitialQuery,
ComponentName componentName, Bundle appSearchData, boolean globalSearch) {
- if (isShowing()) {
- // race condition - already showing but not handling events yet.
- // in this case, just discard the "show" request
- return true;
- }
-
+
// Reset any stored values from last time dialog was shown.
mStoredComponentName = null;
mStoredAppSearchData = null;
@@ -442,11 +439,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
stopLocationUpdates();
- // TODO: Removing the listeners means that they never get called, since
- // Dialog.dismissDialog() calls onStop() before sendDismissMessage().
- setOnCancelListener(null);
- setOnDismissListener(null);
-
// stop receiving broadcasts (throws exception if none registered)
try {
getContext().unregisterReceiver(mBroadcastReceiver);
@@ -654,15 +646,15 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mSearchAutoComplete.setDropDownAnimationStyle(0); // no animation
mSearchAutoComplete.setThreshold(mSearchable.getSuggestThreshold());
+ // we dismiss the entire dialog instead
+ mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
if (mGlobalSearchMode) {
mSearchAutoComplete.setDropDownAlwaysVisible(true); // fill space until results come in
- mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
mSearchAutoComplete.setDropDownBackgroundResource(
com.android.internal.R.drawable.search_dropdown_background);
} else {
mSearchAutoComplete.setDropDownAlwaysVisible(false);
- mSearchAutoComplete.setDropDownDismissedOnCompletion(true);
mSearchAutoComplete.setDropDownBackgroundResource(
com.android.internal.R.drawable.search_dropdown_background_apps);
}
@@ -1317,7 +1309,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
/**
- * Launches an intent. Also dismisses the search dialog if not in global search mode.
+ * Launches an intent and dismisses the search dialog (unless the intent
+ * is one of the special intents that modifies the state of the search dialog).
*/
private void launchIntent(Intent intent) {
if (intent == null) {
@@ -1326,9 +1319,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (handleSpecialIntent(intent)){
return;
}
- if (!mGlobalSearchMode) {
- dismiss();
- }
+ dismiss();
getContext().startActivity(intent);
}
@@ -1511,6 +1502,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
int actionKey, String actionMsg) {
// Now build the Intent
Intent intent = new Intent(action);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (data != null) {
intent.setData(data);
}
@@ -1595,14 +1587,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private boolean isEmpty() {
return TextUtils.getTrimmedLength(getText()) == 0;
}
-
- /**
- * Clears the entered text.
- */
- private void clear() {
- setText("");
- }
-
+
/**
* We override this method to avoid replacing the query box text
* when a suggestion is clicked.
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 820f192..1ddd20a 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -28,6 +28,7 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.server.search.SearchableInfo;
+import android.util.Log;
import android.view.KeyEvent;
import java.util.List;
@@ -1108,6 +1109,10 @@ import java.util.List;
public class SearchManager
implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener
{
+
+ private static final boolean DBG = false;
+ private static final String TAG = "SearchManager";
+
/**
* This is a shortcut definition for the default menu key to use for invoking search.
*
@@ -1494,12 +1499,14 @@ public class SearchManager
private static ISearchManager sService = getSearchManagerService();
private final Context mContext;
- private final Handler mHandler;
-
- private SearchDialog mSearchDialog;
-
- private OnDismissListener mDismissListener = null;
- private OnCancelListener mCancelListener = null;
+
+ // package private since they are used by the inner class SearchManagerCallback
+ /* package */ boolean mIsShowing = false;
+ /* package */ final Handler mHandler;
+ /* package */ OnDismissListener mDismissListener = null;
+ /* package */ OnCancelListener mCancelListener = null;
+
+ private final SearchManagerCallback mSearchManagerCallback = new SearchManagerCallback();
/*package*/ SearchManager(Context context, Handler handler) {
mContext = context;
@@ -1551,17 +1558,16 @@ public class SearchManager
ComponentName launchActivity,
Bundle appSearchData,
boolean globalSearch) {
-
- if (mSearchDialog == null) {
- mSearchDialog = new SearchDialog(mContext);
+ if (DBG) debug("startSearch(), mIsShowing=" + mIsShowing);
+ if (mIsShowing) return;
+ try {
+ mIsShowing = true;
+ // activate the search manager and start it up!
+ sService.startSearch(initialQuery, selectInitialQuery, launchActivity, appSearchData,
+ globalSearch, mSearchManagerCallback);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "startSearch() failed: " + ex);
}
-
- // activate the search manager and start it up!
- mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData,
- globalSearch);
-
- mSearchDialog.setOnCancelListener(this);
- mSearchDialog.setOnDismissListener(this);
}
/**
@@ -1575,9 +1581,16 @@ public class SearchManager
*
* @see #startSearch
*/
- public void stopSearch() {
- if (mSearchDialog != null) {
- mSearchDialog.cancel();
+ public void stopSearch() {
+ if (DBG) debug("stopSearch(), mIsShowing=" + mIsShowing);
+ if (!mIsShowing) return;
+ try {
+ sService.stopSearch();
+ // onDismiss will also clear this, but we do it here too since onDismiss() is
+ // called asynchronously.
+ mIsShowing = false;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "stopSearch() failed: " + ex);
}
}
@@ -1590,13 +1603,11 @@ public class SearchManager
*
* @hide
*/
- public boolean isVisible() {
- if (mSearchDialog != null) {
- return mSearchDialog.isShowing();
- }
- return false;
+ public boolean isVisible() {
+ if (DBG) debug("isVisible(), mIsShowing=" + mIsShowing);
+ return mIsShowing;
}
-
+
/**
* See {@link SearchManager#setOnDismissListener} for configuring your activity to monitor
* search UI state.
@@ -1631,79 +1642,112 @@ public class SearchManager
public void setOnDismissListener(final OnDismissListener listener) {
mDismissListener = listener;
}
-
- /**
- * The callback from the search dialog when dismissed
- * @hide
- */
- public void onDismiss(DialogInterface dialog) {
- if (dialog == mSearchDialog) {
- if (mDismissListener != null) {
- mDismissListener.onDismiss();
- }
- }
- }
/**
* Set or clear the callback that will be invoked whenever the search UI is canceled.
*
* @param listener The {@link OnCancelListener} to use, or null.
*/
- public void setOnCancelListener(final OnCancelListener listener) {
+ public void setOnCancelListener(OnCancelListener listener) {
mCancelListener = listener;
}
-
-
- /**
- * The callback from the search dialog when canceled
- * @hide
- */
- public void onCancel(DialogInterface dialog) {
- if (dialog == mSearchDialog) {
- if (mCancelListener != null) {
- mCancelListener.onCancel();
+
+ private class SearchManagerCallback extends ISearchManagerCallback.Stub {
+
+ private final Runnable mFireOnDismiss = new Runnable() {
+ public void run() {
+ if (DBG) debug("mFireOnDismiss");
+ mIsShowing = false;
+ if (mDismissListener != null) {
+ mDismissListener.onDismiss();
+ }
+ }
+ };
+
+ private final Runnable mFireOnCancel = new Runnable() {
+ public void run() {
+ if (DBG) debug("mFireOnCancel");
+ // doesn't need to clear mIsShowing since onDismiss() always gets called too
+ if (mCancelListener != null) {
+ mCancelListener.onCancel();
+ }
}
+ };
+
+ public void onDismiss() {
+ if (DBG) debug("onDismiss()");
+ mHandler.post(mFireOnDismiss);
+ }
+
+ public void onCancel() {
+ if (DBG) debug("onCancel()");
+ mHandler.post(mFireOnCancel);
}
+
+ }
+
+ // TODO: remove the DialogInterface interfaces from SearchManager.
+ // This changes the public API, so I'll do it in a separate change.
+ public void onCancel(DialogInterface dialog) {
+ throw new UnsupportedOperationException();
+ }
+ public void onDismiss(DialogInterface dialog) {
+ throw new UnsupportedOperationException();
}
/**
- * Save instance state so we can recreate after a rotation.
- *
+ * Saves the state of the search UI.
+ *
+ * @return A Bundle containing the state of the search dialog, or {@code null}
+ * if the search UI is not visible.
+ *
* @hide
*/
- void saveSearchDialog(Bundle outState, String key) {
- if (mSearchDialog != null && mSearchDialog.isShowing()) {
- Bundle searchDialogState = mSearchDialog.onSaveInstanceState();
- outState.putBundle(key, searchDialogState);
+ public Bundle saveSearchDialog() {
+ if (DBG) debug("saveSearchDialog(), mIsShowing=" + mIsShowing);
+ if (!mIsShowing) return null;
+ try {
+ return sService.onSaveInstanceState();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onSaveInstanceState() failed: " + ex);
+ return null;
}
}
/**
- * Restore instance state after a rotation.
- *
+ * Restores the state of the search dialog.
+ *
+ * @param searchDialogState Bundle to read the state from.
+ *
* @hide
*/
- void restoreSearchDialog(Bundle inState, String key) {
- Bundle searchDialogState = inState.getBundle(key);
- if (searchDialogState != null) {
- if (mSearchDialog == null) {
- mSearchDialog = new SearchDialog(mContext);
- }
- mSearchDialog.onRestoreInstanceState(searchDialogState);
+ public void restoreSearchDialog(Bundle searchDialogState) {
+ if (DBG) debug("restoreSearchDialog(" + searchDialogState + ")");
+ if (searchDialogState == null) return;
+ try {
+ sService.onRestoreInstanceState(searchDialogState);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onRestoreInstanceState() failed: " + ex);
}
}
-
+
/**
- * Hook for updating layout on a rotation
- *
+ * Update the search dialog after a configuration change.
+ *
+ * @param newConfig The new configuration.
+ *
* @hide
*/
- void onConfigurationChanged(Configuration newConfig) {
- if (mSearchDialog != null && mSearchDialog.isShowing()) {
- mSearchDialog.onConfigurationChanged(newConfig);
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (DBG) debug("onConfigurationChanged(" + newConfig + "), mIsShowing=" + mIsShowing);
+ if (!mIsShowing) return;
+ try {
+ sService.onConfigurationChanged(newConfig);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onConfigurationChanged() failed:" + ex);
}
}
-
+
private static ISearchManager getSearchManagerService() {
return ISearchManager.Stub.asInterface(
ServiceManager.getService(Context.SEARCH_SERVICE));
@@ -1724,7 +1768,8 @@ public class SearchManager
boolean globalSearch) {
try {
return sService.getSearchableInfo(componentName, globalSearch);
- } catch (RemoteException e) {
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getSearchableInfo() failed: " + ex);
return null;
}
}
@@ -1805,6 +1850,7 @@ public class SearchManager
try {
return sService.getSearchablesInGlobalSearch();
} catch (RemoteException e) {
+ Log.e(TAG, "getSearchablesInGlobalSearch() failed: " + e);
return null;
}
}
@@ -1812,7 +1858,8 @@ public class SearchManager
/**
* Returns a list of the searchable activities that handle web searches.
*
- * @return a a list of all searchable activities that handle {@link SearchManager#ACTION_WEB_SEARCH}.
+ * @return a list of all searchable activities that handle
+ * {@link android.content.Intent#ACTION_WEB_SEARCH}.
*
* @hide because SearchableInfo is not part of the API.
*/
@@ -1820,6 +1867,7 @@ public class SearchManager
try {
return sService.getSearchablesForWebSearch();
} catch (RemoteException e) {
+ Log.e(TAG, "getSearchablesForWebSearch() failed: " + e);
return null;
}
}
@@ -1835,6 +1883,7 @@ public class SearchManager
try {
return sService.getDefaultSearchableForWebSearch();
} catch (RemoteException e) {
+ Log.e(TAG, "getDefaultSearchableForWebSearch() failed: " + e);
return null;
}
}
@@ -1850,6 +1899,12 @@ public class SearchManager
try {
sService.setDefaultWebSearch(component);
} catch (RemoteException e) {
+ Log.e(TAG, "setDefaultWebSearch() failed: " + e);
}
}
+
+ private static void debug(String msg) {
+ Thread thread = Thread.currentThread();
+ Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")");
+ }
}
diff --git a/core/java/android/backup/BackupDataInput.java b/core/java/android/backup/BackupDataInput.java
new file mode 100644
index 0000000..609dd90
--- /dev/null
+++ b/core/java/android/backup/BackupDataInput.java
@@ -0,0 +1,103 @@
+/*
+ * 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.backup;
+
+import android.content.Context;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/** @hide */
+public class BackupDataInput {
+ int mBackupReader;
+
+ private EntityHeader mHeader = new EntityHeader();
+ private boolean mHeaderReady;
+
+ private static class EntityHeader {
+ String key;
+ int dataSize;
+ }
+
+ public BackupDataInput(FileDescriptor fd) {
+ if (fd == null) throw new NullPointerException();
+ mBackupReader = ctor(fd);
+ if (mBackupReader == 0) {
+ throw new RuntimeException("Native initialization failed with fd=" + fd);
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ dtor(mBackupReader);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ public boolean readNextHeader() throws IOException {
+ int result = readNextHeader_native(mBackupReader, mHeader);
+ if (result == 0) {
+ // read successfully
+ mHeaderReady = true;
+ return true;
+ } else if (result > 0) {
+ // done
+ mHeaderReady = false;
+ return false;
+ } else {
+ // error
+ mHeaderReady = false;
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ }
+
+ public String getKey() {
+ if (mHeaderReady) {
+ return mHeader.key;
+ } else {
+ throw new IllegalStateException("mHeaderReady=false");
+ }
+ }
+
+ public int getDataSize() {
+ if (mHeaderReady) {
+ return mHeader.dataSize;
+ } else {
+ throw new IllegalStateException("mHeaderReady=false");
+ }
+ }
+
+ public int readEntityData(byte[] data, int size) throws IOException {
+ if (mHeaderReady) {
+ int result = readEntityData_native(mBackupReader, data, size);
+ if (result >= 0) {
+ return result;
+ } else {
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ } else {
+ throw new IllegalStateException("mHeaderReady=false");
+ }
+ }
+
+ private native static int ctor(FileDescriptor fd);
+ private native static void dtor(int mBackupReader);
+
+ private native int readNextHeader_native(int mBackupReader, EntityHeader entity);
+ private native int readEntityData_native(int mBackupReader, byte[] data, int size);
+}
diff --git a/core/java/android/backup/BackupDataOutput.java b/core/java/android/backup/BackupDataOutput.java
index 25ae15b..1348d81 100644
--- a/core/java/android/backup/BackupDataOutput.java
+++ b/core/java/android/backup/BackupDataOutput.java
@@ -19,6 +19,7 @@ package android.backup;
import android.content.Context;
import java.io.FileDescriptor;
+import java.io.IOException;
/** @hide */
public class BackupDataOutput {
@@ -37,6 +38,24 @@ public class BackupDataOutput {
}
}
+ public int writeEntityHeader(String key, int dataSize) throws IOException {
+ int result = writeEntityHeader_native(mBackupWriter, key, dataSize);
+ if (result >= 0) {
+ return result;
+ } else {
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ }
+
+ public int writeEntityData(byte[] data, int size) throws IOException {
+ int result = writeEntityData_native(mBackupWriter, data, size);
+ if (result >= 0) {
+ return result;
+ } else {
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ }
+
protected void finalize() throws Throwable {
try {
dtor(mBackupWriter);
@@ -47,5 +66,8 @@ public class BackupDataOutput {
private native static int ctor(FileDescriptor fd);
private native static void dtor(int mBackupWriter);
+
+ private native static int writeEntityHeader_native(int mBackupWriter, String key, int dataSize);
+ private native static int writeEntityData_native(int mBackupWriter, byte[] data, int size);
}
diff --git a/core/java/android/backup/FileBackupHelper.java b/core/java/android/backup/FileBackupHelper.java
index 99051bf..ed840bb 100644
--- a/core/java/android/backup/FileBackupHelper.java
+++ b/core/java/android/backup/FileBackupHelper.java
@@ -27,21 +27,56 @@ import java.io.FileDescriptor;
public class FileBackupHelper {
private static final String TAG = "FileBackupHelper";
+ Context mContext;
+ String mKeyPrefix;
+
+ public FileBackupHelper(Context context) {
+ mContext = context;
+ }
+
+ public FileBackupHelper(Context context, String keyPrefix) {
+ mContext = context;
+ mKeyPrefix = keyPrefix;
+ }
+
/**
* Based on oldState, determine which of the files from the application's data directory
* need to be backed up, write them to the data stream, and fill in newState with the
* state as it exists now.
*/
- public static void performBackup(Context context,
- ParcelFileDescriptor oldState, BackupDataOutput data,
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState, String[] files) {
- File base = context.getFilesDir();
+ // file names
+ File base = mContext.getFilesDir();
final int N = files.length;
String[] fullPaths = new String[N];
for (int i=0; i<N; i++) {
fullPaths[i] = (new File(base, files[i])).getAbsolutePath();
}
- performBackup_checked(oldState, data, newState, fullPaths, files);
+
+ // keys
+ String[] keys = makeKeys(mKeyPrefix, files);
+
+ // go
+ performBackup_checked(oldState, data, newState, fullPaths, keys);
+ }
+
+ /**
+ * If keyPrefix is not null, prepend it to each of the strings in <code>original</code>;
+ * otherwise, return original.
+ */
+ static String[] makeKeys(String keyPrefix, String[] original) {
+ if (keyPrefix != null) {
+ String[] keys;
+ final int N = original.length;
+ keys = new String[N];
+ for (int i=0; i<N; i++) {
+ keys[i] = keyPrefix + ':' + original[i];
+ }
+ return keys;
+ } else {
+ return original;
+ }
}
/**
diff --git a/core/java/android/backup/RestoreHelper.java b/core/java/android/backup/RestoreHelper.java
new file mode 100644
index 0000000..ebd9906
--- /dev/null
+++ b/core/java/android/backup/RestoreHelper.java
@@ -0,0 +1,23 @@
+/*
+ * 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.backup;
+
+/** @hide */
+public interface RestoreHelper {
+ public void performRestore();
+}
+
diff --git a/core/java/android/backup/RestoreHelperDistributor.java b/core/java/android/backup/RestoreHelperDistributor.java
new file mode 100644
index 0000000..555ca79
--- /dev/null
+++ b/core/java/android/backup/RestoreHelperDistributor.java
@@ -0,0 +1,28 @@
+/*
+ * 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.backup;
+
+import java.util.HashMap;
+
+/** @hide */
+public class RestoreHelperDistributor {
+ HashMap<String,RestoreHelper> mHelpers;
+
+ public void addHelper(String keyPrefix, RestoreHelper helper) {
+ mHelpers.put(keyPrefix, helper);
+ }
+}
diff --git a/core/java/android/backup/SharedPreferencesBackupHelper.java b/core/java/android/backup/SharedPreferencesBackupHelper.java
index 923dc1b..cad79df 100644
--- a/core/java/android/backup/SharedPreferencesBackupHelper.java
+++ b/core/java/android/backup/SharedPreferencesBackupHelper.java
@@ -23,16 +23,33 @@ import java.io.FileDescriptor;
/** @hide */
public class SharedPreferencesBackupHelper {
- public static void performBackup(Context context,
- ParcelFileDescriptor oldSnapshot, ParcelFileDescriptor newSnapshot,
+ private Context mContext;
+ private String mKeyPrefix;
+
+ public SharedPreferencesBackupHelper(Context context) {
+ mContext = context;
+ }
+
+ public SharedPreferencesBackupHelper(Context context, String keyPrefix) {
+ mContext = context;
+ mKeyPrefix = keyPrefix;
+ }
+
+ public void performBackup(ParcelFileDescriptor oldSnapshot, ParcelFileDescriptor newSnapshot,
BackupDataOutput data, String[] prefGroups) {
+ Context context = mContext;
+
// make filenames for the prefGroups
final int N = prefGroups.length;
String[] files = new String[N];
for (int i=0; i<N; i++) {
- files[i] = context.getSharedPrefsFile(prefGroups[i]).toString();
+ files[i] = context.getSharedPrefsFile(prefGroups[i]).getAbsolutePath();
}
+ // make keys if necessary
+ String[] keys = FileBackupHelper.makeKeys(mKeyPrefix, prefGroups);
+
+ // go
FileBackupHelper.performBackup_checked(oldSnapshot, data, newSnapshot, files, prefGroups);
}
}
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index 060bcea..db812d1 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -17,15 +17,25 @@
package android.server.search;
import android.app.ISearchManager;
+import android.app.ISearchManagerCallback;
+import android.app.SearchDialog;
+import android.app.SearchManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
+import android.util.Log;
import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
/**
* This is a simplified version of the Search Manager service. It no longer handles
@@ -34,16 +44,20 @@ import java.util.List;
* invoked search) to specific searchable activities (where the search will be dispatched).
*/
public class SearchManagerService extends ISearchManager.Stub
+ implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener
{
// general debugging support
private static final String TAG = "SearchManagerService";
- private static final boolean DEBUG = false;
+ private static final boolean DBG = false;
// class maintenance and general shared data
private final Context mContext;
private final Handler mHandler;
private boolean mSearchablesDirty;
- private Searchables mSearchables;
+ private final Searchables mSearchables;
+
+ final SearchDialog mSearchDialog;
+ ISearchManagerCallback mCallback = null;
/**
* Initializes the Search Manager service in the provided system context.
@@ -56,6 +70,9 @@ public class SearchManagerService extends ISearchManager.Stub
mHandler = new Handler();
mSearchablesDirty = true;
mSearchables = new Searchables(context);
+ mSearchDialog = new SearchDialog(context);
+ mSearchDialog.setOnCancelListener(this);
+ mSearchDialog.setOnDismissListener(this);
// Setup the infrastructure for updating and maintaining the list
// of searchable activities.
@@ -107,6 +124,7 @@ public class SearchManagerService extends ISearchManager.Stub
* a package add/remove broadcast message.
*/
private void updateSearchables() {
+ if (DBG) debug("updateSearchables()");
mSearchables.buildSearchableList();
mSearchablesDirty = false;
}
@@ -137,6 +155,10 @@ public class SearchManagerService extends ISearchManager.Stub
if (globalSearch) {
si = mSearchables.getDefaultSearchable();
} else {
+ if (launchActivity == null) {
+ Log.e(TAG, "getSearchableInfo(), activity == null");
+ return null;
+ }
si = mSearchables.getSearchableInfo(launchActivity);
}
@@ -150,6 +172,145 @@ public class SearchManagerService extends ISearchManager.Stub
updateSearchablesIfDirty();
return mSearchables.getSearchablesInGlobalSearchList();
}
+ /**
+ * Launches the search UI on the main thread of the service.
+ *
+ * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean)
+ */
+ public void startSearch(final String initialQuery,
+ final boolean selectInitialQuery,
+ final ComponentName launchActivity,
+ final Bundle appSearchData,
+ final boolean globalSearch,
+ final ISearchManagerCallback searchManagerCallback) {
+ if (DBG) debug("startSearch()");
+ Runnable task = new Runnable() {
+ public void run() {
+ performStartSearch(initialQuery,
+ selectInitialQuery,
+ launchActivity,
+ appSearchData,
+ globalSearch,
+ searchManagerCallback);
+ }
+ };
+ mHandler.post(task);
+ }
+
+ /**
+ * Actually launches the search. This must be called on the service UI thread.
+ */
+ /*package*/ void performStartSearch(String initialQuery,
+ boolean selectInitialQuery,
+ ComponentName launchActivity,
+ Bundle appSearchData,
+ boolean globalSearch,
+ ISearchManagerCallback searchManagerCallback) {
+ if (DBG) debug("performStartSearch()");
+ mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData,
+ globalSearch);
+ if (searchManagerCallback != null) {
+ mCallback = searchManagerCallback;
+ }
+ }
+
+ /**
+ * Cancels the search dialog. Can be called from any thread.
+ */
+ public void stopSearch() {
+ if (DBG) debug("stopSearch()");
+ mHandler.post(new Runnable() {
+ public void run() {
+ performStopSearch();
+ }
+ });
+ }
+
+ /**
+ * Cancels the search dialog. Must be called from the service UI thread.
+ */
+ /*package*/ void performStopSearch() {
+ if (DBG) debug("performStopSearch()");
+ mSearchDialog.cancel();
+ }
+
+ /**
+ * Determines if the Search UI is currently displayed.
+ *
+ * @see SearchManager#isVisible()
+ */
+ public boolean isVisible() {
+ return postAndWait(mIsShowing, false, "isShowing()");
+ }
+
+ private final Callable<Boolean> mIsShowing = new Callable<Boolean>() {
+ public Boolean call() {
+ return mSearchDialog.isShowing();
+ }
+ };
+
+ public Bundle onSaveInstanceState() {
+ return postAndWait(mOnSaveInstanceState, null, "onSaveInstanceState()");
+ }
+
+ private final Callable<Bundle> mOnSaveInstanceState = new Callable<Bundle>() {
+ public Bundle call() {
+ if (mSearchDialog.isShowing()) {
+ return mSearchDialog.onSaveInstanceState();
+ } else {
+ return null;
+ }
+ }
+ };
+
+ public void onRestoreInstanceState(final Bundle searchDialogState) {
+ if (searchDialogState != null) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ mSearchDialog.onRestoreInstanceState(searchDialogState);
+ }
+ });
+ }
+ }
+
+ public void onConfigurationChanged(final Configuration newConfig) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ if (mSearchDialog.isShowing()) {
+ mSearchDialog.onConfigurationChanged(newConfig);
+ }
+ }
+ });
+ }
+
+ /**
+ * Called by {@link SearchDialog} when it goes away.
+ */
+ public void onDismiss(DialogInterface dialog) {
+ if (DBG) debug("onDismiss()");
+ if (mCallback != null) {
+ try {
+ mCallback.onDismiss();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onDismiss() failed: " + ex);
+ }
+ }
+ }
+
+ /**
+ * Called by {@link SearchDialog} when the user or activity cancels search.
+ * When this is called, {@link #onDismiss} is called too.
+ */
+ public void onCancel(DialogInterface dialog) {
+ if (DBG) debug("onCancel()");
+ if (mCallback != null) {
+ try {
+ mCallback.onCancel();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onCancel() failed: " + ex);
+ }
+ }
+ }
/**
* Returns a list of the searchable activities that handle web searches.
@@ -173,4 +334,34 @@ public class SearchManagerService extends ISearchManager.Stub
public void setDefaultWebSearch(ComponentName component) {
mSearchables.setDefaultWebSearch(component);
}
+
+ /**
+ * Runs an operation on the handler for the service, blocks until it returns,
+ * and returns the value returned by the operation.
+ *
+ * @param <V> Return value type.
+ * @param callable Operation to run.
+ * @param errorResult Value to return if the operations throws an exception.
+ * @param name Operation name to include in error log messages.
+ * @return The value returned by the operation.
+ */
+ private <V> V postAndWait(Callable<V> callable, V errorResult, String name) {
+ FutureTask<V> task = new FutureTask<V>(callable);
+ mHandler.post(task);
+ try {
+ return task.get();
+ } catch (InterruptedException ex) {
+ Log.e(TAG, "Error calling " + name + ": " + ex);
+ return errorResult;
+ } catch (ExecutionException ex) {
+ Log.e(TAG, "Error calling " + name + ": " + ex);
+ return errorResult;
+ }
+ }
+
+ private static void debug(String msg) {
+ Thread thread = Thread.currentThread();
+ Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")");
+ }
+
}
diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java
index 4df7368..90dfa0b 100644
--- a/core/java/android/server/search/SearchableInfo.java
+++ b/core/java/android/server/search/SearchableInfo.java
@@ -320,7 +320,7 @@ public final class SearchableInfo implements Parcelable {
// for now, implement some form of rules - minimal data
if (mLabelId == 0) {
- throw new IllegalArgumentException("No label.");
+ throw new IllegalArgumentException("Search label must be a resource reference.");
}
}
@@ -441,13 +441,17 @@ public final class SearchableInfo implements Parcelable {
xml.close();
if (DBG) {
- Log.d(LOG_TAG, "Checked " + activityInfo.name
- + ",label=" + searchable.getLabelId()
- + ",icon=" + searchable.getIconId()
- + ",suggestAuthority=" + searchable.getSuggestAuthority()
- + ",target=" + searchable.getSearchActivity().getClassName()
- + ",global=" + searchable.shouldIncludeInGlobalSearch()
- + ",threshold=" + searchable.getSuggestThreshold());
+ if (searchable != null) {
+ Log.d(LOG_TAG, "Checked " + activityInfo.name
+ + ",label=" + searchable.getLabelId()
+ + ",icon=" + searchable.getIconId()
+ + ",suggestAuthority=" + searchable.getSuggestAuthority()
+ + ",target=" + searchable.getSearchActivity().getClassName()
+ + ",global=" + searchable.shouldIncludeInGlobalSearch()
+ + ",threshold=" + searchable.getSuggestThreshold());
+ } else {
+ Log.d(LOG_TAG, "Checked " + activityInfo.name + ", no searchable meta-data");
+ }
}
return searchable;
}
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index 1b30aa0..367b26c 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -59,9 +59,15 @@ public final class Formatter {
result = result / 1024;
}
if (result < 100) {
- return String.format("%.2f%s", result, context.getText(suffix).toString());
+ String value = String.format("%.2f", result);
+ return context.getResources().
+ getString(com.android.internal.R.string.fileSizeSuffix,
+ value, context.getString(suffix));
}
- return String.format("%.0f%s", result, context.getText(suffix).toString());
+ String value = String.format("%.0f", result);
+ return context.getResources().
+ getString(com.android.internal.R.string.fileSizeSuffix,
+ value, context.getString(suffix));
}
/**
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 62fba4a..83182f2 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -1,5 +1,6 @@
package com.android.internal.backup;
+import android.backup.BackupDataInput;
import android.backup.RestoreSet;
import android.content.Context;
import android.content.pm.PackageInfo;
@@ -24,7 +25,7 @@ import java.util.ArrayList;
public class LocalTransport extends IBackupTransport.Stub {
private static final String TAG = "LocalTransport";
- private static final String DATA_FILE_NAME = "data";
+ private static final boolean DEBUG = true;
private Context mContext;
private PackageManager mPackageManager;
@@ -37,6 +38,7 @@ public class LocalTransport extends IBackupTransport.Stub {
public LocalTransport(Context context) {
+ if (DEBUG) Log.v(TAG, "Transport constructed");
mContext = context;
mPackageManager = context.getPackageManager();
}
@@ -47,29 +49,63 @@ public class LocalTransport extends IBackupTransport.Stub {
}
public int startSession() throws RemoteException {
+ if (DEBUG) Log.v(TAG, "session started");
+ mDataDir.mkdirs();
return 0;
}
public int endSession() throws RemoteException {
+ if (DEBUG) Log.v(TAG, "session ended");
return 0;
}
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data)
throws RemoteException {
- File packageDir = new File(mDataDir, packageInfo.packageName);
- File imageFileName = new File(packageDir, DATA_FILE_NAME);
-
- //!!! TODO: process the (partial) update into the persistent restore set:
-
- // Parse out the existing image file into the key/value map
+ if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
+ int err = 0;
- // Parse out the backup data into the key/value updates
-
- // Apply the backup key/value updates to the image
+ File packageDir = new File(mDataDir, packageInfo.packageName);
+ packageDir.mkdirs();
- // Write out the image in the canonical format
+ // Each 'record' in the restore set is kept in its own file, named by
+ // the record key. Wind through the data file, extracting individual
+ // record operations and building a set of all the updates to apply
+ // in this update.
+ BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor());
+ try {
+ int bufSize = 512;
+ byte[] buf = new byte[bufSize];
+ while (changeSet.readNextHeader()) {
+ String key = changeSet.getKey();
+ int dataSize = changeSet.getDataSize();
+ if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize);
+ if (dataSize > bufSize) {
+ bufSize = dataSize;
+ buf = new byte[bufSize];
+ }
+ changeSet.readEntityData(buf, dataSize);
+ if (DEBUG) Log.v(TAG, " + data size " + dataSize);
+
+ File entityFile = new File(packageDir, key);
+ FileOutputStream entity = new FileOutputStream(entityFile);
+ try {
+ entity.write(buf, 0, dataSize);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to update key file "
+ + entityFile.getAbsolutePath());
+ err = -1;
+ } finally {
+ entity.close();
+ }
+ }
+ } catch (IOException e) {
+ // oops, something went wrong. abort the operation and return error.
+ Log.v(TAG, "Exception reading backup input:");
+ e.printStackTrace();
+ err = -1;
+ }
- return -1;
+ return err;
}
// Restore handling
@@ -83,6 +119,7 @@ public class LocalTransport extends IBackupTransport.Stub {
}
public PackageInfo[] getAppSet(int token) throws android.os.RemoteException {
+ if (DEBUG) Log.v(TAG, "getting app set " + token);
// the available packages are the extant subdirs of mDatadir
File[] packageDirs = mDataDir.listFiles(mDirFileFilter);
ArrayList<PackageInfo> packages = new ArrayList<PackageInfo>();
@@ -99,9 +136,11 @@ public class LocalTransport extends IBackupTransport.Stub {
}
}
- Log.v(TAG, "Built app set of " + packages.size() + " entries:");
- for (PackageInfo p : packages) {
- Log.v(TAG, " + " + p.packageName);
+ if (DEBUG) {
+ Log.v(TAG, "Built app set of " + packages.size() + " entries:");
+ for (PackageInfo p : packages) {
+ Log.v(TAG, " + " + p.packageName);
+ }
}
PackageInfo[] result = new PackageInfo[packages.size()];
@@ -110,16 +149,25 @@ public class LocalTransport extends IBackupTransport.Stub {
public int getRestoreData(int token, PackageInfo packageInfo, ParcelFileDescriptor output)
throws android.os.RemoteException {
+ if (DEBUG) Log.v(TAG, "getting restore data " + token + " : " + packageInfo.packageName);
// we only support one hardcoded restore set
if (token != 0) return -1;
// the data for a given package is at a known location
File packageDir = new File(mDataDir, packageInfo.packageName);
- File imageFile = new File(packageDir, DATA_FILE_NAME);
- // restore is relatively easy: we already maintain the full data set in
- // the canonical form understandable to the BackupAgent
- return copyFileToFD(imageFile, output);
+ // The restore set is the concatenation of the individual record blobs,
+ // each of which is a file in the package's directory
+ File[] blobs = packageDir.listFiles();
+ int err = 0;
+ if (blobs != null && blobs.length > 0) {
+ for (File f : blobs) {
+ err = copyFileToFD(f, output);
+ if (err != 0) break;
+ }
+ }
+
+ return err;
}
private int copyFileToFD(File source, ParcelFileDescriptor dest) {