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