1f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin/*
2f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Copyright (C) 2011 The Android Open Source Project
3f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin *
4f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Licensed under the Apache License, Version 2.0 (the "License");
5f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * you may not use this file except in compliance with the License.
6f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * You may obtain a copy of the License at
7f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin *
8f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin *      http://www.apache.org/licenses/LICENSE-2.0
9f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin *
10f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Unless required by applicable law or agreed to in writing, software
11f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * distributed under the License is distributed on an "AS IS" BASIS,
12f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * See the License for the specific language governing permissions and
14f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * limitations under the License.
15f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin */
16f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
17f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpackage com.android.gallery3d.common;
18f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
19f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.content.ContentResolver;
20f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.net.Uri;
21f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
22f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.io.FileInputStream;
23f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.io.IOException;
24f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.io.InputStream;
25f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.security.DigestInputStream;
26f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.security.MessageDigest;
27f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.security.NoSuchAlgorithmException;
28f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.Arrays;
29f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.List;
30f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
31f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin/**
32f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * MD5-based digest Wrapper.
33f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin */
34f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpublic class Fingerprint {
35f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // Instance of the MessageDigest using our specified digest algorithm.
36f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final MessageDigest DIGESTER;
37f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
38f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    /**
39f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * Name of the digest algorithm we use in {@link java.security.MessageDigest}
40f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     */
41f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final String DIGEST_MD5 = "md5";
42f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
43f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // Version 1 streamId prefix.
44f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // Hard coded stream id length limit is 40-chars. Don't ask!
45f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final String STREAM_ID_CS_PREFIX = "cs_01_";
46f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
47f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // 16 bytes for 128-bit fingerprint
48f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final int FINGERPRINT_BYTE_LENGTH;
49f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
50f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // length of prefix + 32 hex chars for 128-bit fingerprint
51f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final int STREAM_ID_CS_01_LENGTH;
52f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
53f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    static {
54f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        try {
55f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            DIGESTER = MessageDigest.getInstance(DIGEST_MD5);
56f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            FINGERPRINT_BYTE_LENGTH = DIGESTER.getDigestLength();
57f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            STREAM_ID_CS_01_LENGTH = STREAM_ID_CS_PREFIX.length()
58f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    + (FINGERPRINT_BYTE_LENGTH * 2);
59f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } catch (NoSuchAlgorithmException e) {
60f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            // can't continue, but really shouldn't happen
61f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            throw new IllegalStateException(e);
62f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
63f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
64f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
65f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // md5 digest bytes.
66f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final byte[] mMd5Digest;
67f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
68f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    /**
69f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * Creates a new Fingerprint.
70f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     */
71f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public Fingerprint(byte[] bytes) {
72f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if ((bytes == null) || (bytes.length != FINGERPRINT_BYTE_LENGTH)) {
73f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            throw new IllegalArgumentException();
74f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
75f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mMd5Digest = bytes;
76f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
77f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
78f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    /**
79f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * Creates a Fingerprint based on the contents of a file.
80f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     *
81f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * Note that this will close() stream after calculating the digest.
82f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * @param byteCount length of original data will be stored at byteCount[0] as a side product
83f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     *        of the fingerprint calculation
84f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     */
85f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static Fingerprint fromInputStream(InputStream stream, long[] byteCount)
86f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            throws IOException {
87f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        DigestInputStream in = null;
88f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        long count = 0;
89f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        try {
90f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            in = new DigestInputStream(stream, DIGESTER);
91f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            byte[] bytes = new byte[8192];
92f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            while (true) {
93f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                // scan through file to compute a fingerprint.
94f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int n = in.read(bytes);
95f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                if (n < 0) break;
96f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                count += n;
97f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
98f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } finally {
99f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (in != null) in.close();
100f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
101f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if ((byteCount != null) && (byteCount.length > 0)) byteCount[0] = count;
102f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return new Fingerprint(in.getMessageDigest().digest());
103f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
104f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
105f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    /**
106f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * Decodes a string stream id to a 128-bit fingerprint.
107f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     */
108f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static Fingerprint fromStreamId(String streamId) {
109f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if ((streamId == null)
110f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                || !streamId.startsWith(STREAM_ID_CS_PREFIX)
111f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                || (streamId.length() != STREAM_ID_CS_01_LENGTH)) {
112f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            throw new IllegalArgumentException("bad streamId: " + streamId);
113f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
114f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
115f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // decode the hex bytes of the fingerprint portion
116f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        byte[] bytes = new byte[FINGERPRINT_BYTE_LENGTH];
117f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int byteIdx = 0;
118f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        for (int idx = STREAM_ID_CS_PREFIX.length(); idx < STREAM_ID_CS_01_LENGTH;
119f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                idx += 2) {
120f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int value = (toDigit(streamId, idx) << 4) | toDigit(streamId, idx + 1);
121f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            bytes[byteIdx++] = (byte) (value & 0xff);
122f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
123f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return new Fingerprint(bytes);
124f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
125f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
126f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    /**
127f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * Scans a list of strings for a valid streamId.
128f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     *
129f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * @param streamIdList list of stream id's to be scanned
130f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * @return valid fingerprint or null if it can't be found
131f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     */
132f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static Fingerprint extractFingerprint(List<String> streamIdList) {
133f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        for (String streamId : streamIdList) {
134f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (streamId.startsWith(STREAM_ID_CS_PREFIX)) {
135f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                return fromStreamId(streamId);
136f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
137f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
138f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return null;
139f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
140f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
141f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    /**
142f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * Encodes a 128-bit fingerprint as a string stream id.
143f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     *
144f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * Stream id string is limited to 40 characters, which could be digits, lower case ASCII and
145f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     * underscores.
146f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin     */
147f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public String toStreamId() {
148f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        StringBuilder streamId = new StringBuilder(STREAM_ID_CS_PREFIX);
149f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        appendHexFingerprint(streamId, mMd5Digest);
150f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return streamId.toString();
151f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
152f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
153f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public byte[] getBytes() {
154f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mMd5Digest;
155f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
156f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
157f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
158f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public boolean equals(Object obj) {
159f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (this == obj) return true;
160f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (!(obj instanceof Fingerprint)) return false;
161f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        Fingerprint other = (Fingerprint) obj;
162f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return Arrays.equals(mMd5Digest, other.mMd5Digest);
163f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
164f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
165f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public boolean equals(byte[] md5Digest) {
166f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return Arrays.equals(mMd5Digest, md5Digest);
167f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
168f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
169f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
170f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int hashCode() {
171f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return Arrays.hashCode(mMd5Digest);
172f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
173f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
174f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // Utility methods.
175f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
176f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static int toDigit(String streamId, int index) {
177f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int digit = Character.digit(streamId.charAt(index), 16);
178f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (digit < 0) {
179f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            throw new IllegalArgumentException("illegal hex digit in " + streamId);
180f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
181f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return digit;
182f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
183f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
184f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static void appendHexFingerprint(StringBuilder sb, byte[] bytes) {
185f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        for (int idx = 0; idx < FINGERPRINT_BYTE_LENGTH; idx++) {
186f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int value = bytes[idx];
187f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            sb.append(Integer.toHexString((value >> 4) & 0x0f));
188f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            sb.append(Integer.toHexString(value& 0x0f));
189f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
190f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
191f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin}
192