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 
17 package android.content.pm;
18 
19 import android.os.Parcel;
20 import android.util.Log;
21 
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 
25 /**
26  * Helper classes to read from and write to Parcel with pooled strings.
27  *
28  * @hide
29  */
30 public class PackageParserCacheHelper {
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 
ReadHelper(Parcel p)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          */
startAndInstall()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
readString(Parcel p)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          */
WriteHelper(Parcel p)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
writeString(Parcel p, String s)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          */
finishAndUninstall()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