diff options
10 files changed, 1533 insertions, 85 deletions
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 076f657..f1391aa 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -45,6 +45,7 @@ import android.util.AttributeSet; import android.util.Log; import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.Serializable; @@ -604,6 +605,15 @@ import java.util.Set; * of all possible flags. */ public class Intent implements Parcelable, Cloneable { + private static final String ATTR_ACTION = "action"; + private static final String TAG_CATEGORIES = "categories"; + private static final String ATTR_CATEGORY = "category"; + private static final String TAG_EXTRA = "extra"; + private static final String ATTR_TYPE = "type"; + private static final String ATTR_COMPONENT = "component"; + private static final String ATTR_DATA = "data"; + private static final String ATTR_FLAGS = "flags"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent activity actions (see action variable). @@ -7347,7 +7357,7 @@ public class Intent implements Parcelable, Cloneable { } String nodeName = parser.getName(); - if (nodeName.equals("category")) { + if (nodeName.equals(TAG_CATEGORIES)) { sa = resources.obtainAttributes(attrs, com.android.internal.R.styleable.IntentCategory); String cat = sa.getString(com.android.internal.R.styleable.IntentCategory_name); @@ -7358,11 +7368,11 @@ public class Intent implements Parcelable, Cloneable { } XmlUtils.skipCurrentTag(parser); - } else if (nodeName.equals("extra")) { + } else if (nodeName.equals(TAG_EXTRA)) { if (intent.mExtras == null) { intent.mExtras = new Bundle(); } - resources.parseBundleExtra("extra", attrs, intent.mExtras); + resources.parseBundleExtra(TAG_EXTRA, attrs, intent.mExtras); XmlUtils.skipCurrentTag(parser); } else { @@ -7373,6 +7383,76 @@ public class Intent implements Parcelable, Cloneable { return intent; } + /** @hide */ + public void saveToXml(XmlSerializer out) throws IOException { + if (mAction != null) { + out.attribute(null, ATTR_ACTION, mAction); + } + if (mData != null) { + out.attribute(null, ATTR_DATA, mData.toString()); + } + if (mType != null) { + out.attribute(null, ATTR_TYPE, mType); + } + if (mComponent != null) { + out.attribute(null, ATTR_COMPONENT, mComponent.flattenToShortString()); + } + out.attribute(null, ATTR_FLAGS, Integer.toHexString(getFlags())); + + if (mCategories != null) { + out.startTag(null, TAG_CATEGORIES); + for (int categoryNdx = mCategories.size() - 1; categoryNdx >= 0; --categoryNdx) { + out.attribute(null, ATTR_CATEGORY, mCategories.valueAt(categoryNdx)); + } + } + } + + /** @hide */ + public static Intent restoreFromXml(XmlPullParser in) throws IOException, + XmlPullParserException { + Intent intent = new Intent(); + final int outerDepth = in.getDepth(); + + int attrCount = in.getAttributeCount(); + for (int attrNdx = attrCount - 1; attrNdx >= 0; --attrNdx) { + final String attrName = in.getAttributeName(attrNdx); + final String attrValue = in.getAttributeValue(attrNdx); + if (ATTR_ACTION.equals(attrName)) { + intent.setAction(attrValue); + } else if (ATTR_DATA.equals(attrName)) { + intent.setData(Uri.parse(attrValue)); + } else if (ATTR_TYPE.equals(attrName)) { + intent.setType(attrValue); + } else if (ATTR_COMPONENT.equals(attrName)) { + intent.setComponent(ComponentName.unflattenFromString(attrValue)); + } else if (ATTR_FLAGS.equals(attrName)) { + intent.setFlags(Integer.valueOf(attrValue, 16)); + } else { + Log.e("Intent", "restoreFromXml: unknown attribute=" + attrName); + } + } + + int event; + String name; + while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && + (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) { + if (event == XmlPullParser.START_TAG) { + name = in.getName(); + if (TAG_CATEGORIES.equals(name)) { + attrCount = in.getAttributeCount(); + for (int attrNdx = attrCount - 1; attrNdx >= 0; --attrNdx) { + intent.addCategory(in.getAttributeValue(attrNdx)); + } + } else { + Log.w("Intent", "restoreFromXml: unknown name=" + name); + XmlUtils.skipCurrentTag(in); + } + } + } + + return intent; + } + /** * Normalize a MIME data type. * diff --git a/core/java/android/os/CommonBundle.java b/core/java/android/os/CommonBundle.java index e11f170..c1b202c 100644 --- a/core/java/android/os/CommonBundle.java +++ b/core/java/android/os/CommonBundle.java @@ -18,11 +18,10 @@ package android.os; import android.util.ArrayMap; import android.util.Log; -import android.util.SparseArray; import java.io.Serializable; import java.util.ArrayList; -import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -304,6 +303,16 @@ abstract class CommonBundle implements Parcelable, Cloneable { } /** + * Inserts all mappings from the given Map into this CommonBundle. + * + * @param map a Map + */ + void putAll(Map map) { + unparcel(); + mMap.putAll(map); + } + + /** * Returns a Set containing the Strings used as keys in this Bundle. * * @return a Set of String keys diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java index c2cd3be..cd8d515 100644 --- a/core/java/android/os/PersistableBundle.java +++ b/core/java/android/os/PersistableBundle.java @@ -17,7 +17,14 @@ package android.os; import android.util.ArrayMap; - +import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; import java.util.Set; /** @@ -25,7 +32,8 @@ import java.util.Set; * restored. * */ -public final class PersistableBundle extends CommonBundle { +public final class PersistableBundle extends CommonBundle implements XmlUtils.WriteMapCallback { + private static final String TAG_PERSISTABLEMAP = "pbundle_as_map"; public static final PersistableBundle EMPTY; static final Parcel EMPTY_PARCEL; @@ -88,6 +96,38 @@ public final class PersistableBundle extends CommonBundle { } /** + * Constructs a PersistableBundle containing the mappings passed in. + * + * @param map a Map containing only those items that can be persisted. + * @throws IllegalArgumentException if any element of #map cannot be persisted. + */ + private PersistableBundle(Map<String, Object> map) { + super(); + + // First stuff everything in. + putAll(map); + + // Now verify each item throwing an exception if there is a violation. + Set<String> keys = map.keySet(); + Iterator<String> iterator = keys.iterator(); + while (iterator.hasNext()) { + String key = iterator.next(); + Object value = map.get(key); + if (value instanceof Map) { + // Fix up any Maps by replacing them with PersistableBundles. + putPersistableBundle(key, new PersistableBundle((Map<String, Object>) value)); + } else if (!(value instanceof Integer) && !(value instanceof Long) && + !(value instanceof Double) && !(value instanceof String) && + !(value instanceof int[]) && !(value instanceof long[]) && + !(value instanceof double[]) && !(value instanceof String[]) && + !(value instanceof PersistableBundle) && (value != null)) { + throw new IllegalArgumentException("Bad value in PersistableBundle key=" + key + + " value=" + value); + } + } + } + + /** * Make a PersistableBundle for a single key/value pair. * * @hide @@ -206,6 +246,7 @@ public final class PersistableBundle extends CommonBundle { * * @param bundle a PersistableBundle */ + @Override public void putAll(PersistableBundle bundle) { super.putAll(bundle); } @@ -323,6 +364,7 @@ public final class PersistableBundle extends CommonBundle { * @param key a String, or null * @param value a Bundle object, or null */ + @Override public void putPersistableBundle(String key, PersistableBundle value) { super.putPersistableBundle(key, value); } @@ -539,6 +581,57 @@ public final class PersistableBundle extends CommonBundle { super.readFromParcelInner(parcel); } + /** @hide */ + @Override + public void writeUnknownObject(Object v, String name, XmlSerializer out) + throws XmlPullParserException, IOException { + if (v instanceof PersistableBundle) { + out.startTag(null, TAG_PERSISTABLEMAP); + out.attribute(null, "name", name); + ((PersistableBundle) v).saveToXml(out); + out.endTag(null, TAG_PERSISTABLEMAP); + } else { + throw new XmlPullParserException("Unknown Object o=" + v); + } + } + + /** @hide */ + public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException { + unparcel(); + XmlUtils.writeMapXml(mMap, out, this); + } + + /** @hide */ + static class MyReadMapCallback implements XmlUtils.ReadMapCallback { + @Override + public Object readThisUnknownObjectXml(XmlPullParser in, String tag) + throws XmlPullParserException, IOException { + if (TAG_PERSISTABLEMAP.equals(tag)) { + return restoreFromXml(in); + } + throw new XmlPullParserException("Unknown tag=" + tag); + } + } + + /** + * @hide + */ + public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOException, + XmlPullParserException { + final int outerDepth = in.getDepth(); + final String startTag = in.getName(); + final String[] tagName = new String[1]; + int event; + while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && + (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) { + if (event == XmlPullParser.START_TAG) { + return new PersistableBundle((Map<String, Object>) + XmlUtils.readThisMapXml(in, startTag, tagName, new MyReadMapCallback())); + } + } + return EMPTY; + } + @Override synchronized public String toString() { if (mParcelledData != null) { diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java index 5b59599..dca9921 100644 --- a/core/java/com/android/internal/util/XmlUtils.java +++ b/core/java/com/android/internal/util/XmlUtils.java @@ -220,28 +220,74 @@ public class XmlUtils { * @see #readMapXml */ public static final void writeMapXml(Map val, String name, XmlSerializer out) - throws XmlPullParserException, java.io.IOException - { + throws XmlPullParserException, java.io.IOException { + writeMapXml(val, name, out, null); + } + + /** + * Flatten a Map into an XmlSerializer. The map can later be read back + * with readThisMapXml(). + * + * @param val The map to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the map into. + * @param callback Method to call when an Object type is not recognized. + * + * @see #writeMapXml(Map, OutputStream) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + * + * @hide + */ + public static final void writeMapXml(Map val, String name, XmlSerializer out, + WriteMapCallback callback) throws XmlPullParserException, java.io.IOException { + if (val == null) { out.startTag(null, "null"); out.endTag(null, "null"); return; } - Set s = val.entrySet(); - Iterator i = s.iterator(); - out.startTag(null, "map"); if (name != null) { out.attribute(null, "name", name); } + writeMapXml(val, out, callback); + + out.endTag(null, "map"); + } + + /** + * Flatten a Map into an XmlSerializer. The map can later be read back + * with readThisMapXml(). This method presumes that the start tag and + * name attribute have already been written and does not write an end tag. + * + * @param val The map to be flattened. + * @param out XmlSerializer to write the map into. + * + * @see #writeMapXml(Map, OutputStream) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + * + * @hide + */ + public static final void writeMapXml(Map val, XmlSerializer out, + WriteMapCallback callback) throws XmlPullParserException, java.io.IOException { + if (val == null) { + return; + } + + Set s = val.entrySet(); + Iterator i = s.iterator(); + while (i.hasNext()) { Map.Entry e = (Map.Entry)i.next(); - writeValueXml(e.getValue(), (String)e.getKey(), out); + writeValueXml(e.getValue(), (String)e.getKey(), out, callback); } - - out.endTag(null, "map"); } /** @@ -387,6 +433,123 @@ public class XmlUtils { } /** + * Flatten a long[] into an XmlSerializer. The list can later be read back + * with readThisLongArrayXml(). + * + * @param val The long array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + * @see #readThisIntArrayXml + */ + public static final void writeLongArrayXml(long[] val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "long-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + for (int i=0; i<N; i++) { + out.startTag(null, "item"); + out.attribute(null, "value", Long.toString(val[i])); + out.endTag(null, "item"); + } + + out.endTag(null, "long-array"); + } + + /** + * Flatten a double[] into an XmlSerializer. The list can later be read back + * with readThisDoubleArrayXml(). + * + * @param val The double array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + * @see #readThisIntArrayXml + */ + public static final void writeDoubleArrayXml(double[] val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "double-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + for (int i=0; i<N; i++) { + out.startTag(null, "item"); + out.attribute(null, "value", Double.toString(val[i])); + out.endTag(null, "item"); + } + + out.endTag(null, "double-array"); + } + + /** + * Flatten a String[] into an XmlSerializer. The list can later be read back + * with readThisStringArrayXml(). + * + * @param val The long array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + * @see #readThisIntArrayXml + */ + public static final void writeStringArrayXml(String[] val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "string-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + for (int i=0; i<N; i++) { + out.startTag(null, "item"); + out.attribute(null, "value", val[i]); + out.endTag(null, "item"); + } + + out.endTag(null, "string-array"); + } + + /** * Flatten an object's value into an XmlSerializer. The value can later * be read back with readThisValueXml(). * @@ -403,8 +566,29 @@ public class XmlUtils { * @see #readValueXml */ public static final void writeValueXml(Object v, String name, XmlSerializer out) - throws XmlPullParserException, java.io.IOException - { + throws XmlPullParserException, java.io.IOException { + writeValueXml(v, name, out, null); + } + + /** + * Flatten an object's value into an XmlSerializer. The value can later + * be read back with readThisValueXml(). + * + * Currently supported value types are: null, String, Integer, Long, + * Float, Double Boolean, Map, List. + * + * @param v The object to be flattened. + * @param name Name attribute to include with this value's tag, or null + * for none. + * @param out XmlSerializer to write the object into. + * @param callback Handler for Object types not recognized. + * + * @see #writeMapXml + * @see #writeListXml + * @see #readValueXml + */ + private static final void writeValueXml(Object v, String name, XmlSerializer out, + WriteMapCallback callback) throws XmlPullParserException, java.io.IOException { String typeStr; if (v == null) { out.startTag(null, "null"); @@ -437,14 +621,23 @@ public class XmlUtils { } else if (v instanceof int[]) { writeIntArrayXml((int[])v, name, out); return; + } else if (v instanceof long[]) { + writeLongArrayXml((long[])v, name, out); + return; + } else if (v instanceof double[]) { + writeDoubleArrayXml((double[])v, name, out); + return; + } else if (v instanceof String[]) { + writeStringArrayXml((String[])v, name, out); + return; } else if (v instanceof Map) { writeMapXml((Map)v, name, out); return; } else if (v instanceof List) { - writeListXml((List)v, name, out); + writeListXml((List) v, name, out); return; } else if (v instanceof Set) { - writeSetXml((Set)v, name, out); + writeSetXml((Set) v, name, out); return; } else if (v instanceof CharSequence) { // XXX This is to allow us to at least write something if @@ -457,6 +650,9 @@ public class XmlUtils { out.text(v.toString()); out.endTag(null, "string"); return; + } else if (callback != null) { + callback.writeUnknownObject(v, name, out); + return; } else { throw new RuntimeException("writeValueXml: unable to write value " + v); } @@ -550,14 +746,35 @@ public class XmlUtils { * @see #readMapXml */ public static final HashMap<String, ?> readThisMapXml(XmlPullParser parser, String endTag, - String[] name) throws XmlPullParserException, java.io.IOException + String[] name) throws XmlPullParserException, java.io.IOException { + return readThisMapXml(parser, endTag, name, null); + } + + /** + * Read a HashMap object from an XmlPullParser. The XML data could + * previously have been generated by writeMapXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the map. + * + * @param parser The XmlPullParser from which to read the map data. + * @param endTag Name of the tag that will end the map, usually "map". + * @param name An array of one string, used to return the name attribute + * of the map's tag. + * + * @return HashMap The newly generated map. + * + * @see #readMapXml + * @hide + */ + public static final HashMap<String, ?> readThisMapXml(XmlPullParser parser, String endTag, + String[] name, ReadMapCallback callback) + throws XmlPullParserException, java.io.IOException { HashMap<String, Object> map = new HashMap<String, Object>(); int eventType = parser.getEventType(); do { if (eventType == parser.START_TAG) { - Object val = readThisValueXml(parser, name); + Object val = readThisValueXml(parser, name, callback); map.put(name[0], val); } else if (eventType == parser.END_TAG) { if (parser.getName().equals(endTag)) { @@ -587,15 +804,34 @@ public class XmlUtils { * * @see #readListXml */ - public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, String[] name) - throws XmlPullParserException, java.io.IOException - { + public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, + String[] name) throws XmlPullParserException, java.io.IOException { + return readThisListXml(parser, endTag, name, null); + } + + /** + * Read an ArrayList object from an XmlPullParser. The XML data could + * previously have been generated by writeListXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return HashMap The newly generated list. + * + * @see #readListXml + */ + private static final ArrayList readThisListXml(XmlPullParser parser, String endTag, + String[] name, ReadMapCallback callback) + throws XmlPullParserException, java.io.IOException { ArrayList list = new ArrayList(); int eventType = parser.getEventType(); do { if (eventType == parser.START_TAG) { - Object val = readThisValueXml(parser, name); + Object val = readThisValueXml(parser, name, callback); list.add(val); //System.out.println("Adding to list: " + val); } else if (eventType == parser.END_TAG) { @@ -611,7 +847,29 @@ public class XmlUtils { throw new XmlPullParserException( "Document ended before " + endTag + " end tag"); } - + + /** + * Read a HashSet object from an XmlPullParser. The XML data could previously + * have been generated by writeSetXml(). The XmlPullParser must be positioned + * <em>after</em> the tag that begins the set. + * + * @param parser The XmlPullParser from which to read the set data. + * @param endTag Name of the tag that will end the set, usually "set". + * @param name An array of one string, used to return the name attribute + * of the set's tag. + * + * @return HashSet The newly generated set. + * + * @throws XmlPullParserException + * @throws java.io.IOException + * + * @see #readSetXml + */ + public static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + return readThisSetXml(parser, endTag, name, null); + } + /** * Read a HashSet object from an XmlPullParser. The XML data could previously * have been generated by writeSetXml(). The XmlPullParser must be positioned @@ -628,15 +886,16 @@ public class XmlUtils { * @throws java.io.IOException * * @see #readSetXml + * @hide */ - public static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name) - throws XmlPullParserException, java.io.IOException { + private static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name, + ReadMapCallback callback) throws XmlPullParserException, java.io.IOException { HashSet set = new HashSet(); int eventType = parser.getEventType(); do { if (eventType == parser.START_TAG) { - Object val = readThisValueXml(parser, name); + Object val = readThisValueXml(parser, name, callback); set.add(val); //System.out.println("Adding to set: " + val); } else if (eventType == parser.END_TAG) { @@ -681,6 +940,7 @@ public class XmlUtils { throw new XmlPullParserException( "Not a number in num attribute in byte-array"); } + parser.next(); int[] array = new int[num]; int i = 0; @@ -722,6 +982,187 @@ public class XmlUtils { } /** + * Read a long[] object from an XmlPullParser. The XML data could + * previously have been generated by writeLongArrayXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated long[]. + * + * @see #readListXml + */ + public static final long[] readThisLongArrayXml(XmlPullParser parser, + String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need num attribute in long-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in num attribute in long-array"); + } + parser.next(); + + long[] array = new long[num]; + int i = 0; + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = Long.parseLong(parser.getAttributeValue(null, "value")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException("Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException("Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException("Document ended before " + endTag + " end tag"); + } + + /** + * Read a double[] object from an XmlPullParser. The XML data could + * previously have been generated by writeDoubleArrayXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "double-array". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated double[]. + * + * @see #readListXml + */ + public static final double[] readThisDoubleArrayXml(XmlPullParser parser, String endTag, + String[] name) throws XmlPullParserException, java.io.IOException { + + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need num attribute in double-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in num attribute in double-array"); + } + parser.next(); + + double[] array = new double[num]; + int i = 0; + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = Double.parseDouble(parser.getAttributeValue(null, "value")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException("Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException("Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException("Document ended before " + endTag + " end tag"); + } + + /** + * Read a String[] object from an XmlPullParser. The XML data could + * previously have been generated by writeStringArrayXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "string-array". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated String[]. + * + * @see #readListXml + */ + public static final String[] readThisStringArrayXml(XmlPullParser parser, String endTag, + String[] name) throws XmlPullParserException, java.io.IOException { + + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need num attribute in string-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in num attribute in string-array"); + } + parser.next(); + + String[] array = new String[num]; + int i = 0; + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = parser.getAttributeValue(null, "value"); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException("Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException("Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException("Document ended before " + endTag + " end tag"); + } + + /** * Read a flattened object from an XmlPullParser. The XML data could * previously have been written with writeMapXml(), writeListXml(), or * writeValueXml(). The XmlPullParser must be positioned <em>at</em> the @@ -743,7 +1184,7 @@ public class XmlUtils { int eventType = parser.getEventType(); do { if (eventType == parser.START_TAG) { - return readThisValueXml(parser, name); + return readThisValueXml(parser, name, null); } else if (eventType == parser.END_TAG) { throw new XmlPullParserException( "Unexpected end tag at: " + parser.getName()); @@ -758,9 +1199,8 @@ public class XmlUtils { "Unexpected end of document"); } - private static final Object readThisValueXml(XmlPullParser parser, String[] name) - throws XmlPullParserException, java.io.IOException - { + private static final Object readThisValueXml(XmlPullParser parser, String[] name, + ReadMapCallback callback) throws XmlPullParserException, java.io.IOException { final String valueName = parser.getAttributeValue(null, "name"); final String tagName = parser.getName(); @@ -794,11 +1234,25 @@ public class XmlUtils { } else if ((res = readThisPrimitiveValueXml(parser, tagName)) != null) { // all work already done by readThisPrimitiveValueXml } else if (tagName.equals("int-array")) { - parser.next(); res = readThisIntArrayXml(parser, "int-array", name); name[0] = valueName; //System.out.println("Returning value for " + valueName + ": " + res); return res; + } else if (tagName.equals("long-array")) { + res = readThisLongArrayXml(parser, "long-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("double-array")) { + res = readThisDoubleArrayXml(parser, "double-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("string-array")) { + res = readThisStringArrayXml(parser, "string-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; } else if (tagName.equals("map")) { parser.next(); res = readThisMapXml(parser, "map", name); @@ -817,9 +1271,12 @@ public class XmlUtils { name[0] = valueName; //System.out.println("Returning value for " + valueName + ": " + res); return res; + } else if (callback != null) { + res = callback.readThisUnknownObjectXml(parser, tagName); + name[0] = valueName; + return res; } else { - throw new XmlPullParserException( - "Unknown tag: " + tagName); + throw new XmlPullParserException("Unknown tag: " + tagName); } // Skip through to end tag. @@ -967,4 +1424,39 @@ public class XmlUtils { throws IOException { out.attribute(null, name, Boolean.toString(value)); } + + /** @hide */ + public interface WriteMapCallback { + /** + * Called from writeMapXml when an Object type is not recognized. The implementer + * must write out the entire element including start and end tags. + * + * @param v The object to be written out + * @param name The mapping key for v. Must be written into the "name" attribute of the + * start tag. + * @param out The XML output stream. + * @throws XmlPullParserException on unrecognized Object type. + * @throws IOException on XmlSerializer serialization errors. + * @hide + */ + public void writeUnknownObject(Object v, String name, XmlSerializer out) + throws XmlPullParserException, IOException; + } + + /** @hide */ + public interface ReadMapCallback { + /** + * Called from readThisMapXml when a START_TAG is not recognized. The input stream + * is positioned within the start tag so that attributes can be read using in.getAttribute. + * + * @param in the XML input stream + * @param tag the START_TAG that was not recognized. + * @return the Object parsed from the stream which will be put into the map. + * @throws XmlPullParserException if the START_TAG is not recognized. + * @throws IOException on XmlPullParser serialization errors. + * @hide + */ + public Object readThisUnknownObjectXml(XmlPullParser in, String tag) + throws XmlPullParserException, IOException; + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ac30319..88bebcb 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -409,7 +409,7 @@ public final class ActivityManagerService extends ActivityManagerNative /** * List of intents that were used to start the most recent tasks. */ - final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>(); + ArrayList<TaskRecord> mRecentTasks; public class PendingAssistExtras extends Binder implements Runnable { public final ActivityRecord activity; @@ -822,6 +822,11 @@ public final class ActivityManagerService extends ActivityManagerNative final AppOpsService mAppOpsService; /** + * Save recent tasks information across reboots. + */ + final TaskPersister mTaskPersister; + + /** * Current configuration information. HistoryRecord objects are given * a reference to this object to indicate which configuration they are * currently running in, so this object must be kept immutable. @@ -2138,6 +2143,7 @@ public final class ActivityManagerService extends ActivityManagerNative mCompatModePackages = new CompatModePackages(this, systemDir, mHandler); mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler); mStackSupervisor = new ActivityStackSupervisor(this); + mTaskPersister = new TaskPersister(systemDir, mStackSupervisor); mProcessCpuThread = new Thread("CpuTracker") { @Override @@ -7081,12 +7087,12 @@ public final class ActivityManagerService extends ActivityManagerNative private ActivityManager.RecentTaskInfo createRecentTaskInfoFromTaskRecord(TaskRecord tr) { ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); - rti.id = tr.numActivities > 0 ? tr.taskId : -1; + rti.id = tr.mActivities.isEmpty() ? -1 : tr.taskId; rti.persistentId = tr.taskId; rti.baseIntent = new Intent(tr.getBaseIntent()); rti.origActivity = tr.origActivity; rti.description = tr.lastDescription; - rti.stackId = tr.stack.mStackId; + rti.stackId = tr.stack != null ? tr.stack.mStackId : -1; rti.userId = tr.userId; rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription); return rti; @@ -7320,6 +7326,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (tr != null) { tr.removeTaskActivitiesLocked(-1, false); cleanUpRemovedTaskLocked(tr, flags); + if (tr.isPersistable) { + notifyTaskPersisterLocked(tr, true); + } return true; } return false; @@ -7559,14 +7568,11 @@ public final class ActivityManagerService extends ActivityManagerNative try { synchronized (this) { TaskRecord tr = recentTaskForIdLocked(taskId); - if (tr != null) { - return tr.stack.isHomeStack(); - } + return tr != null && tr.stack != null && tr.stack.isHomeStack(); } } finally { Binder.restoreCallingIdentity(ident); } - return false; } @Override @@ -8635,6 +8641,10 @@ public final class ActivityManagerService extends ActivityManagerNative } } + void notifyTaskPersisterLocked(TaskRecord task, boolean flush) { + mTaskPersister.notify(task, flush); + } + @Override public boolean shutdown(int timeout) { if (checkCallingPermission(android.Manifest.permission.SHUTDOWN) @@ -8657,6 +8667,7 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { mProcessStats.shutdownLocked(); } + notifyTaskPersisterLocked(null, true); return timedout; } @@ -9562,7 +9573,13 @@ public final class ActivityManagerService extends ActivityManagerNative if (goingCallback != null) goingCallback.run(); return; } - + + mRecentTasks = mTaskPersister.restoreTasksLocked(); + if (!mRecentTasks.isEmpty()) { + mStackSupervisor.createStackForRestoredTaskHistory(mRecentTasks); + } + mTaskPersister.startPersisting(); + // Check to see if there are any update receivers to run. if (!mDidUpdate) { if (mWaitingUpdate) { @@ -17179,7 +17196,7 @@ public final class ActivityManagerService extends ActivityManagerNative /** * An implementation of IAppTask, that allows an app to manage its own tasks via - * {@link android.app.ActivityManager#AppTask}. We keep track of the callingUid to ensure that + * {@link android.app.ActivityManager.AppTask}. We keep track of the callingUid to ensure that * only the process that calls getAppTasks() can call the AppTask methods. */ class AppTaskImpl extends IAppTask.Stub { diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index dbe2ca1..b948c41 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -16,14 +16,15 @@ package com.android.server.am; +import android.app.ActivityManager.TaskDescription; import android.os.PersistableBundle; import android.os.Trace; import com.android.internal.app.ResolverActivity; +import com.android.internal.util.XmlUtils; import com.android.server.AttributeCache; import com.android.server.am.ActivityStack.ActivityState; import com.android.server.am.ActivityStackSupervisor.ActivityContainer; -import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ResultInfo; import android.content.ComponentName; @@ -48,7 +49,11 @@ import android.util.Slog; import android.util.TimeUtils; import android.view.IApplicationToken; import android.view.WindowManager; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; +import java.io.IOException; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -62,6 +67,19 @@ final class ActivityRecord { static final boolean DEBUG_SAVED_STATE = ActivityStackSupervisor.DEBUG_SAVED_STATE; final public static String RECENTS_PACKAGE_NAME = "com.android.systemui.recent"; + private static final String TAG_ACTIVITY = "activity"; + private static final String ATTR_ID = "id"; + private static final String TAG_INTENT = "intent"; + private static final String ATTR_USERID = "user_id"; + private static final String TAG_PERSISTABLEBUNDLE = "persistable_bundle"; + private static final String ATTR_LAUNCHEDFROMUID = "launched_from_uid"; + private static final String ATTR_LAUNCHEDFROMPACKAGE = "launched_from_package"; + private static final String ATTR_RESOLVEDTYPE = "resolved_type"; + private static final String ATTR_COMPONENTSPECIFIED = "component_specified"; + private static final String ATTR_TASKDESCRIPTIONLABEL = "task_description_label"; + private static final String ATTR_TASKDESCRIPTIONCOLOR = "task_description_color"; + private static final String ACTIVITY_ICON_SUFFIX = "_activity_icon_"; + final ActivityManagerService service; // owner final IApplicationToken.Stub appToken; // window manager token final ActivityInfo info; // all about me @@ -97,6 +115,7 @@ final class ActivityRecord { int windowFlags; // custom window flags for preview window. TaskRecord task; // the task this is in. ThumbnailHolder thumbHolder; // where our thumbnails should go. + long createTime = System.currentTimeMillis(); long displayStartTime; // when we started launching this activity long fullyDrawnStartTime; // when we started launching this activity long startTime; // last time this activity was started @@ -149,7 +168,7 @@ final class ActivityRecord { boolean mStartingWindowShown = false; ActivityContainer mInitialActivityContainer; - ActivityManager.TaskDescription taskDescription; // the recents information for this activity + TaskDescription taskDescription; // the recents information for this activity void dump(PrintWriter pw, String prefix) { final long now = SystemClock.uptimeMillis(); @@ -490,14 +509,6 @@ final class ActivityRecord { (newTask == null ? null : newTask.stack)); } } - if (inHistory && !finishing) { - if (task != null) { - task.numActivities--; - } - if (newTask != null) { - newTask.numActivities++; - } - } if (newThumbHolder == null) { newThumbHolder = newTask; } @@ -527,9 +538,6 @@ final class ActivityRecord { void putInHistory() { if (!inHistory) { inHistory = true; - if (task != null && !finishing) { - task.numActivities++; - } } } @@ -537,7 +545,6 @@ final class ActivityRecord { if (inHistory) { inHistory = false; if (task != null && !finishing) { - task.numActivities--; task = null; } clearOptionsLocked(); @@ -560,12 +567,13 @@ final class ActivityRecord { return mActivityType == APPLICATION_ACTIVITY_TYPE; } + boolean isPersistable() { + return (info.flags & ActivityInfo.FLAG_PERSISTABLE) != 0; + } + void makeFinishing() { if (!finishing) { finishing = true; - if (task != null && inHistory) { - task.numActivities--; - } if (stopped) { clearOptionsLocked(); } @@ -767,6 +775,9 @@ final class ActivityRecord { "Setting thumbnail of " + this + " holder " + thumbHolder + " to " + newThumbnail); thumbHolder.lastThumbnail = newThumbnail; + if (isPersistable()) { + mStackSupervisor.mService.notifyTaskPersisterLocked(task, false); + } } thumbHolder.lastDescription = description; } @@ -1042,7 +1053,132 @@ final class ActivityRecord { return null; } - private String activityTypeToString(int type) { + void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException { + out.attribute(null, ATTR_ID, String.valueOf(createTime)); + out.attribute(null, ATTR_LAUNCHEDFROMUID, String.valueOf(launchedFromUid)); + if (launchedFromPackage != null) { + out.attribute(null, ATTR_LAUNCHEDFROMPACKAGE, launchedFromPackage); + } + if (resolvedType != null) { + out.attribute(null, ATTR_RESOLVEDTYPE, resolvedType); + } + out.attribute(null, ATTR_COMPONENTSPECIFIED, String.valueOf(componentSpecified)); + out.attribute(null, ATTR_USERID, String.valueOf(userId)); + if (taskDescription != null) { + final String label = taskDescription.getLabel(); + if (label != null) { + out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, label); + } + final int colorPrimary = taskDescription.getPrimaryColor(); + if (colorPrimary != 0) { + out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR, Integer.toHexString(colorPrimary)); + } + final Bitmap icon = taskDescription.getIcon(); + if (icon != null) { + TaskPersister.saveImage(icon, String.valueOf(task.taskId) + ACTIVITY_ICON_SUFFIX + + createTime); + } + } + + out.startTag(null, TAG_INTENT); + intent.saveToXml(out); + out.endTag(null, TAG_INTENT); + + if (isPersistable() && persistentState != null) { + out.startTag(null, TAG_PERSISTABLEBUNDLE); + persistentState.saveToXml(out); + out.endTag(null, TAG_PERSISTABLEBUNDLE); + } + } + + static ActivityRecord restoreFromXml(XmlPullParser in, int taskId, + ActivityStackSupervisor stackSupervisor) throws IOException, XmlPullParserException { + Intent intent = null; + PersistableBundle persistentState = null; + int launchedFromUid = 0; + String launchedFromPackage = null; + String resolvedType = null; + boolean componentSpecified = false; + int userId = 0; + String activityLabel = null; + int activityColor = 0; + long createTime = -1; + final int outerDepth = in.getDepth(); + + for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) { + final String attrName = in.getAttributeName(attrNdx); + final String attrValue = in.getAttributeValue(attrNdx); + if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "ActivityRecord: attribute name=" + + attrName + " value=" + attrValue); + if (ATTR_ID.equals(attrName)) { + createTime = Long.valueOf(attrValue); + } else if (ATTR_LAUNCHEDFROMUID.equals(attrName)) { + launchedFromUid = Integer.valueOf(attrValue); + } else if (ATTR_LAUNCHEDFROMPACKAGE.equals(attrName)) { + launchedFromPackage = attrValue; + } else if (ATTR_RESOLVEDTYPE.equals(attrName)) { + resolvedType = attrValue; + } else if (ATTR_COMPONENTSPECIFIED.equals(attrName)) { + componentSpecified = Boolean.valueOf(attrValue); + } else if (ATTR_USERID.equals(attrName)) { + userId = Integer.valueOf(attrValue); + } else if (ATTR_TASKDESCRIPTIONLABEL.equals(attrName)) { + activityLabel = attrValue; + } else if (ATTR_TASKDESCRIPTIONCOLOR.equals(attrName)) { + activityColor = (int) Long.parseLong(attrValue, 16); + } else { + Log.d(TAG, "Unknown ActivityRecord attribute=" + attrName); + } + } + + int event; + while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && + (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) { + if (event == XmlPullParser.START_TAG) { + final String name = in.getName(); + if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, + "ActivityRecord: START_TAG name=" + name); + if (TAG_INTENT.equals(name)) { + intent = Intent.restoreFromXml(in); + if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, + "ActivityRecord: intent=" + intent); + } else if (TAG_PERSISTABLEBUNDLE.equals(name)) { + persistentState = PersistableBundle.restoreFromXml(in); + if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, + "ActivityRecord: persistentState=" + persistentState); + } else { + Slog.w(TAG, "restoreActivity: unexpected name=" + name); + XmlUtils.skipCurrentTag(in); + } + } + } + + if (intent == null) { + Slog.e(TAG, "restoreActivity error intent=" + intent); + return null; + } + + final ActivityManagerService service = stackSupervisor.mService; + final ActivityInfo aInfo = stackSupervisor.resolveActivity(intent, resolvedType, 0, null, + null, userId); + final ActivityRecord r = new ActivityRecord(service, /*caller*/null, launchedFromUid, + launchedFromPackage, intent, resolvedType, aInfo, service.getConfiguration(), + null, null, 0, componentSpecified, stackSupervisor, null, null); + + r.persistentState = persistentState; + + Bitmap icon = null; + if (createTime >= 0) { + icon = TaskPersister.restoreImage(String.valueOf(taskId) + ACTIVITY_ICON_SUFFIX + + createTime); + } + r.taskDescription = new TaskDescription(activityLabel, icon, activityColor); + r.createTime = createTime; + + return r; + } + + private static String activityTypeToString(int type) { switch (type) { case APPLICATION_ACTIVITY_TYPE: return "APPLICATION_ACTIVITY_TYPE"; case HOME_ACTIVITY_TYPE: return "HOME_ACTIVITY_TYPE"; diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 33e59a7..534fd90 100755 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -863,7 +863,10 @@ final class ActivityStack { final ActivityRecord r = isInStackLocked(token); if (r != null) { mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); - r.persistentState = persistentState; + if (persistentState != null) { + r.persistentState = persistentState; + mService.notifyTaskPersisterLocked(r.task, false); + } if (mPausingActivity == r) { if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSED: " + r + (timeout ? " (due to timeout)" : " (pause complete)")); @@ -885,7 +888,10 @@ final class ActivityStack { mHandler.removeMessages(STOP_TIMEOUT_MSG, r); return; } - r.persistentState = persistentState; + if (persistentState != null) { + r.persistentState = persistentState; + mService.notifyTaskPersisterLocked(r.task, false); + } if (DEBUG_SAVED_STATE) Slog.i(TAG, "Saving icicle of " + r + ": " + icicle); if (icicle != null) { // If icicle is null, this is happening due to a timeout, so we @@ -1821,6 +1827,7 @@ final class ActivityStack { ++stackNdx; } mTaskHistory.add(stackNdx, task); + updateTaskMovement(task, true); } final void startActivityLocked(ActivityRecord r, boolean newTask, @@ -3138,6 +3145,18 @@ final class ActivityStack { mWindowManager.prepareAppTransition(transit, false); } + void updateTaskMovement(TaskRecord task, boolean toFront) { + if (task.isPersistable) { + task.mLastTimeMoved = System.currentTimeMillis(); + // Sign is used to keep tasks sorted when persisted. Tasks sent to the bottom most + // recently will be most negative, tasks sent to the bottom before that will be less + // negative. Similarly for recent tasks moved to the top which will be most positive. + if (!toFront) { + task.mLastTimeMoved *= -1; + } + } + } + void moveHomeTaskToTop() { final int top = mTaskHistory.size() - 1; for (int taskNdx = top; taskNdx >= 0; --taskNdx) { @@ -3146,6 +3165,7 @@ final class ActivityStack { if (DEBUG_TASKS || DEBUG_STACK) Slog.d(TAG, "moveHomeTaskToTop: moving " + task); mTaskHistory.remove(taskNdx); mTaskHistory.add(top, task); + updateTaskMovement(task, true); mWindowManager.moveTaskToTop(task.taskId); return; } @@ -3247,10 +3267,10 @@ final class ActivityStack { mTaskHistory.remove(tr); mTaskHistory.add(0, tr); + updateTaskMovement(tr, false); // There is an assumption that moving a task to the back moves it behind the home activity. // We make sure here that some activity in the stack will launch home. - ActivityRecord lastActivity = null; int numTasks = mTaskHistory.size(); for (int taskNdx = numTasks - 1; taskNdx >= 1; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); @@ -3727,6 +3747,7 @@ final class ActivityStack { mTaskHistory.get(taskNdx + 1).mOnTopOfHome = true; } mTaskHistory.remove(task); + updateTaskMovement(task, true); if (task.mActivities.isEmpty()) { final boolean isVoiceSession = task.voiceSession != null; @@ -3758,7 +3779,8 @@ final class ActivityStack { TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, boolean toTop) { - TaskRecord task = new TaskRecord(taskId, info, intent, voiceSession, voiceInteractor); + TaskRecord task = new TaskRecord(mService, taskId, info, intent, voiceSession, + voiceInteractor); addTask(task, toTop, false); return task; } @@ -3773,6 +3795,7 @@ final class ActivityStack { insertTaskAtTop(task); } else { mTaskHistory.add(0, task); + updateTaskMovement(task, false); } if (!moving && task.voiceSession != null) { try { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 252c0bb..e9565d6 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -370,6 +370,12 @@ public final class ActivityStackSupervisor implements DisplayListener { return null; } + void setNextTaskId(int taskId) { + if (taskId > mCurTaskId) { + mCurTaskId = taskId; + } + } + int getNextTaskId() { do { mCurTaskId++; @@ -2250,6 +2256,26 @@ public final class ActivityStackSupervisor implements DisplayListener { return mLastStackId; } + void createStackForRestoredTaskHistory(ArrayList<TaskRecord> tasks) { + int stackId = createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY); + final ActivityStack stack = getStack(stackId); + for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { + final TaskRecord task = tasks.get(taskNdx); + stack.addTask(task, false, false); + final int taskId = task.taskId; + final ArrayList<ActivityRecord> activities = task.mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + mWindowManager.addAppToken(0, r.appToken, taskId, stackId, + r.info.screenOrientation, r.fullscreen, + (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, + r.userId, r.info.configChanges); + } + mWindowManager.addTask(taskId, stackId, false); + } + resumeHomeActivity(null); + } + void moveTaskToStack(int taskId, int stackId, boolean toTop) { final TaskRecord task = anyTaskForIdLocked(taskId); if (task == null) { diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java new file mode 100644 index 0000000..ba3f2fe --- /dev/null +++ b/services/core/java/com/android/server/am/TaskPersister.java @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2014 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 com.android.server.am; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Debug; +import android.os.SystemClock; +import android.util.ArraySet; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.Xml; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; + +public class TaskPersister { + static final String TAG = "TaskPersister"; + static final boolean DEBUG = false; + + /** When in slow mode don't write tasks out faster than this */ + private static final long INTER_TASK_DELAY_MS = 60000; + private static final long DEBUG_INTER_TASK_DELAY_MS = 5000; + + private static final String RECENTS_FILENAME = "_task"; + private static final String TASKS_DIRNAME = "recent_tasks"; + private static final String TASK_EXTENSION = ".xml"; + private static final String IMAGES_DIRNAME = "recent_images"; + private static final String IMAGE_EXTENSION = ".png"; + + private static final String TAG_TASK = "task"; + + private static File sImagesDir; + private static File sTasksDir; + + private final ActivityManagerService mService; + private final ActivityStackSupervisor mStackSupervisor; + + private boolean mRecentsChanged = false; + + private final LazyTaskWriterThread mLazyTaskWriterThread; + + TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) { + sTasksDir = new File(systemDir, TASKS_DIRNAME); + if (!sTasksDir.exists()) { + if (!sTasksDir.mkdir()) { + Slog.e(TAG, "Failure creating tasks directory " + sTasksDir); + } + } + + sImagesDir = new File(systemDir, IMAGES_DIRNAME); + if (!sImagesDir.exists()) { + if (!sImagesDir.mkdir()) { + Slog.e(TAG, "Failure creating images directory " + sImagesDir); + } + } + + mStackSupervisor = stackSupervisor; + mService = stackSupervisor.mService; + + mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread"); + } + + void startPersisting() { + mLazyTaskWriterThread.start(); + } + + public void notify(TaskRecord task, boolean flush) { + if (DEBUG) Slog.d(TAG, "notify: task=" + task + " flush=" + flush + + " Callers=" + Debug.getCallers(4)); + if (task != null) { + task.needsPersisting = true; + } + synchronized (this) { + mLazyTaskWriterThread.mSlow = !flush; + mRecentsChanged = true; + notifyAll(); + } + } + + private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException { + if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task); + final XmlSerializer xmlSerializer = new FastXmlSerializer(); + StringWriter stringWriter = new StringWriter(); + xmlSerializer.setOutput(stringWriter); + + if (DEBUG) xmlSerializer.setFeature( + "http://xmlpull.org/v1/doc/features.html#indent-output", true); + + // save task + xmlSerializer.startDocument(null, true); + + xmlSerializer.startTag(null, TAG_TASK); + task.saveToXml(xmlSerializer); + xmlSerializer.endTag(null, TAG_TASK); + + xmlSerializer.endDocument(); + xmlSerializer.flush(); + + return stringWriter; + } + + static void saveImage(Bitmap image, String filename) throws IOException { + if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename); + FileOutputStream imageFile = null; + try { + imageFile = new FileOutputStream(new File(sImagesDir, filename + IMAGE_EXTENSION)); + image.compress(Bitmap.CompressFormat.PNG, 100, imageFile); + } catch (Exception e) { + Slog.e(TAG, "saveImage: unable to save " + filename, e); + } finally { + if (imageFile != null) { + imageFile.close(); + } + } + } + + ArrayList<TaskRecord> restoreTasksLocked() { + final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>(); + ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>(); + + File[] recentFiles = sTasksDir.listFiles(); + if (recentFiles == null) { + Slog.e(TAG, "Unable to list files from " + sTasksDir); + return tasks; + } + + for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) { + File taskFile = recentFiles[taskNdx]; + if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName()); + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(taskFile)); + final XmlPullParser in = Xml.newPullParser(); + in.setInput(reader); + + int event; + while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && + event != XmlPullParser.END_TAG) { + final String name = in.getName(); + if (event == XmlPullParser.START_TAG) { + if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name); + if (TAG_TASK.equals(name)) { + final TaskRecord task = + TaskRecord.restoreFromXml(in, mStackSupervisor); + if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" + task); + if (task != null) { + tasks.add(task); + final int taskId = task.taskId; + recoveredTaskIds.add(taskId); + mStackSupervisor.setNextTaskId(taskId); + } + } else { + Slog.e(TAG, "restoreTasksLocked Unknown xml event=" + event + " name=" + + name); + } + } + XmlUtils.skipCurrentTag(in); + } + } catch (IOException e) { + Slog.e(TAG, "Unable to parse " + taskFile + ". Error " + e); + } catch (XmlPullParserException e) { + Slog.e(TAG, "Unable to parse " + taskFile + ". Error " + e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + } + } + } + } + + if (!DEBUG) { + removeObsoleteFiles(recoveredTaskIds); + } + + TaskRecord[] tasksArray = new TaskRecord[tasks.size()]; + tasks.toArray(tasksArray); + Arrays.sort(tasksArray, new Comparator<TaskRecord>() { + @Override + public int compare(TaskRecord lhs, TaskRecord rhs) { + final long diff = lhs.mLastTimeMoved - rhs.mLastTimeMoved; + if (diff < 0) { + return -1; + } else if (diff > 0) { + return +1; + } else { + return 0; + } + } + }); + + return new ArrayList<TaskRecord>(Arrays.asList(tasksArray)); + } + + private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) { + for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) { + File file = files[fileNdx]; + String filename = file.getName(); + final int taskIdEnd = filename.indexOf('_') + 1; + if (taskIdEnd > 0) { + final int taskId; + try { + taskId = Integer.valueOf(filename.substring(0, taskIdEnd)); + } catch (Exception e) { + if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Can't parse file=" + + file.getName()); + file.delete(); + continue; + } + if (!persistentTaskIds.contains(taskId)) { + if (DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" + file.getName()); + file.delete(); + } + } + } + } + + private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) { + removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles()); + removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles()); + } + + static Bitmap restoreImage(String filename) { + if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename); + return BitmapFactory.decodeFile(sImagesDir + File.separator + filename + IMAGE_EXTENSION); + } + + private class LazyTaskWriterThread extends Thread { + boolean mSlow = true; + + LazyTaskWriterThread(String name) { + super(name); + } + + @Override + public void run() { + ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>(); + while (true) { + // If mSlow, then delay between each call to saveToXml(). + synchronized (TaskPersister.this) { + long now = SystemClock.uptimeMillis(); + final long releaseTime = + now + (DEBUG ? DEBUG_INTER_TASK_DELAY_MS: INTER_TASK_DELAY_MS); + while (mSlow && now < releaseTime) { + try { + if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " + + (releaseTime - now)); + TaskPersister.this.wait(releaseTime - now); + } catch (InterruptedException e) { + } + now = SystemClock.uptimeMillis(); + } + } + + StringWriter stringWriter = null; + TaskRecord task = null; + synchronized(mService) { + final ArrayList<TaskRecord> tasks = mService.mRecentTasks; + persistentTaskIds.clear(); + int taskNdx; + for (taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { + task = tasks.get(taskNdx); + if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" + + task.isPersistable + " needsPersisting=" + task.needsPersisting); + if (task.isPersistable) { + persistentTaskIds.add(task.taskId); + + if (task.needsPersisting) { + try { + stringWriter = saveToXml(task); + break; + } catch (IOException e) { + } catch (XmlPullParserException e) { + } finally { + task.needsPersisting = false; + } + } + } + } + } + + if (stringWriter != null) { + // Write out xml file while not holding mService lock. + FileOutputStream file = null; + AtomicFile atomicFile = null; + try { + atomicFile = new AtomicFile(new File(sTasksDir, + String.valueOf(task.taskId) + RECENTS_FILENAME + TASK_EXTENSION)); + file = atomicFile.startWrite(); + file.write(stringWriter.toString().getBytes()); + file.write('\n'); + atomicFile.finishWrite(file); + } catch (IOException e) { + if (file != null) { + atomicFile.failWrite(file); + } + Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " + e); + } + } else { + // Made it through the entire list and didn't find anything new that needed + // persisting. + if (!DEBUG) { + removeObsoleteFiles(persistentTaskIds); + } + + // Wait here for someone to call setRecentsChanged(). + synchronized (TaskPersister.this) { + while (!mRecentsChanged) { + if (DEBUG) Slog.d(TAG, "LazyTaskWriter: Waiting."); + try { + TaskPersister.this.wait(); + } catch (InterruptedException e) { + } + } + mRecentsChanged = false; + if (DEBUG) Slog.d(TAG, "LazyTaskWriter: Awake"); + } + } + // Some recents file needs to be written. + } + } + } +} diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 6d66b29..ce83ae6 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -27,15 +27,39 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Bitmap; +import android.os.SystemClock; import android.os.UserHandle; import android.service.voice.IVoiceInteractionSession; import android.util.Slog; import com.android.internal.app.IVoiceInteractor; +import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; +import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; final class TaskRecord extends ThumbnailHolder { + private static final String TAG_TASK = "task"; + private static final String ATTR_TASKID = "task_id"; + private static final String TAG_INTENT = "intent"; + private static final String TAG_AFFINITYINTENT = "affinity_intent"; + private static final String ATTR_REALACTIVITY = "real_activity"; + private static final String ATTR_ORIGACTIVITY = "orig_activity"; + private static final String TAG_ACTIVITY = "activity"; + private static final String ATTR_AFFINITY = "affinity"; + private static final String ATTR_ROOTHASRESET = "root_has_reset"; + private static final String ATTR_ASKEDCOMPATMODE = "asked_compat_mode"; + private static final String ATTR_USERID = "user_id"; + private static final String ATTR_TASKTYPE = "task_type"; + private static final String ATTR_ONTOPOFHOME = "on_top_of_home"; + private static final String ATTR_LASTDESCRIPTION = "last_description"; + private static final String ATTR_LASTTIMEMOVED = "last_time_moved"; + + private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail"; + final int taskId; // Unique identifier for this task. final String affinity; // The affinity name for this task, or null. final IVoiceInteractionSession voiceSession; // Voice interaction session driving task @@ -62,25 +86,63 @@ final class TaskRecord extends ThumbnailHolder { new ActivityManager.TaskDescription(); /** List of all activities in the task arranged in history order */ - final ArrayList<ActivityRecord> mActivities = new ArrayList<ActivityRecord>(); + final ArrayList<ActivityRecord> mActivities; /** Current stack */ ActivityStack stack; /** Takes on same set of values as ActivityRecord.mActivityType */ - private int mTaskType; + int taskType; + + /** Takes on same value as first root activity */ + boolean isPersistable = false; + /** Only used for persistable tasks, otherwise 0. The last time this task was moved. Used for + * determining the order when restoring. Sign indicates whether last task movement was to front + * (positive) or back (negative). Absolute value indicates time. */ + long mLastTimeMoved = System.currentTimeMillis(); + + /** True if persistable, has changed, and has not yet been persisted */ + boolean needsPersisting = false; /** Launch the home activity when leaving this task. Will be false for tasks that are not on * Display.DEFAULT_DISPLAY. */ boolean mOnTopOfHome = false; - TaskRecord(int _taskId, ActivityInfo info, Intent _intent, + final ActivityManagerService mService; + + TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent, IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) { + mService = service; taskId = _taskId; affinity = info.taskAffinity; voiceSession = _voiceSession; voiceInteractor = _voiceInteractor; setIntent(_intent, info); + mActivities = new ArrayList<ActivityRecord>(); + } + + TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, Intent _affinityIntent, + String _affinity, ComponentName _realActivity, ComponentName _origActivity, + boolean _rootWasReset, boolean _askedCompatMode, int _taskType, boolean _onTopOfHome, + int _userId, String _lastDescription, ArrayList<ActivityRecord> activities, + long lastTimeMoved) { + mService = service; + taskId = _taskId; + intent = _intent; + affinityIntent = _affinityIntent; + affinity = _affinity; + voiceSession = null; + voiceInteractor = null; + realActivity = _realActivity; + origActivity = _origActivity; + rootWasReset = _rootWasReset; + askedCompatMode = _askedCompatMode; + taskType = _taskType; + mOnTopOfHome = _onTopOfHome; + userId = _userId; + lastDescription = _lastDescription; + mActivities = activities; + mLastTimeMoved = lastTimeMoved; } void touchActiveTime() { @@ -237,12 +299,16 @@ final class TaskRecord extends ThumbnailHolder { } // Only set this based on the first activity if (mActivities.isEmpty()) { - mTaskType = r.mActivityType; + taskType = r.mActivityType; + isPersistable = r.isPersistable(); } else { // Otherwise make all added activities match this one. - r.mActivityType = mTaskType; + r.mActivityType = taskType; } mActivities.add(index, r); + if (r.isPersistable()) { + mService.notifyTaskPersisterLocked(this, false); + } } /** @return true if this was the last activity in the task */ @@ -251,6 +317,9 @@ final class TaskRecord extends ThumbnailHolder { // Was previously in list. numFullscreen--; } + if (r.isPersistable()) { + mService.notifyTaskPersisterLocked(this, false); + } return mActivities.size() == 0; } @@ -270,7 +339,14 @@ final class TaskRecord extends ThumbnailHolder { if (r.finishing) { continue; } - if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear", false)) { + if (stack == null) { + // Task was restored from persistent storage. + r.takeFromHistory(); + mActivities.remove(activityNdx); + --activityNdx; + --numActivities; + } else if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear", + false)) { --activityNdx; --numActivities; } @@ -354,11 +430,13 @@ final class TaskRecord extends ThumbnailHolder { } public Bitmap getTaskTopThumbnailLocked() { - final ActivityRecord resumedActivity = stack.mResumedActivity; - if (resumedActivity != null && resumedActivity.task == this) { - // This task is the current resumed task, we just need to take - // a screenshot of it and return that. - return stack.screenshotActivities(resumedActivity); + if (stack != null) { + final ActivityRecord resumedActivity = stack.mResumedActivity; + if (resumedActivity != null && resumedActivity.task == this) { + // This task is the current resumed task, we just need to take + // a screenshot of it and return that. + return stack.screenshotActivities(resumedActivity); + } } // Return the information about the task, to figure out the top // thumbnail to return. @@ -399,11 +477,11 @@ final class TaskRecord extends ThumbnailHolder { } boolean isHomeTask() { - return mTaskType == ActivityRecord.HOME_ACTIVITY_TYPE; + return taskType == ActivityRecord.HOME_ACTIVITY_TYPE; } boolean isApplicationTask() { - return mTaskType == ActivityRecord.APPLICATION_ACTIVITY_TYPE; + return taskType == ActivityRecord.APPLICATION_ACTIVITY_TYPE; } public TaskAccessInfo getTaskAccessInfoLocked() { @@ -493,7 +571,7 @@ final class TaskRecord extends ThumbnailHolder { int activityNdx; final int numActivities = mActivities.size(); for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities; - ++activityNdx) { + ++activityNdx) { final ActivityRecord r = mActivities.get(activityNdx); if (r.intent != null && (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) @@ -528,12 +606,155 @@ final class TaskRecord extends ThumbnailHolder { } } + void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException { + Slog.i(TAG, "Saving task=" + this); + + out.attribute(null, ATTR_TASKID, String.valueOf(taskId)); + if (realActivity != null) { + out.attribute(null, ATTR_REALACTIVITY, realActivity.flattenToShortString()); + } + if (origActivity != null) { + out.attribute(null, ATTR_ORIGACTIVITY, origActivity.flattenToShortString()); + } + if (affinity != null) { + out.attribute(null, ATTR_AFFINITY, affinity); + } + out.attribute(null, ATTR_ROOTHASRESET, String.valueOf(rootWasReset)); + out.attribute(null, ATTR_ASKEDCOMPATMODE, String.valueOf(askedCompatMode)); + out.attribute(null, ATTR_USERID, String.valueOf(userId)); + out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType)); + out.attribute(null, ATTR_ONTOPOFHOME, String.valueOf(mOnTopOfHome)); + out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved)); + if (lastDescription != null) { + out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString()); + } + + if (affinityIntent != null) { + out.startTag(null, TAG_AFFINITYINTENT); + affinityIntent.saveToXml(out); + out.endTag(null, TAG_AFFINITYINTENT); + } + + out.startTag(null, TAG_INTENT); + intent.saveToXml(out); + out.endTag(null, TAG_INTENT); + + final ArrayList<ActivityRecord> activities = mActivities; + final int numActivities = activities.size(); + for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if (!r.isPersistable() || (r.intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) == + Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) { + break; + } + out.startTag(null, TAG_ACTIVITY); + r.saveToXml(out); + out.endTag(null, TAG_ACTIVITY); + } + + final Bitmap thumbnail = getTaskTopThumbnailLocked(); + if (thumbnail != null) { + TaskPersister.saveImage(thumbnail, String.valueOf(taskId) + TASK_THUMBNAIL_SUFFIX); + } + } + + static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor) + throws IOException, XmlPullParserException { + Intent intent = null; + Intent affinityIntent = null; + ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>(); + ComponentName realActivity = null; + ComponentName origActivity = null; + String affinity = null; + boolean rootHasReset = false; + boolean askedCompatMode = false; + int taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE; + boolean onTopOfHome = true; + int userId = 0; + String lastDescription = null; + long lastTimeOnTop = 0; + int taskId = -1; + final int outerDepth = in.getDepth(); + + for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) { + final String attrName = in.getAttributeName(attrNdx); + final String attrValue = in.getAttributeValue(attrNdx); + if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: attribute name=" + + attrName + " value=" + attrValue); + if (ATTR_TASKID.equals(attrName)) { + taskId = Integer.valueOf(attrValue); + } else if (ATTR_REALACTIVITY.equals(attrName)) { + realActivity = ComponentName.unflattenFromString(attrValue); + } else if (ATTR_ORIGACTIVITY.equals(attrName)) { + origActivity = ComponentName.unflattenFromString(attrValue); + } else if (ATTR_AFFINITY.equals(attrName)) { + affinity = attrValue; + } else if (ATTR_ROOTHASRESET.equals(attrName)) { + rootHasReset = Boolean.valueOf(attrValue); + } else if (ATTR_ASKEDCOMPATMODE.equals(attrName)) { + askedCompatMode = Boolean.valueOf(attrValue); + } else if (ATTR_USERID.equals(attrName)) { + userId = Integer.valueOf(attrValue); + } else if (ATTR_TASKTYPE.equals(attrName)) { + taskType = Integer.valueOf(attrValue); + } else if (ATTR_ONTOPOFHOME.equals(attrName)) { + onTopOfHome = Boolean.valueOf(attrValue); + } else if (ATTR_LASTDESCRIPTION.equals(attrName)) { + lastDescription = attrValue; + } else if (ATTR_LASTTIMEMOVED.equals(attrName)) { + lastTimeOnTop = Long.valueOf(attrValue); + } else { + Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName); + } + } + + int event; + while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && + (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) { + if (event == XmlPullParser.START_TAG) { + final String name = in.getName(); + if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: START_TAG name=" + + name); + if (TAG_AFFINITYINTENT.equals(name)) { + affinityIntent = Intent.restoreFromXml(in); + } else if (TAG_INTENT.equals(name)) { + intent = Intent.restoreFromXml(in); + } else if (TAG_ACTIVITY.equals(name)) { + ActivityRecord activity = + ActivityRecord.restoreFromXml(in, taskId, stackSupervisor); + if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: activity=" + + activity); + if (activity != null) { + activities.add(activity); + } + } else { + Slog.e(TAG, "restoreTask: Unexpected name=" + name); + XmlUtils.skipCurrentTag(in); + } + } + } + + final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent, + affinityIntent, affinity, realActivity, origActivity, rootHasReset, + askedCompatMode, taskType, onTopOfHome, userId, lastDescription, activities, + lastTimeOnTop); + + for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + r.thumbHolder = r.task = task; + } + + task.lastThumbnail = TaskPersister.restoreImage(taskId + TASK_THUMBNAIL_SUFFIX); + + Slog.i(TAG, "Restored task=" + task); + return task; + } + void dump(PrintWriter pw, String prefix) { - if (numActivities != 0 || rootWasReset || userId != 0 || numFullscreen != 0) { - pw.print(prefix); pw.print("numActivities="); pw.print(numActivities); - pw.print(" rootWasReset="); pw.print(rootWasReset); + if (rootWasReset || userId != 0 || numFullscreen != 0) { + pw.print(prefix); pw.print(" rootWasReset="); pw.print(rootWasReset); pw.print(" userId="); pw.print(userId); - pw.print(" mTaskType="); pw.print(mTaskType); + pw.print(" taskType="); pw.print(taskType); pw.print(" numFullscreen="); pw.print(numFullscreen); pw.print(" mOnTopOfHome="); pw.println(mOnTopOfHome); } |