1 /*
2 **
3 ** Copyright 2013, The Android Open Source Project
4 **
5 ** Licensed under the Apache License, Version 2.0 (the "License");
6 ** you may not use this file except in compliance with the License.
7 ** You may obtain a copy of the License at
8 **
9 **     http://www.apache.org/licenses/LICENSE-2.0
10 **
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 */
17 package com.android.packageinstaller;
18 
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.net.Uri;
22 import android.os.AsyncTask;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.os.SystemClock;
26 import android.provider.Settings;
27 import android.util.EventLog;
28 import android.util.Log;
29 
30 import java.io.BufferedInputStream;
31 import java.io.File;
32 import java.io.FileInputStream;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.security.MessageDigest;
36 import java.security.NoSuchAlgorithmException;
37 
38 import libcore.io.IoUtils;
39 
40 /**
41  * Analytics about an attempt to install a package via {@link PackageInstallerActivity}.
42  *
43  * <p>An instance of this class is created at the beginning of the install flow and gradually filled
44  * as the user progresses through the flow. When the flow terminates (regardless of the reason),
45  * {@link #setFlowFinished(byte)} is invoked which reports the installation attempt as an event
46  * to the Event Log.
47  */
48 public class InstallFlowAnalytics implements Parcelable {
49 
50     private static final String TAG = "InstallFlowAnalytics";
51 
52     /** Installation has not yet terminated. */
53     static final byte RESULT_NOT_YET_AVAILABLE = -1;
54 
55     /** Package successfully installed. */
56     static final byte RESULT_SUCCESS = 0;
57 
58     /** Installation failed because scheme unsupported. */
59     static final byte RESULT_FAILED_UNSUPPORTED_SCHEME = 1;
60 
61     /**
62      * Installation of an APK failed because of a failure to obtain information from the provided
63      * APK.
64      */
65     static final byte RESULT_FAILED_TO_GET_PACKAGE_INFO = 2;
66 
67     /**
68      * Installation of an already installed package into the current user profile failed because the
69      * specified package is not installed.
70      */
71     static final byte RESULT_FAILED_PACKAGE_MISSING = 3;
72 
73     /**
74      * Installation failed because installation from unknown sources is prohibited by the Unknown
75      * Sources setting.
76      */
77     static final byte RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING = 4;
78 
79     /** Installation cancelled by the user. */
80     static final byte RESULT_CANCELLED_BY_USER = 5;
81 
82     /**
83      * Installation failed due to {@code PackageManager} failure. PackageManager error code is
84      * provided in {@link #mPackageManagerInstallResult}).
85      */
86     static final byte RESULT_PACKAGE_MANAGER_INSTALL_FAILED = 6;
87 
88     private static final int FLAG_INSTALLS_FROM_UNKNOWN_SOURCES_PERMITTED = 1 << 0;
89     private static final int FLAG_INSTALL_REQUEST_FROM_UNKNOWN_SOURCE = 1 << 1;
90     private static final int FLAG_VERIFY_APPS_ENABLED = 1 << 2;
91     private static final int FLAG_APP_VERIFIER_INSTALLED = 1 << 3;
92     private static final int FLAG_FILE_URI = 1 << 4;
93     private static final int FLAG_REPLACE = 1 << 5;
94     private static final int FLAG_SYSTEM_APP = 1 << 6;
95     private static final int FLAG_PACKAGE_INFO_OBTAINED = 1 << 7;
96     private static final int FLAG_INSTALL_BUTTON_CLICKED = 1 << 8;
97     private static final int FLAG_NEW_PERMISSIONS_FOUND = 1 << 9;
98     private static final int FLAG_PERMISSIONS_DISPLAYED = 1 << 10;
99     private static final int FLAG_NEW_PERMISSIONS_DISPLAYED = 1 << 11;
100     private static final int FLAG_ALL_PERMISSIONS_DISPLAYED = 1 << 12;
101 
102     /**
103      * Information about this flow expressed as a collection of flags. See {@code FLAG_...}
104      * constants.
105      */
106     private int mFlags;
107 
108     /** Outcome of the flow. See {@code RESULT_...} constants. */
109     private byte mResult = RESULT_NOT_YET_AVAILABLE;
110 
111     /**
112      * Result code returned by {@code PackageManager} to install the package or {@code 0} if
113      * {@code PackageManager} has not yet been invoked to install the package.
114      */
115     private int mPackageManagerInstallResult;
116 
117     /**
118      * Time instant when the installation request arrived, measured in elapsed realtime
119      * milliseconds. See {@link SystemClock#elapsedRealtime()}.
120      */
121     private long mStartTimestampMillis;
122 
123     /**
124      * Time instant when the information about the package being installed was obtained, measured in
125      * elapsed realtime milliseconds. See {@link SystemClock#elapsedRealtime()}.
126      */
127     private long mPackageInfoObtainedTimestampMillis;
128 
129     /**
130      * Time instant when the user clicked the Install button, measured in elapsed realtime
131      * milliseconds. See {@link SystemClock#elapsedRealtime()}. This field is only valid if the
132      * Install button has been clicked, as signaled by {@link #FLAG_INSTALL_BUTTON_CLICKED}.
133      */
134     private long mInstallButtonClickTimestampMillis;
135 
136     /**
137      * Time instant when this flow terminated, measured in elapsed realtime milliseconds. See
138      * {@link SystemClock#elapsedRealtime()}.
139      */
140     private long mEndTimestampMillis;
141 
142     /** URI of the package being installed. */
143     private String mPackageUri;
144 
145     /** Whether this attempt has been logged to the Event Log. */
146     private boolean mLogged;
147 
148     private Context mContext;
149 
150     public static final Parcelable.Creator<InstallFlowAnalytics> CREATOR =
151             new Parcelable.Creator<InstallFlowAnalytics>() {
152         @Override
153         public InstallFlowAnalytics createFromParcel(Parcel in) {
154             return new InstallFlowAnalytics(in);
155         }
156 
157         @Override
158         public InstallFlowAnalytics[] newArray(int size) {
159             return new InstallFlowAnalytics[size];
160         }
161     };
162 
InstallFlowAnalytics()163     public InstallFlowAnalytics() {}
164 
InstallFlowAnalytics(Parcel in)165     public InstallFlowAnalytics(Parcel in) {
166         mFlags = in.readInt();
167         mResult = in.readByte();
168         mPackageManagerInstallResult = in.readInt();
169         mStartTimestampMillis = in.readLong();
170         mPackageInfoObtainedTimestampMillis = in.readLong();
171         mInstallButtonClickTimestampMillis = in.readLong();
172         mEndTimestampMillis = in.readLong();
173         mPackageUri = in.readString();
174         mLogged = readBoolean(in);
175     }
176 
177     @Override
writeToParcel(Parcel dest, int flags)178     public void writeToParcel(Parcel dest, int flags) {
179         dest.writeInt(mFlags);
180         dest.writeByte(mResult);
181         dest.writeInt(mPackageManagerInstallResult);
182         dest.writeLong(mStartTimestampMillis);
183         dest.writeLong(mPackageInfoObtainedTimestampMillis);
184         dest.writeLong(mInstallButtonClickTimestampMillis);
185         dest.writeLong(mEndTimestampMillis);
186         dest.writeString(mPackageUri);
187         writeBoolean(dest, mLogged);
188     }
189 
writeBoolean(Parcel dest, boolean value)190     private static void writeBoolean(Parcel dest, boolean value) {
191         dest.writeByte((byte) (value ? 1 : 0));
192     }
193 
readBoolean(Parcel dest)194     private static boolean readBoolean(Parcel dest) {
195         return dest.readByte() != 0;
196     }
197 
198     @Override
describeContents()199     public int describeContents() {
200         return 0;
201     }
202 
setContext(Context context)203     void setContext(Context context) {
204         mContext = context;
205     }
206 
207     /** Sets whether the Unknown Sources setting is checked. */
setInstallsFromUnknownSourcesPermitted(boolean permitted)208     void setInstallsFromUnknownSourcesPermitted(boolean permitted) {
209         setFlagState(FLAG_INSTALLS_FROM_UNKNOWN_SOURCES_PERMITTED, permitted);
210     }
211 
212     /** Gets whether the Unknown Sources setting is checked. */
isInstallsFromUnknownSourcesPermitted()213     private boolean isInstallsFromUnknownSourcesPermitted() {
214         return isFlagSet(FLAG_INSTALLS_FROM_UNKNOWN_SOURCES_PERMITTED);
215     }
216 
217     /** Sets whether this install attempt is from an unknown source. */
setInstallRequestFromUnknownSource(boolean unknownSource)218     void setInstallRequestFromUnknownSource(boolean unknownSource) {
219         setFlagState(FLAG_INSTALL_REQUEST_FROM_UNKNOWN_SOURCE, unknownSource);
220     }
221 
222     /** Gets whether this install attempt is from an unknown source. */
isInstallRequestFromUnknownSource()223     private boolean isInstallRequestFromUnknownSource() {
224         return isFlagSet(FLAG_INSTALL_REQUEST_FROM_UNKNOWN_SOURCE);
225     }
226 
227     /** Sets whether app verification is enabled. */
setVerifyAppsEnabled(boolean enabled)228     void setVerifyAppsEnabled(boolean enabled) {
229         setFlagState(FLAG_VERIFY_APPS_ENABLED, enabled);
230     }
231 
232     /** Gets whether app verification is enabled. */
isVerifyAppsEnabled()233     private boolean isVerifyAppsEnabled() {
234         return isFlagSet(FLAG_VERIFY_APPS_ENABLED);
235     }
236 
237     /** Sets whether at least one app verifier is installed. */
setAppVerifierInstalled(boolean installed)238     void setAppVerifierInstalled(boolean installed) {
239         setFlagState(FLAG_APP_VERIFIER_INSTALLED, installed);
240     }
241 
242     /** Gets whether at least one app verifier is installed. */
isAppVerifierInstalled()243     private boolean isAppVerifierInstalled() {
244         return isFlagSet(FLAG_APP_VERIFIER_INSTALLED);
245     }
246 
247     /**
248      * Sets whether an APK file is being installed.
249      *
250      * @param fileUri {@code true} if an APK file is being installed, {@code false} if an already
251      *        installed package is being installed to this user profile.
252      */
setFileUri(boolean fileUri)253     void setFileUri(boolean fileUri) {
254         setFlagState(FLAG_FILE_URI, fileUri);
255     }
256 
257     /**
258      * Sets the URI of the package being installed.
259      */
setPackageUri(String packageUri)260     void setPackageUri(String packageUri) {
261         mPackageUri = packageUri;
262     }
263 
264     /**
265      * Gets whether an APK file is being installed.
266      *
267      * @return {@code true} if an APK file is being installed, {@code false} if an already
268      *         installed package is being installed to this user profile.
269      */
isFileUri()270     private boolean isFileUri() {
271         return isFlagSet(FLAG_FILE_URI);
272     }
273 
274     /** Sets whether this is an attempt to replace an existing package. */
setReplace(boolean replace)275     void setReplace(boolean replace) {
276         setFlagState(FLAG_REPLACE, replace);
277     }
278 
279     /** Gets whether this is an attempt to replace an existing package. */
isReplace()280     private boolean isReplace() {
281         return isFlagSet(FLAG_REPLACE);
282     }
283 
284     /** Sets whether the package being updated is a system package. */
setSystemApp(boolean systemApp)285     void setSystemApp(boolean systemApp) {
286         setFlagState(FLAG_SYSTEM_APP, systemApp);
287     }
288 
289     /** Gets whether the package being updated is a system package. */
isSystemApp()290     private boolean isSystemApp() {
291         return isFlagSet(FLAG_SYSTEM_APP);
292     }
293 
294     /**
295      * Sets whether the package being installed is requesting more permissions than the already
296      * installed version of the package.
297      */
setNewPermissionsFound(boolean found)298     void setNewPermissionsFound(boolean found) {
299         setFlagState(FLAG_NEW_PERMISSIONS_FOUND, found);
300     }
301 
302     /**
303      * Gets whether the package being installed is requesting more permissions than the already
304      * installed version of the package.
305      */
isNewPermissionsFound()306     private boolean isNewPermissionsFound() {
307         return isFlagSet(FLAG_NEW_PERMISSIONS_FOUND);
308     }
309 
310     /** Sets whether permissions were displayed to the user. */
setPermissionsDisplayed(boolean displayed)311     void setPermissionsDisplayed(boolean displayed) {
312         setFlagState(FLAG_PERMISSIONS_DISPLAYED, displayed);
313     }
314 
315     /** Gets whether permissions were displayed to the user. */
isPermissionsDisplayed()316     private boolean isPermissionsDisplayed() {
317         return isFlagSet(FLAG_PERMISSIONS_DISPLAYED);
318     }
319 
320     /**
321      * Sets whether new permissions were displayed to the user (if permissions were displayed at
322      * all).
323      */
setNewPermissionsDisplayed(boolean displayed)324     void setNewPermissionsDisplayed(boolean displayed) {
325         setFlagState(FLAG_NEW_PERMISSIONS_DISPLAYED, displayed);
326     }
327 
328     /**
329      * Gets whether new permissions were displayed to the user (if permissions were displayed at
330      * all).
331      */
isNewPermissionsDisplayed()332     private boolean isNewPermissionsDisplayed() {
333         return isFlagSet(FLAG_NEW_PERMISSIONS_DISPLAYED);
334     }
335 
336     /**
337      * Sets whether all permissions were displayed to the user (if permissions were displayed at
338      * all).
339      */
setAllPermissionsDisplayed(boolean displayed)340     void setAllPermissionsDisplayed(boolean displayed) {
341         setFlagState(FLAG_ALL_PERMISSIONS_DISPLAYED, displayed);
342     }
343 
344     /**
345      * Gets whether all permissions were displayed to the user (if permissions were displayed at
346      * all).
347      */
isAllPermissionsDisplayed()348     private boolean isAllPermissionsDisplayed() {
349         return isFlagSet(FLAG_ALL_PERMISSIONS_DISPLAYED);
350     }
351 
352     /**
353      * Sets the time instant when the installation request arrived, measured in elapsed realtime
354      * milliseconds. See {@link SystemClock#elapsedRealtime()}.
355      */
setStartTimestampMillis(long timestampMillis)356     void setStartTimestampMillis(long timestampMillis) {
357         mStartTimestampMillis = timestampMillis;
358     }
359 
360     /**
361      * Records that the information about the package info has been obtained or that there has been
362      * a failure to obtain the information.
363      */
setPackageInfoObtained()364     void setPackageInfoObtained() {
365         setFlagState(FLAG_PACKAGE_INFO_OBTAINED, true);
366         mPackageInfoObtainedTimestampMillis = SystemClock.elapsedRealtime();
367     }
368 
369     /**
370      * Checks whether the information about the package info has been obtained or that there has
371      * been a failure to obtain the information.
372      */
isPackageInfoObtained()373     private boolean isPackageInfoObtained() {
374         return isFlagSet(FLAG_PACKAGE_INFO_OBTAINED);
375     }
376 
377     /**
378      * Records that the Install button has been clicked.
379      */
setInstallButtonClicked()380     void setInstallButtonClicked() {
381         setFlagState(FLAG_INSTALL_BUTTON_CLICKED, true);
382         mInstallButtonClickTimestampMillis = SystemClock.elapsedRealtime();
383     }
384 
385     /**
386      * Checks whether the Install button has been clicked.
387      */
isInstallButtonClicked()388     private boolean isInstallButtonClicked() {
389         return isFlagSet(FLAG_INSTALL_BUTTON_CLICKED);
390     }
391 
392     /**
393      * Marks this flow as finished due to {@code PackageManager} succeeding or failing to install
394      * the package and reports this to the Event Log.
395      */
setFlowFinishedWithPackageManagerResult(int packageManagerResult)396     void setFlowFinishedWithPackageManagerResult(int packageManagerResult) {
397         mPackageManagerInstallResult = packageManagerResult;
398         if (packageManagerResult == PackageManager.INSTALL_SUCCEEDED) {
399             setFlowFinished(
400                     InstallFlowAnalytics.RESULT_SUCCESS);
401         } else {
402             setFlowFinished(
403                     InstallFlowAnalytics.RESULT_PACKAGE_MANAGER_INSTALL_FAILED);
404         }
405     }
406 
407     /**
408      * Marks this flow as finished and reports this to the Event Log.
409      */
setFlowFinished(byte result)410     void setFlowFinished(byte result) {
411         if (mLogged) {
412             return;
413         }
414         mResult = result;
415         mEndTimestampMillis = SystemClock.elapsedRealtime();
416         writeToEventLog();
417     }
418 
writeToEventLog()419     private void writeToEventLog() {
420         byte packageManagerInstallResultByte = 0;
421         if (mResult == RESULT_PACKAGE_MANAGER_INSTALL_FAILED) {
422             // PackageManager install error codes are negative, starting from -1 and going to
423             // -111 (at the moment). We thus store them in negated form.
424             packageManagerInstallResultByte = clipUnsignedValueToUnsignedByte(
425                     -mPackageManagerInstallResult);
426         }
427 
428         final int resultAndFlags = (mResult & 0xff)
429                 | ((packageManagerInstallResultByte & 0xff) << 8)
430                 | ((mFlags & 0xffff) << 16);
431 
432         // Total elapsed time from start to end, in milliseconds.
433         final int totalElapsedTime =
434                 clipUnsignedLongToUnsignedInt(mEndTimestampMillis - mStartTimestampMillis);
435 
436         // Total elapsed time from start till information about the package being installed was
437         // obtained, in milliseconds.
438         final int elapsedTimeTillPackageInfoObtained = (isPackageInfoObtained())
439                 ? clipUnsignedLongToUnsignedInt(
440                         mPackageInfoObtainedTimestampMillis - mStartTimestampMillis)
441                 : 0;
442 
443         // Total elapsed time from start till Install button clicked, in milliseconds
444         // milliseconds.
445         final int elapsedTimeTillInstallButtonClick = (isInstallButtonClicked())
446                 ? clipUnsignedLongToUnsignedInt(
447                             mInstallButtonClickTimestampMillis - mStartTimestampMillis)
448                 : 0;
449 
450         // If this user has consented to app verification, augment the logged event with the hash of
451         // the contents of the APK.
452         if (((mFlags & FLAG_FILE_URI) != 0)
453                 && ((mFlags & FLAG_VERIFY_APPS_ENABLED) != 0)
454                 && (isUserConsentToVerifyAppsGranted())) {
455             // Log the hash of the APK's contents.
456             // Reading the APK may take a while -- perform in background.
457             AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
458                 @Override
459                 public void run() {
460                     byte[] digest = null;
461                     try {
462                         digest = getPackageContentsDigest();
463                     } catch (IOException e) {
464                         Log.w(TAG, "Failed to hash APK contents", e);
465                     } finally {
466                         String digestHex = (digest != null)
467                                 ? IntegralToString.bytesToHexString(digest, false)
468                                 : "";
469                         EventLogTags.writeInstallPackageAttempt(
470                                 resultAndFlags,
471                                 totalElapsedTime,
472                                 elapsedTimeTillPackageInfoObtained,
473                                 elapsedTimeTillInstallButtonClick,
474                                 digestHex);
475                     }
476                 }
477             });
478         } else {
479             // Do not log the hash of the APK's contents
480             EventLogTags.writeInstallPackageAttempt(
481                     resultAndFlags,
482                     totalElapsedTime,
483                     elapsedTimeTillPackageInfoObtained,
484                     elapsedTimeTillInstallButtonClick,
485                     "");
486         }
487         mLogged = true;
488 
489         if (Log.isLoggable(TAG, Log.VERBOSE)) {
490             Log.v(TAG, "Analytics:"
491                     + "\n\tinstallsFromUnknownSourcesPermitted: "
492                         + isInstallsFromUnknownSourcesPermitted()
493                     + "\n\tinstallRequestFromUnknownSource: " + isInstallRequestFromUnknownSource()
494                     + "\n\tverifyAppsEnabled: " + isVerifyAppsEnabled()
495                     + "\n\tappVerifierInstalled: " + isAppVerifierInstalled()
496                     + "\n\tfileUri: " + isFileUri()
497                     + "\n\treplace: " + isReplace()
498                     + "\n\tsystemApp: " + isSystemApp()
499                     + "\n\tpackageInfoObtained: " + isPackageInfoObtained()
500                     + "\n\tinstallButtonClicked: " + isInstallButtonClicked()
501                     + "\n\tpermissionsDisplayed: " + isPermissionsDisplayed()
502                     + "\n\tnewPermissionsDisplayed: " + isNewPermissionsDisplayed()
503                     + "\n\tallPermissionsDisplayed: " + isAllPermissionsDisplayed()
504                     + "\n\tnewPermissionsFound: " + isNewPermissionsFound()
505                     + "\n\tresult: " + mResult
506                     + "\n\tpackageManagerInstallResult: " + mPackageManagerInstallResult
507                     + "\n\ttotalDuration: " + (mEndTimestampMillis - mStartTimestampMillis) + " ms"
508                     + "\n\ttimeTillPackageInfoObtained: "
509                         + ((isPackageInfoObtained())
510                             ? ((mPackageInfoObtainedTimestampMillis - mStartTimestampMillis)
511                                     + " ms")
512                             : "n/a")
513                     + "\n\ttimeTillInstallButtonClick: "
514                         + ((isInstallButtonClicked())
515                             ? ((mInstallButtonClickTimestampMillis - mStartTimestampMillis) + " ms")
516                             : "n/a"));
517             Log.v(TAG, "Wrote to Event Log: 0x" + Long.toString(resultAndFlags & 0xffffffffL, 16)
518                     + ", " + totalElapsedTime
519                     + ", " + elapsedTimeTillPackageInfoObtained
520                     + ", " + elapsedTimeTillInstallButtonClick);
521         }
522     }
523 
clipUnsignedValueToUnsignedByte(long value)524     private static final byte clipUnsignedValueToUnsignedByte(long value) {
525         if (value < 0) {
526             return 0;
527         } else if (value > 0xff) {
528             return (byte) 0xff;
529         } else {
530             return (byte) value;
531         }
532     }
533 
clipUnsignedLongToUnsignedInt(long value)534     private static final int clipUnsignedLongToUnsignedInt(long value) {
535         if (value < 0) {
536             return 0;
537         } else if (value > 0xffffffffL) {
538             return 0xffffffff;
539         } else {
540             return (int) value;
541         }
542     }
543 
544     /**
545      * Sets or clears the specified flag in the {@link #mFlags} field.
546      */
setFlagState(int flag, boolean set)547     private void setFlagState(int flag, boolean set) {
548         if (set) {
549             mFlags |= flag;
550         } else {
551             mFlags &= ~flag;
552         }
553     }
554 
555     /**
556      * Checks whether the specified flag is set in the {@link #mFlags} field.
557      */
isFlagSet(int flag)558     private boolean isFlagSet(int flag) {
559         return (mFlags & flag) == flag;
560     }
561 
562     /**
563      * Checks whether the user has consented to app verification.
564      */
isUserConsentToVerifyAppsGranted()565     private boolean isUserConsentToVerifyAppsGranted() {
566         return Settings.Secure.getInt(
567                 mContext.getContentResolver(),
568                 Settings.Secure.PACKAGE_VERIFIER_USER_CONSENT, 0) != 0;
569     }
570 
571     /**
572      * Gets the digest of the contents of the package being installed.
573      */
getPackageContentsDigest()574     private byte[] getPackageContentsDigest() throws IOException {
575         File file = new File(Uri.parse(mPackageUri).getPath());
576         return getSha256ContentsDigest(file);
577     }
578 
579     /**
580      * Gets the SHA-256 digest of the contents of the specified file.
581      */
getSha256ContentsDigest(File file)582     private static byte[] getSha256ContentsDigest(File file) throws IOException {
583         MessageDigest digest;
584         try {
585             digest = MessageDigest.getInstance("SHA-256");
586         } catch (NoSuchAlgorithmException e) {
587             throw new RuntimeException("SHA-256 not available", e);
588         }
589 
590         byte[] buf = new byte[8192];
591         InputStream in = null;
592         try {
593             in = new BufferedInputStream(new FileInputStream(file), buf.length);
594             int chunkSize;
595             while ((chunkSize = in.read(buf)) != -1) {
596                 digest.update(buf, 0, chunkSize);
597             }
598         } finally {
599             IoUtils.closeQuietly(in);
600         }
601         return digest.digest();
602     }
603 }