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.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.content.IntentFilter;
23 import android.net.Uri;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 
27 import java.security.MessageDigest;
28 import java.security.NoSuchAlgorithmException;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Locale;
32 
33 /**
34  * Information about an instant application.
35  * @hide
36  */
37 @SystemApi
38 public final class InstantAppResolveInfo implements Parcelable {
39     /** Algorithm that will be used to generate the domain digest */
40     public static final String SHA_ALGORITHM = "SHA-256";
41 
42     private final InstantAppDigest mDigest;
43     private final String mPackageName;
44     /** The filters used to match domain */
45     private final List<InstantAppIntentFilter> mFilters;
46     /** The version code of the app that this class resolves to */
47     private final int mVersionCode;
48 
InstantAppResolveInfo(@onNull InstantAppDigest digest, @Nullable String packageName, @Nullable List<InstantAppIntentFilter> filters, int versionCode)49     public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName,
50             @Nullable List<InstantAppIntentFilter> filters, int versionCode) {
51         // validate arguments
52         if ((packageName == null && (filters != null && filters.size() != 0))
53                 || (packageName != null && (filters == null || filters.size() == 0))) {
54             throw new IllegalArgumentException();
55         }
56         mDigest = digest;
57         if (filters != null) {
58             mFilters = new ArrayList<InstantAppIntentFilter>(filters.size());
59             mFilters.addAll(filters);
60         } else {
61             mFilters = null;
62         }
63         mPackageName = packageName;
64         mVersionCode = versionCode;
65     }
66 
InstantAppResolveInfo(@onNull String hostName, @Nullable String packageName, @Nullable List<InstantAppIntentFilter> filters)67     public InstantAppResolveInfo(@NonNull String hostName, @Nullable String packageName,
68             @Nullable List<InstantAppIntentFilter> filters) {
69         this(new InstantAppDigest(hostName), packageName, filters, -1 /*versionCode*/);
70     }
71 
InstantAppResolveInfo(Parcel in)72     InstantAppResolveInfo(Parcel in) {
73         mDigest = in.readParcelable(null /*loader*/);
74         mPackageName = in.readString();
75         mFilters = new ArrayList<InstantAppIntentFilter>();
76         in.readList(mFilters, null /*loader*/);
77         mVersionCode = in.readInt();
78     }
79 
getDigestBytes()80     public byte[] getDigestBytes() {
81         return mDigest.getDigestBytes()[0];
82     }
83 
getDigestPrefix()84     public int getDigestPrefix() {
85         return mDigest.getDigestPrefix()[0];
86     }
87 
getPackageName()88     public String getPackageName() {
89         return mPackageName;
90     }
91 
getIntentFilters()92     public List<InstantAppIntentFilter> getIntentFilters() {
93         return mFilters;
94     }
95 
getVersionCode()96     public int getVersionCode() {
97         return mVersionCode;
98     }
99 
100     @Override
describeContents()101     public int describeContents() {
102         return 0;
103     }
104 
105     @Override
writeToParcel(Parcel out, int flags)106     public void writeToParcel(Parcel out, int flags) {
107         out.writeParcelable(mDigest, flags);
108         out.writeString(mPackageName);
109         out.writeList(mFilters);
110         out.writeInt(mVersionCode);
111     }
112 
113     public static final Parcelable.Creator<InstantAppResolveInfo> CREATOR
114             = new Parcelable.Creator<InstantAppResolveInfo>() {
115         public InstantAppResolveInfo createFromParcel(Parcel in) {
116             return new InstantAppResolveInfo(in);
117         }
118 
119         public InstantAppResolveInfo[] newArray(int size) {
120             return new InstantAppResolveInfo[size];
121         }
122     };
123 
124     /**
125      * Helper class to generate and store each of the digests and prefixes
126      * sent to the Instant App Resolver.
127      * <p>
128      * Since intent filters may want to handle multiple hosts within a
129      * domain [eg “*.google.com”], the resolver is presented with multiple
130      * hash prefixes. For example, "a.b.c.d.e" generates digests for
131      * "d.e", "c.d.e", "b.c.d.e" and "a.b.c.d.e".
132      *
133      * @hide
134      */
135     @SystemApi
136     public static final class InstantAppDigest implements Parcelable {
137         private static final int DIGEST_MASK = 0xfffff000;
138         private static final int DIGEST_PREFIX_COUNT = 5;
139         /** Full digest of the domain hashes */
140         private final byte[][] mDigestBytes;
141         /** The first 4 bytes of the domain hashes */
142         private final int[] mDigestPrefix;
143 
InstantAppDigest(@onNull String hostName)144         public InstantAppDigest(@NonNull String hostName) {
145             this(hostName, -1 /*maxDigests*/);
146         }
147 
148         /** @hide */
InstantAppDigest(@onNull String hostName, int maxDigests)149         public InstantAppDigest(@NonNull String hostName, int maxDigests) {
150             if (hostName == null) {
151                 throw new IllegalArgumentException();
152             }
153             mDigestBytes = generateDigest(hostName.toLowerCase(Locale.ENGLISH), maxDigests);
154             mDigestPrefix = new int[mDigestBytes.length];
155             for (int i = 0; i < mDigestBytes.length; i++) {
156                 mDigestPrefix[i] =
157                         ((mDigestBytes[i][0] & 0xFF) << 24
158                                 | (mDigestBytes[i][1] & 0xFF) << 16
159                                 | (mDigestBytes[i][2] & 0xFF) << 8
160                                 | (mDigestBytes[i][3] & 0xFF) << 0)
161                         & DIGEST_MASK;
162             }
163         }
164 
generateDigest(String hostName, int maxDigests)165         private static byte[][] generateDigest(String hostName, int maxDigests) {
166             ArrayList<byte[]> digests = new ArrayList<>();
167             try {
168                 final MessageDigest digest = MessageDigest.getInstance(SHA_ALGORITHM);
169                 if (maxDigests <= 0) {
170                     final byte[] hostBytes = hostName.getBytes();
171                     digests.add(digest.digest(hostBytes));
172                 } else {
173                     int prevDot = hostName.lastIndexOf('.');
174                     prevDot = hostName.lastIndexOf('.', prevDot - 1);
175                     // shortcut for short URLs
176                     if (prevDot < 0) {
177                         digests.add(digest.digest(hostName.getBytes()));
178                     } else {
179                         byte[] hostBytes =
180                                 hostName.substring(prevDot + 1, hostName.length()).getBytes();
181                         digests.add(digest.digest(hostBytes));
182                         int digestCount = 1;
183                         while (prevDot >= 0 && digestCount < maxDigests) {
184                             prevDot = hostName.lastIndexOf('.', prevDot - 1);
185                             hostBytes =
186                                     hostName.substring(prevDot + 1, hostName.length()).getBytes();
187                             digests.add(digest.digest(hostBytes));
188                             digestCount++;
189                         }
190                     }
191                 }
192             } catch (NoSuchAlgorithmException e) {
193                 throw new IllegalStateException("could not find digest algorithm");
194             }
195             return digests.toArray(new byte[digests.size()][]);
196         }
197 
InstantAppDigest(Parcel in)198         InstantAppDigest(Parcel in) {
199             final int digestCount = in.readInt();
200             if (digestCount == -1) {
201                 mDigestBytes = null;
202             } else {
203                 mDigestBytes = new byte[digestCount][];
204                 for (int i = 0; i < digestCount; i++) {
205                     mDigestBytes[i] = in.createByteArray();
206                 }
207             }
208             mDigestPrefix = in.createIntArray();
209         }
210 
getDigestBytes()211         public byte[][] getDigestBytes() {
212             return mDigestBytes;
213         }
214 
getDigestPrefix()215         public int[] getDigestPrefix() {
216             return mDigestPrefix;
217         }
218 
219         @Override
describeContents()220         public int describeContents() {
221             return 0;
222         }
223 
224         @Override
writeToParcel(Parcel out, int flags)225         public void writeToParcel(Parcel out, int flags) {
226             if (mDigestBytes == null) {
227                 out.writeInt(-1);
228             } else {
229                 out.writeInt(mDigestBytes.length);
230                 for (int i = 0; i < mDigestBytes.length; i++) {
231                     out.writeByteArray(mDigestBytes[i]);
232                 }
233             }
234             out.writeIntArray(mDigestPrefix);
235         }
236 
237         @SuppressWarnings("hiding")
238         public static final Parcelable.Creator<InstantAppDigest> CREATOR =
239                 new Parcelable.Creator<InstantAppDigest>() {
240             @Override
241             public InstantAppDigest createFromParcel(Parcel in) {
242                 return new InstantAppDigest(in);
243             }
244             @Override
245             public InstantAppDigest[] newArray(int size) {
246                 return new InstantAppDigest[size];
247             }
248         };
249     }
250 }
251