Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit adfe8b86 authored by Christopher Tate's avatar Christopher Tate
Browse files

App widget backup/restore infrastructure

Backup/restore now supports app widgets.

An application involved with app widgets, either hosting or publishing,
now has associated data in its backup dataset related to the state of
widget instantiation on the ancestral device.  That data is processed
by the OS during restore so that the matching widget instances can be
"automatically" regenerated.

To take advantage of this facility, widget-using apps need to do two
things:  first, implement a backup agent and store whatever widget
state they need to properly deal with them post-restore (e.g. the
widget instance size & location, for a host); and second, implement
handlers for new AppWidgetManager broadcasts that describe how to
translate ancestral-dataset widget id numbers to the post-restore
world.  Note that a host or provider doesn't technically need to
store *any* data on its own via its agent; it just needs to opt in
to the backup/restore process by publishing an agent.  The OS will
then store a small amount of data on behalf of each widget-savvy
app within the backup dataset, and act on that data at restore time.

The broadcasts are AppWidgetManager.ACTION_APPWIDGET_RESTORED and
ACTION_APPWIDGET_HOST_RESTORED, and have three associated extras:

    EXTRA_APPWIDGET_OLD_IDS
    EXTRA_APPWIDGET_IDS
    EXTRA_HOST_ID [for the host-side broadcast]

The first two are same-sized arrays of integer widget IDs.  The
_OLD_IDS values are the widget IDs as known to the ancestral device.
The _IDS array holds the corresponding widget IDs in the new post-
restore environment.  The app should simply update the stored
widget IDs in its bookkeeping to the new values, and things are
off and running.  The HOST_ID extra, as one might expect, is the
app-defined host ID value of the particular host instance which
has just been restored.

The broadcasts are sent following the conclusion of the overall
restore pass.  This is because the restore might have occurred in a
tightly restricted lifecycle environment without content providers
or the package's custom Application class.  The _RESTORED broadcast,
however, is always delivered into a normal application environment,
so that the app can use its content provider etc as expected.

*All* widget instances that were processed over the course of the
system restore are indicated in the _RESTORED broadcast, even if
the backing provider or host is not yet installed.  The widget
participant is responsible for understanding that these are
promises that might be fulfilled later rather than necessarily
reflecting the immediate presentable widget state.  (Remember
that following a cloud restore, apps may be installed piecemeal
over a lengthy period of time.)  Telling the hosts up front
about all intended widget instances allows them to show placeholder
UI or similarly useful information rather than surprising the user
with piecemeal unexpected appearances.

The AppWidgetProvider helper class has been updated to add a new
callback, onRestored(...), invoked when the _RESTORED broadcast
is received.  The call to onRestored() is immediately followed by
an invocation of onUpdate() for the affected widgets because
they will need to have their RemoteViews regenerated under the
new ID values.

Bug 10622506
Bug 10707117

