22
33import android .content .Context ;
44import android .net .Uri ;
5+ import android .os .ParcelFileDescriptor ;
56
67import java .io .File ;
78import java .io .FileInputStream ;
1112
1213final class OtaApkVerifier {
1314
15+ private static final int OPEN_RETRY_COUNT = 5 ;
16+ private static final long OPEN_RETRY_DELAY_MS = 300L ;
17+
1418 private OtaApkVerifier () {
1519 }
1620
1721 // Verification is intentionally isolated so download integrity stays testable without UI code.
18- static VerificationResult verifyDownloadedApk (Context context , Uri apkUri , OtaUpdateManager .UpdateInfo info ) {
22+ static VerificationResult verifyDownloadedApk (
23+ Context context ,
24+ long downloadId ,
25+ Uri apkUri ,
26+ OtaUpdateManager .UpdateInfo info
27+ ) {
1928 boolean success ;
2029 String computed = "" ;
2130 String message ;
22- try (InputStream in = openApkStream (context , apkUri )) {
31+ try (InputStream in = openApkStream (context , downloadId , apkUri )) {
2332 if (info == null || info .expectedSha256 == null || info .expectedSha256 .isEmpty ()) {
2433 throw new IllegalStateException ("Missing expected SHA-256" );
2534 }
@@ -28,32 +37,70 @@ static VerificationResult verifyDownloadedApk(Context context, Uri apkUri, OtaUp
2837 message = success ? "OK" : "SHA256 mismatch" ;
2938 } catch (Exception e ) {
3039 success = false ;
31- message = e .getClass ().getSimpleName ();
40+ String detail = e .getMessage ();
41+ message = (detail == null || detail .trim ().isEmpty ())
42+ ? e .getClass ().getSimpleName ()
43+ : e .getClass ().getSimpleName () + ": " + detail ;
3244 }
3345 return new VerificationResult (success , computed , message );
3446 }
3547
36- private static InputStream openApkStream (Context context , Uri apkUri ) throws Exception {
48+ private static InputStream openApkStream (Context context , long downloadId , Uri apkUri ) throws Exception {
3749 if (context == null ) {
3850 throw new IllegalStateException ("Context missing" );
3951 }
40- if (apkUri == null ) {
41- throw new IllegalStateException ("Downloaded APK not found" );
42- }
43- String scheme = apkUri .getScheme ();
44- if ("file" .equalsIgnoreCase (scheme )) {
45- File apkFile = new File (apkUri .getPath ());
46- if (!apkFile .exists ()) {
47- throw new IllegalStateException ("Downloaded APK not found" );
52+ Exception lastError = null ;
53+ for (int attempt = 0 ; attempt < OPEN_RETRY_COUNT ; attempt += 1 ) {
54+ try {
55+ InputStream fromDownloadManager = openDownloadedFileStream (context , downloadId );
56+ if (fromDownloadManager != null ) {
57+ return fromDownloadManager ;
58+ }
59+ if (apkUri == null ) {
60+ throw new IllegalStateException ("Downloaded APK not found" );
61+ }
62+ String scheme = apkUri .getScheme ();
63+ if ("file" .equalsIgnoreCase (scheme )) {
64+ File apkFile = new File (apkUri .getPath ());
65+ if (!apkFile .exists ()) {
66+ throw new IllegalStateException ("Downloaded APK not found" );
67+ }
68+ return new FileInputStream (apkFile );
69+ }
70+
71+ InputStream stream = context .getContentResolver ().openInputStream (apkUri );
72+ if (stream != null ) {
73+ return stream ;
74+ }
75+ throw new IllegalStateException ("Downloaded APK stream unavailable" );
76+ } catch (Exception e ) {
77+ lastError = e ;
78+ if (attempt == OPEN_RETRY_COUNT - 1 ) {
79+ break ;
80+ }
81+ try {
82+ Thread .sleep (OPEN_RETRY_DELAY_MS );
83+ } catch (InterruptedException interrupted ) {
84+ Thread .currentThread ().interrupt ();
85+ throw new IllegalStateException ("Verification interrupted" , interrupted );
86+ }
4887 }
49- return new FileInputStream (apkFile );
5088 }
89+ throw lastError != null ? lastError : new IllegalStateException ("Downloaded APK not found" );
90+ }
5191
52- InputStream stream = context .getContentResolver ().openInputStream (apkUri );
53- if (stream == null ) {
54- throw new IllegalStateException ("Downloaded APK not found" );
92+ private static InputStream openDownloadedFileStream (Context context , long downloadId ) {
93+ if (downloadId <= 0L ) return null ;
94+ try {
95+ android .app .DownloadManager dm =
96+ (android .app .DownloadManager ) context .getSystemService (Context .DOWNLOAD_SERVICE );
97+ if (dm == null ) return null ;
98+ ParcelFileDescriptor pfd = dm .openDownloadedFile (downloadId );
99+ if (pfd == null ) return null ;
100+ return new ParcelFileDescriptor .AutoCloseInputStream (pfd );
101+ } catch (Throwable ignored ) {
102+ return null ;
55103 }
56- return stream ;
57104 }
58105
59106 private static String computeSha256 (File file ) throws Exception {
0 commit comments