1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.content.pm;
18
19import android.os.Parcel;
20import android.util.Log;
21
22import java.util.ArrayList;
23import java.util.HashMap;
24
25/**
26 * Helper classes to read from and write to Parcel with pooled strings.
27 *
28 * @hide
29 */
30public class PackageParserCacheHelper {
31    private PackageParserCacheHelper() {
32    }
33
34    private static final String TAG = "PackageParserCacheHelper";
35    private static final boolean DEBUG = false;
36
37    /**
38     * Parcel read helper with a string pool.
39     */
40    public static class ReadHelper extends Parcel.ReadWriteHelper {
41        private final ArrayList<String> mStrings = new ArrayList<>();
42
43        private final Parcel mParcel;
44
45        public ReadHelper(Parcel p) {
46            mParcel = p;
47        }
48
49        /**
50         * Prepare to read from a parcel, and install itself as a read-write helper.
51         *
52         * (We don't do it in the constructor to avoid calling methods before the constructor
53         * finishes.)
54         */
55        public void startAndInstall() {
56            mStrings.clear();
57
58            final int poolPosition = mParcel.readInt();
59            final int startPosition = mParcel.dataPosition();
60
61            // The pool is at the end of the parcel.
62            mParcel.setDataPosition(poolPosition);
63            mParcel.readStringList(mStrings);
64
65            // Then move back.
66            mParcel.setDataPosition(startPosition);
67
68            if (DEBUG) {
69                Log.i(TAG, "Read " + mStrings.size() + " strings");
70                for (int i = 0; i < mStrings.size(); i++) {
71                    Log.i(TAG, "  " + i + ": \"" + mStrings.get(i) + "\"");
72                }
73            }
74
75            mParcel.setReadWriteHelper(this);
76        }
77
78        /**
79         * Read an string index from a parcel, and returns the corresponding string from the pool.
80         */
81        @Override
82        public String readString(Parcel p) {
83            return mStrings.get(p.readInt());
84        }
85    }
86
87    /**
88     * Parcel write helper with a string pool.
89     */
90    public static class WriteHelper extends Parcel.ReadWriteHelper {
91        private final ArrayList<String> mStrings = new ArrayList<>();
92
93        private final HashMap<String, Integer> mIndexes = new HashMap<>();
94
95        private final Parcel mParcel;
96        private final int mStartPos;
97
98        /**
99         * Constructor.  Prepare a parcel, and install it self as a read-write helper.
100         */
101        public WriteHelper(Parcel p) {
102            mParcel = p;
103            mStartPos = p.dataPosition();
104            mParcel.writeInt(0); // We come back later here and write the pool position.
105
106            mParcel.setReadWriteHelper(this);
107        }
108
109        /**
110         * Instead of writing a string directly to a parcel, this method adds it to the pool,
111         * and write the index in the pool to the parcel.
112         */
113        @Override
114        public void writeString(Parcel p, String s) {
115            final Integer cur = mIndexes.get(s);
116            if (cur != null) {
117                // String already in the pool. Just write the index.
118                p.writeInt(cur); // Already in the pool.
119                if (DEBUG) {
120                    Log.i(TAG, "Duplicate '" + s + "' at " + cur);
121                }
122            } else {
123                // Not in the pool. Add to the pool, and write the index.
124                final int index = mStrings.size();
125                mIndexes.put(s, index);
126                mStrings.add(s);
127
128                if (DEBUG) {
129                    Log.i(TAG, "New '" + s + "' at " + index);
130                }
131
132                p.writeInt(index);
133            }
134        }
135
136        /**
137         * Closes a parcel by appending the string pool at the end and updating the pool offset,
138         * which it assumes is at the first byte.  It also uninstalls itself as a read-write helper.
139         */
140        public void finishAndUninstall() {
141            // Uninstall first, so that writeStringList() uses the native writeString.
142            mParcel.setReadWriteHelper(null);
143
144            final int poolPosition = mParcel.dataPosition();
145            mParcel.writeStringList(mStrings);
146
147            mParcel.setDataPosition(mStartPos);
148            mParcel.writeInt(poolPosition);
149
150            // Move back to the end.
151            mParcel.setDataPosition(mParcel.dataSize());
152            if (DEBUG) {
153                Log.i(TAG, "Wrote " + mStrings.size() + " strings");
154            }
155        }
156    }
157}
158