Change-Id: Ie0007cdf809600b880d91989c00c3c3b8a4f988b
parent aef4f6eb
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -4976,15 +4976,19 @@ package android.appwidget {
    field public static final java.lang.String ACTION_APPWIDGET_DELETED = "android.appwidget.action.APPWIDGET_DELETED";
    field public static final java.lang.String ACTION_APPWIDGET_DISABLED = "android.appwidget.action.APPWIDGET_DISABLED";
    field public static final java.lang.String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED";
    field public static final java.lang.String ACTION_APPWIDGET_HOST_RESTORED = "android.appwidget.action.APPWIDGET_HOST_RESTORED";
    field public static final java.lang.String ACTION_APPWIDGET_OPTIONS_CHANGED = "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS";
    field public static final java.lang.String ACTION_APPWIDGET_PICK = "android.appwidget.action.APPWIDGET_PICK";
    field public static final java.lang.String ACTION_APPWIDGET_RESTORED = "android.appwidget.action.APPWIDGET_RESTORED";
    field public static final java.lang.String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE";
    field public static final java.lang.String EXTRA_APPWIDGET_ID = "appWidgetId";
    field public static final java.lang.String EXTRA_APPWIDGET_IDS = "appWidgetIds";
    field public static final java.lang.String EXTRA_APPWIDGET_OLD_IDS = "appWidgetOldIds";
    field public static final java.lang.String EXTRA_APPWIDGET_OPTIONS = "appWidgetOptions";
    field public static final java.lang.String EXTRA_APPWIDGET_PROVIDER = "appWidgetProvider";
    field public static final java.lang.String EXTRA_CUSTOM_EXTRAS = "customExtras";
    field public static final java.lang.String EXTRA_CUSTOM_INFO = "customInfo";
    field public static final java.lang.String EXTRA_HOST_ID = "hostId";
    field public static final int INVALID_APPWIDGET_ID = 0; // 0x0
    field public static final java.lang.String META_DATA_APPWIDGET_PROVIDER = "android.appwidget.provider";
    field public static final java.lang.String OPTION_APPWIDGET_HOST_CATEGORY = "appWidgetCategory";
@@ -5001,6 +5005,7 @@ package android.appwidget {
    method public void onDisabled(android.content.Context);
    method public void onEnabled(android.content.Context);
    method public void onReceive(android.content.Context, android.content.Intent);
    method public void onRestored(android.content.Context, int[], int[]);
    method public void onUpdate(android.content.Context, android.appwidget.AppWidgetManager, int[]);
  }
+7 −2
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ public final class Backup {
        boolean saveObbs = false;
        boolean saveShared = false;
        boolean doEverything = false;
        boolean doWidgets = false;
        boolean allIncludesSystem = true;

        String arg;
@@ -89,6 +90,10 @@ public final class Backup {
                    allIncludesSystem = true;
                } else if ("-nosystem".equals(arg)) {
                    allIncludesSystem = false;
                } else if ("-widgets".equals(arg)) {
                    doWidgets = true;
                } else if ("-nowidgets".equals(arg)) {
                    doWidgets = false;
                } else if ("-all".equals(arg)) {
                    doEverything = true;
                } else {
@@ -114,8 +119,8 @@ public final class Backup {
        try {
            fd = ParcelFileDescriptor.adoptFd(socketFd);
            String[] packArray = new String[packages.size()];
            mBackupManager.fullBackup(fd, saveApks, saveObbs, saveShared, doEverything,
                    allIncludesSystem, packages.toArray(packArray));
            mBackupManager.fullBackup(fd, saveApks, saveObbs, saveShared, doWidgets,
                    doEverything, allIncludesSystem, packages.toArray(packArray));
        } catch (RemoteException e) {
            Log.e(TAG, "Unable to invoke backup manager for backup");
        } finally {
+10 −2
Original line number Diff line number Diff line
@@ -76,8 +76,9 @@ oneway interface IBackupAgent {
     * @param callbackBinder Binder on which to indicate operation completion,
     *        passed here as a convenience to the agent.
     */
    void doRestore(in ParcelFileDescriptor data, int appVersionCode,
            in ParcelFileDescriptor newState, int token, IBackupManager callbackBinder);
    void doRestore(in ParcelFileDescriptor data,
            int appVersionCode, in ParcelFileDescriptor newState,
            int token, IBackupManager callbackBinder);

    /**
     * Perform a "full" backup to the given file descriptor.  The output file is presumed
@@ -112,8 +113,15 @@ oneway interface IBackupAgent {
     * @param path Relative path of the file within its semantic domain.
     * @param mode Access mode of the file system entity, e.g. 0660.
     * @param mtime Last modification time of the file system entity.
     * @param token Opaque token identifying this transaction.  This must
     *        be echoed back to the backup service binder once the agent is
     *        finished restoring the application based on the restore data
     *        contents.
     * @param callbackBinder Binder on which to indicate operation completion,
     *        passed here as a convenience to the agent.
     */
    void doRestoreFile(in ParcelFileDescriptor data, long size,
            int type, String domain, String path, long mode, long mtime,
            int token, IBackupManager callbackBinder);

}
+8 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.app.backup;

import android.os.ParcelFileDescriptor;
import android.os.Process;

import java.io.FileDescriptor;
import java.io.IOException;
@@ -76,13 +77,19 @@ public class BackupDataOutput {
    /**
     * Mark the beginning of one record in the backup data stream. This must be called before
     * {@link #writeEntityData}.
     * @param key A string key that uniquely identifies the data record within the application
     * @param key A string key that uniquely identifies the data record within the application.
     *    Keys whose first character is \uFF00 or higher are not valid.
     * @param dataSize The size in bytes of this record's data.  Passing a dataSize
     *    of -1 indicates that the record under this key should be deleted.
     * @return The number of bytes written to the backup stream
     * @throws IOException if the write failed
     */
    public int writeEntityHeader(String key, int dataSize) throws IOException {
        if (key != null && key.charAt(0) >= 0xff00) {
            if (Process.myUid() != Process.SYSTEM_UID) {
                throw new IllegalArgumentException("Invalid key " + key);
            }
        }
        int result = writeEntityHeader_native(mBackupWriter, key, dataSize);
        if (result >= 0) {
            return result;
+1 −1
Original line number Diff line number Diff line
@@ -167,7 +167,7 @@ interface IBackupManager {
     *     are to be backed up.  The <code>allApps</code> parameter supersedes this.
     */
    void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
            boolean includeShared, boolean allApps, boolean allIncludesSystem,
            boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem,
            in String[] packageNames);

    /**
Loading