16a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen/*
26a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * Copyright (C) 2015 The Android Open Source Project
36a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen *
46a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * Licensed under the Apache License, Version 2.0 (the "License");
56a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * you may not use this file except in compliance with the License.
66a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * You may obtain a copy of the License at
76a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen *
86a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen *      http://www.apache.org/licenses/LICENSE-2.0
96a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen *
106a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * Unless required by applicable law or agreed to in writing, software
116a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * distributed under the License is distributed on an "AS IS" BASIS,
126a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * See the License for the specific language governing permissions and
146a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * limitations under the License.
156a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen */
166a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
176a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wenpackage com.android.statementservice.retriever;
186a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
196a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wenimport org.json.JSONArray;
206a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wenimport org.json.JSONException;
216a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wenimport org.json.JSONObject;
226a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
236a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wenimport java.util.ArrayList;
246a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wenimport java.util.Collections;
256a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wenimport java.util.HashSet;
266a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wenimport java.util.List;
276a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wenimport java.util.Locale;
286a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
296a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen/**
306a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * Immutable value type that names an Android app asset.
316a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen *
326a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * <p>An Android app can be named by its package name and certificate fingerprints using this JSON
336a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * string: { "namespace": "android_app", "package_name": "[Java package name]",
346a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * "sha256_cert_fingerprints": ["[SHA256 fingerprint of signing cert]", "[additional cert]", ...] }
356a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen *
366a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * <p>For example, { "namespace": "android_app", "package_name": "com.test.mytestapp",
376a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * "sha256_cert_fingerprints": ["24:D9:B4:57:A6:42:FB:E6:E5:B8:D6:9E:7B:2D:C2:D1:CB:D1:77:17:1D:7F:D4:A9:16:10:11:AB:92:B9:8F:3F"]
386a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * }
396a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen *
406a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * <p>Given a signed APK, Java 7's commandline keytool can compute the fingerprint using:
416a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * {@code keytool -list -printcert -jarfile signed_app.apk}
426a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen *
436a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * <p>Each entry in "sha256_cert_fingerprints" is a colon-separated hex string (e.g. 14:6D:E9:...)
446a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * representing the certificate SHA-256 fingerprint.
456a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen */
466a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen/* package private */ final class AndroidAppAsset extends AbstractAsset {
476a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
486a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    private static final String MISSING_FIELD_FORMAT_STRING = "Expected %s to be set.";
496a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    private static final String MISSING_APPCERTS_FORMAT_STRING =
506a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            "Expected %s to be non-empty array.";
516a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    private static final String APPCERT_NOT_STRING_FORMAT_STRING = "Expected all %s to be strings.";
526a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
536a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    private final List<String> mCertFingerprints;
546a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    private final String mPackageName;
556a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
566a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    public List<String> getCertFingerprints() {
576a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        return Collections.unmodifiableList(mCertFingerprints);
586a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    }
596a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
606a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    public String getPackageName() {
616a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        return mPackageName;
626a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    }
636a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
646a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    @Override
656a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    public String toJson() {
666a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        AssetJsonWriter writer = new AssetJsonWriter();
676a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
686a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        writer.writeFieldLower(Utils.NAMESPACE_FIELD, Utils.NAMESPACE_ANDROID_APP);
696a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        writer.writeFieldLower(Utils.ANDROID_APP_ASSET_FIELD_PACKAGE_NAME, mPackageName);
706a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        writer.writeArrayUpper(Utils.ANDROID_APP_ASSET_FIELD_CERT_FPS, mCertFingerprints);
716a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
726a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        return writer.closeAndGetString();
736a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    }
746a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
756a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    @Override
766a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    public String toString() {
776a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        StringBuilder asset = new StringBuilder();
786a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        asset.append("AndroidAppAsset: ");
796a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        asset.append(toJson());
806a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        return asset.toString();
816a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    }
826a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
836a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    @Override
846a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    public boolean equals(Object o) {
856a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        if (!(o instanceof AndroidAppAsset)) {
866a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            return false;
876a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        }
886a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
896a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        return ((AndroidAppAsset) o).toJson().equals(toJson());
906a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    }
916a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
926a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    @Override
936a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    public int hashCode() {
946a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        return toJson().hashCode();
956a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    }
966a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
976a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    @Override
986a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    public int lookupKey() {
996a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        return getPackageName().hashCode();
1006a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    }
1016a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
102aef66921e9bf8147cb0f1444ebdb102016d69f3bJoseph Wen    @Override
103aef66921e9bf8147cb0f1444ebdb102016d69f3bJoseph Wen    public boolean followInsecureInclude() {
104aef66921e9bf8147cb0f1444ebdb102016d69f3bJoseph Wen        // Non-HTTPS includes are not allowed in Android App assets.
105aef66921e9bf8147cb0f1444ebdb102016d69f3bJoseph Wen        return false;
106aef66921e9bf8147cb0f1444ebdb102016d69f3bJoseph Wen    }
107aef66921e9bf8147cb0f1444ebdb102016d69f3bJoseph Wen
1086a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    /**
1096a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen     * Checks that the input is a valid Android app asset.
1106a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen     *
1116a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen     * @param asset a JSONObject that has "namespace", "package_name", and
1126a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen     *              "sha256_cert_fingerprints" fields.
1136a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen     * @throws AssociationServiceException if the asset is not well formatted.
1146a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen     */
1156a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    public static AndroidAppAsset create(JSONObject asset)
1166a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            throws AssociationServiceException {
1176a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        String packageName = asset.optString(Utils.ANDROID_APP_ASSET_FIELD_PACKAGE_NAME);
1186a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        if (packageName.equals("")) {
1196a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            throw new AssociationServiceException(String.format(MISSING_FIELD_FORMAT_STRING,
1206a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen                    Utils.ANDROID_APP_ASSET_FIELD_PACKAGE_NAME));
1216a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        }
1226a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
1236a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        JSONArray certArray = asset.optJSONArray(Utils.ANDROID_APP_ASSET_FIELD_CERT_FPS);
1246a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        if (certArray == null || certArray.length() == 0) {
1256a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            throw new AssociationServiceException(
1266a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen                    String.format(MISSING_APPCERTS_FORMAT_STRING,
1276a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen                            Utils.ANDROID_APP_ASSET_FIELD_CERT_FPS));
1286a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        }
1296a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        List<String> certFingerprints = new ArrayList<>(certArray.length());
1306a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        for (int i = 0; i < certArray.length(); i++) {
1316a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            try {
1326a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen                certFingerprints.add(certArray.getString(i));
1336a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            } catch (JSONException e) {
1346a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen                throw new AssociationServiceException(
1356a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen                        String.format(APPCERT_NOT_STRING_FORMAT_STRING,
1366a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen                                Utils.ANDROID_APP_ASSET_FIELD_CERT_FPS));
1376a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            }
1386a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        }
1396a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
1406a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        return new AndroidAppAsset(packageName, certFingerprints);
1416a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    }
1426a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
1436a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    /**
1446a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen     * Creates a new AndroidAppAsset.
1456a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen     *
1466a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen     * @param packageName the package name of the Android app.
1476a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen     * @param certFingerprints at least one of the Android app signing certificate sha-256
1486a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen     *                         fingerprint.
1496a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen     */
1506a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    public static AndroidAppAsset create(String packageName, List<String> certFingerprints) {
1516a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        if (packageName == null || packageName.equals("")) {
1526a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            throw new AssertionError("Expected packageName to be set.");
1536a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        }
1546a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        if (certFingerprints == null || certFingerprints.size() == 0) {
1556a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            throw new AssertionError("Expected certFingerprints to be set.");
1566a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        }
1576a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        List<String> lowerFps = new ArrayList<String>(certFingerprints.size());
1586a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        for (String fp : certFingerprints) {
1596a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            lowerFps.add(fp.toUpperCase(Locale.US));
1606a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        }
1616a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        return new AndroidAppAsset(packageName, lowerFps);
1626a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    }
1636a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
1646a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    private AndroidAppAsset(String packageName, List<String> certFingerprints) {
1656a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        if (packageName.equals("")) {
1666a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            mPackageName = null;
1676a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        } else {
1686a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            mPackageName = packageName;
1696a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        }
1706a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
1716a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        if (certFingerprints == null || certFingerprints.size() == 0) {
1726a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            mCertFingerprints = null;
1736a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        } else {
1746a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            mCertFingerprints = Collections.unmodifiableList(sortAndDeDuplicate(certFingerprints));
1756a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        }
1766a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    }
1776a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
1786a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    /**
1796a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen     * Returns an ASCII-sorted copy of the list of certs with all duplicates removed.
1806a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen     */
1816a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    private List<String> sortAndDeDuplicate(List<String> certs) {
1826a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        if (certs.size() <= 1) {
1836a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen            return certs;
1846a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        }
1856a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        HashSet<String> set = new HashSet<>(certs);
1866a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        List<String> result = new ArrayList<>(set);
1876a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        Collections.sort(result);
1886a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen        return result;
1896a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen    }
1906a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen
1916a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen}
192