9

API21 released the PersistableBundle which is a bundle that the system retains for various purposes (JobScheduler jobs, ShortcutInfos etc). I'd like an easy way to convert the Bundle's that are present in my old code to PersistableBundle's...how can I do it?

Reuben Tanner
  • 5,229
  • 3
  • 31
  • 46
  • 1
    I imagine you'll get a limited space for what you're saving. Anyway, instead of carpet bombing go for a precision strike i.e. save what you need to recover after a reboot and nothing else. /// `JobInfo.Builder.setExtras` accepts a `PersistableBundle` only. So if you create one directly you don't even touch regular `Bundle` and there's nothing to convert. – Eugen Pechanec Jul 28 '17 at 14:39
  • This is not an answer to the question. – Reuben Tanner Jul 31 '17 at 16:43
  • 1
    That's why it's a comment. I'm just making sure you're not solving an artificial problem (a problem that shouldn't occur in the first place). – Eugen Pechanec Jul 31 '17 at 21:13
  • 1
    As an example for a use case where the conversion is necessary, what if I wish to call a jobservice one a user clicks a notification? The notification would have a pending intent, where the extras are a `Bundle`, but it needs to pass the data again to the service, which uses a `PersistableBundle` – Allan W Dec 29 '17 at 19:13

3 Answers3

7

Here's a utility that actually converts a Bundle to a PersistableBundle and back:

/**
 * Creates a new {@link Bundle} based on the specified {@link PersistableBundle}.
 */
public static Bundle toBundle(PersistableBundle persistableBundle) {
    if (persistableBundle == null) {
        return null;
    }
    Bundle bundle = new Bundle();
    bundle.putAll(persistableBundle);
    return bundle;
}

/**
 * Creates a new {@link PersistableBundle} from the specified {@link Bundle}.
 * Will ignore all values that are not persistable, according
 * to {@link #isPersistableBundleType(Object)}.
 */
public static PersistableBundle toPersistableBundle(Bundle bundle) {
    if (bundle == null) {
        return null;
    }
    PersistableBundle persistableBundle = new PersistableBundle();
    for (String key : bundle.keySet()) {
        Object value = bundle.get(key);
        if (isPersistableBundleType(value)) {
            putIntoBundle(persistableBundle, key, value);
        }
    }
    return persistableBundle;
}

/**
 * Checks if the specified object can be put into a {@link PersistableBundle}.
 *
 * @see <a href="https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/os/PersistableBundle.java#49">PersistableBundle Implementation</a>
 */
public static boolean isPersistableBundleType(Object value) {
    return ((value instanceof PersistableBundle) ||
            (value instanceof Integer) || (value instanceof int[]) ||
            (value instanceof Long) || (value instanceof long[]) ||
            (value instanceof Double) || (value instanceof double[]) ||
            (value instanceof String) || (value instanceof String[]) ||
            (value instanceof Boolean) || (value instanceof boolean[])
    );
}

/**
 * Attempts to insert the specified key value pair into the specified bundle.
 *
 * @throws IllegalArgumentException if the value type can not be put into the bundle.
 */
public static void putIntoBundle(BaseBundle baseBundle, String key, Object value) throws IllegalArgumentException {
    if (value == null) {
        throw new IllegalArgumentException("Unable to determine type of null values");
    } else if (value instanceof Integer) {
        baseBundle.putInt(key, (int) value);
    } else if (value instanceof int[]) {
        baseBundle.putIntArray(key, (int[]) value);
    } else if (value instanceof Long) {
        baseBundle.putLong(key, (long) value);
    } else if (value instanceof long[]) {
        baseBundle.putLongArray(key, (long[]) value);
    } else if (value instanceof Double) {
        baseBundle.putDouble(key, (double) value);
    } else if (value instanceof double[]) {
        baseBundle.putDoubleArray(key, (double[]) value);
    } else if (value instanceof String) {
        baseBundle.putString(key, (String) value);
    } else if (value instanceof String[]) {
        baseBundle.putStringArray(key, (String[]) value);
    } else if (value instanceof Boolean) {
        baseBundle.putBoolean(key, (boolean) value);
    } else if (value instanceof boolean[]) {
        baseBundle.putBooleanArray(key, (boolean[]) value);
    } else if (value instanceof PersistableBundle) {
        if (baseBundle instanceof PersistableBundle)
            ((PersistableBundle) baseBundle).putPersistableBundle(key, (PersistableBundle)value);
        else if (baseBundle instanceof Bundle)
            ((Bundle) baseBundle).putBundle(key, toBundle((PersistableBundle) value));
    } else {
        throw new IllegalArgumentException("Objects of type " + value.getClass().getSimpleName()
                + " can not be put into a " + BaseBundle.class.getSimpleName());
    }
}

You can find the complete class and unit tests in this gist.

Arnold Balliu
  • 1,099
  • 10
  • 21
Steppschuh
  • 329
  • 1
  • 5
  • 14
  • 1
    This does not handle nested bundles properly, instead it flattens them out. In particular, `putIntoBundle` should be using `putPersistableBundle(key, ...)` instead of `putAll`. – Jaen May 16 '18 at 00:24
  • Is there any way to add `Parcelable` Object into `Persistable bundle`? – Richard Jan 16 '20 at 11:38
4

As of API26, there is no way exposed to easily do this, you have to manually verify that the values are compatible:

private boolean bundleCanBePersisted(final Bundle extras) {
    if (extras == null) {
        return true;
    }

    Set<String> keys = extras.keySet();
    Iterator<String> it = keys.iterator();
    boolean allExtrasPersistable = true;
    while (it.hasNext()) {
        String key = it.next();
        boolean isPersistable = isPersistable(extras.get(key));

        if (!isPersistable) {
            LOGGER.warning("Non persistable value in bundle. " + bundleItemToString(key, extras));
        }

        allExtrasPersistable &= isPersistable;
    }
    return allExtrasPersistable;
}

/**
 * These are all the values that can be put into a PersistableBundle.
 */
private boolean isPersistable(final Object o) {
    return o == null
            || o instanceof PersistableBundle
            || o instanceof String
            || o instanceof String[]
            || o instanceof Boolean
            || o instanceof Boolean[]
            || o instanceof Double
            || o instanceof Double[]
            || o instanceof Integer
            || o instanceof Integer[]
            || o instanceof Long
            || o instanceof Long[];
}

private String bundleItemToString(final String key, final Bundle bundle) {
    Object value = bundle.get(key);
    Class<?> valueClazz = null;
    if (value != null) {
        valueClazz = value.getClass();
    }
    return String.format("[%s = %s (%s)]", key, value, valueClazz);
}
Reuben Tanner
  • 5,229
  • 3
  • 31
  • 46
-1

Converting form Bundle to PersistableBundle

Bundle b = new Bundle();
PersistableBundle pb = new PersistableBundle(b);

Converting from PersistableBundle to Bundle

PersistableBundle pb = new PersistableBundle();
Bundle b= new Bundle();
b.putAll(pb);