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