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 076f657b7f97..f1391aae870f 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 e11f170bf0de..c1b202c07454 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 c2cd3be3f727..cd8d515b5973 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 5b59599f10a9..dca9921f6cf3 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 ac30319dd1cd..88bebcb51108 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 dbe2ca17a750..b948c41da741 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 33e59a7e853a..534fd906ec3c 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 252c0bb12281..e9565d64f9bd 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 000000000000..ba3f2fe671a4 --- /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 6d66b295f956..ce83ae6fb0e7 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);          }  |