diff options
Diffstat (limited to 'core/java')
| -rw-r--r-- | core/java/android/app/Activity.java | 52 | ||||
| -rw-r--r-- | core/java/android/app/FullBackupAgent.java | 5 | ||||
| -rw-r--r-- | core/java/android/app/ISearchManager.aidl | 14 | ||||
| -rw-r--r-- | core/java/android/app/ISearchManagerCallback.aidl | 23 | ||||
| -rw-r--r-- | core/java/android/app/SearchDialog.java | 55 | ||||
| -rw-r--r-- | core/java/android/app/SearchManager.java | 199 | ||||
| -rw-r--r-- | core/java/android/backup/BackupDataInput.java | 103 | ||||
| -rw-r--r-- | core/java/android/backup/BackupDataOutput.java | 22 | ||||
| -rw-r--r-- | core/java/android/backup/FileBackupHelper.java | 43 | ||||
| -rw-r--r-- | core/java/android/backup/RestoreHelper.java | 23 | ||||
| -rw-r--r-- | core/java/android/backup/RestoreHelperDistributor.java | 28 | ||||
| -rw-r--r-- | core/java/android/backup/SharedPreferencesBackupHelper.java | 23 | ||||
| -rw-r--r-- | core/java/android/server/search/SearchManagerService.java | 195 | ||||
| -rw-r--r-- | core/java/android/server/search/SearchableInfo.java | 20 | ||||
| -rw-r--r-- | core/java/android/text/format/Formatter.java | 10 | ||||
| -rw-r--r-- | core/java/com/android/internal/backup/LocalTransport.java | 86 | 
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) { | 
