diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index a323d42e1aee1..092f603e3fa11 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -547,7 +547,6 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterView.java FILE: ../../../flutter/shell/platform/android/io/flutter/view/ResourceCleaner.java FILE: ../../../flutter/shell/platform/android/io/flutter/view/ResourceExtractor.java FILE: ../../../flutter/shell/platform/android/io/flutter/view/ResourcePaths.java -FILE: ../../../flutter/shell/platform/android/io/flutter/view/ResourceUpdater.java FILE: ../../../flutter/shell/platform/android/io/flutter/view/TextureRegistry.java FILE: ../../../flutter/shell/platform/android/io/flutter/view/VsyncWaiter.java FILE: ../../../flutter/shell/platform/android/library_loader.cc diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 2eddf97c1a56c..4dad12890cf81 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -167,7 +167,6 @@ java_library("flutter_shell_java") { "io/flutter/view/ResourceCleaner.java", "io/flutter/view/ResourceExtractor.java", "io/flutter/view/ResourcePaths.java", - "io/flutter/view/ResourceUpdater.java", "io/flutter/view/TextureRegistry.java", "io/flutter/view/VsyncWaiter.java", ] diff --git a/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java b/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java index f7d56d289270f..1ab43db5f792f 100644 --- a/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java +++ b/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java @@ -32,7 +32,6 @@ import io.flutter.view.FlutterNativeView; import io.flutter.view.FlutterRunArguments; import io.flutter.view.FlutterView; -import io.flutter.view.ResourceUpdater; import org.json.JSONObject; import java.io.File; @@ -213,7 +212,6 @@ public void onStart() { @Override public void onResume() { Application app = (Application) activity.getApplicationContext(); - FlutterMain.onResume(app); if (app instanceof FlutterApplication) { FlutterApplication flutterApp = (FlutterApplication) app; flutterApp.setCurrentActivity(activity); @@ -354,14 +352,6 @@ private void runBundle(String appBundlePath) { if (!flutterView.getFlutterNativeView().isApplicationRunning()) { FlutterRunArguments args = new FlutterRunArguments(); ArrayList bundlePaths = new ArrayList<>(); - ResourceUpdater resourceUpdater = FlutterMain.getResourceUpdater(); - if (resourceUpdater != null) { - File patchFile = resourceUpdater.getInstalledPatch(); - JSONObject manifest = resourceUpdater.readManifest(patchFile); - if (resourceUpdater.validateManifest(manifest)) { - bundlePaths.add(patchFile.getPath()); - } - } bundlePaths.add(appBundlePath); args.bundlePaths = bundlePaths.toArray(new String[0]); args.entrypoint = "main"; diff --git a/shell/platform/android/io/flutter/view/FlutterMain.java b/shell/platform/android/io/flutter/view/FlutterMain.java index 14b932061877e..f934f9a1a04a9 100644 --- a/shell/platform/android/io/flutter/view/FlutterMain.java +++ b/shell/platform/android/io/flutter/view/FlutterMain.java @@ -79,7 +79,6 @@ private static String fromFlutterAssets(String filePath) { private static String sFlutterAssetsDir = DEFAULT_FLUTTER_ASSETS_DIR; private static boolean sInitialized = false; - private static ResourceUpdater sResourceUpdater; private static ResourceExtractor sResourceExtractor; private static boolean sIsPrecompiledAsBlobs; private static boolean sIsPrecompiledAsSharedLibrary; @@ -157,17 +156,7 @@ public static void startInitialization(Context applicationContext, Settings sett initAot(applicationContext); initResources(applicationContext); - if (sResourceUpdater == null) { - System.loadLibrary("flutter"); - } else { - sResourceExtractor.waitForCompletion(); - File lib = new File(PathUtils.getDataDirectory(applicationContext), DEFAULT_LIBRARY); - if (lib.exists()) { - System.load(lib.getAbsolutePath()); - } else { - System.loadLibrary("flutter"); - } - } + System.loadLibrary("flutter"); // We record the initialization time using SystemClock because at the start of the // initialization we have not yet loaded the native library to call into dart_tools_api.h. @@ -316,21 +305,6 @@ private static void initResources(Context applicationContext) { Log.e(TAG, "Unable to read application info", e); } - if (metaData != null && metaData.getBoolean("DynamicPatching")) { - sResourceUpdater = new ResourceUpdater(context); - // Also checking for ON_RESUME here since it's more efficient than waiting for actual - // onResume. Even though actual onResume is imminent when the app has just restarted, - // it's better to start downloading now, in parallel with the rest of initialization, - // and avoid a second application restart a bit later when actual onResume happens. - if (sResourceUpdater.getDownloadMode() == ResourceUpdater.DownloadMode.ON_RESTART || - sResourceUpdater.getDownloadMode() == ResourceUpdater.DownloadMode.ON_RESUME) { - sResourceUpdater.startUpdateDownloadOnce(); - if (sResourceUpdater.getInstallMode() == ResourceUpdater.InstallMode.IMMEDIATE) { - sResourceUpdater.waitForDownloadCompletion(); - } - } - } - sResourceExtractor = new ResourceExtractor(context); sResourceExtractor @@ -353,22 +327,9 @@ private static void initResources(Context applicationContext) { .addResource(sAotIsolateSnapshotInstr); } - if (sResourceUpdater != null) { - sResourceExtractor - .addResource(DEFAULT_LIBRARY); - } - sResourceExtractor.start(); } - public static void onResume(Context context) { - if (sResourceUpdater != null) { - if (sResourceUpdater.getDownloadMode() == ResourceUpdater.DownloadMode.ON_RESUME) { - sResourceUpdater.startUpdateDownloadOnce(); - } - } - } - /** * Returns a list of the file names at the root of the application's asset * path. @@ -410,15 +371,6 @@ public static String findAppBundlePath(Context applicationContext) { return appBundle.exists() ? appBundle.getPath() : null; } - /** - * Returns the main internal interface for the dynamic patching subsystem. - * - * If this is null, it means that dynamic patching is disabled in this app. - */ - public static ResourceUpdater getResourceUpdater() { - return sResourceUpdater; - } - /** * Returns the file name for the given asset. * The returned file name can be used to access the asset in the APK diff --git a/shell/platform/android/io/flutter/view/ResourceExtractor.java b/shell/platform/android/io/flutter/view/ResourceExtractor.java index 2895db56b4cbb..424ace596be93 100644 --- a/shell/platform/android/io/flutter/view/ResourceExtractor.java +++ b/shell/platform/android/io/flutter/view/ResourceExtractor.java @@ -52,64 +52,26 @@ private class ExtractTask extends AsyncTask { protected Void doInBackground(Void... unused) { final File dataDir = new File(PathUtils.getDataDirectory(mContext)); - ResourceUpdater resourceUpdater = FlutterMain.getResourceUpdater(); - if (resourceUpdater != null) { - // Protect patch file from being overwritten by downloader while - // it's being extracted since downloading happens asynchronously. - resourceUpdater.getInstallationLock().lock(); + final String timestamp = checkTimestamp(dataDir); + if (timestamp == null) { + return null; } - try { - if (resourceUpdater != null) { - File updateFile = resourceUpdater.getDownloadedPatch(); - File activeFile = resourceUpdater.getInstalledPatch(); - - if (updateFile.exists()) { - JSONObject manifest = resourceUpdater.readManifest(updateFile); - if (resourceUpdater.validateManifest(manifest)) { - // Graduate patch file as active for asset manager. - if (activeFile.exists() && !activeFile.delete()) { - Log.w(TAG, "Could not delete file " + activeFile); - return null; - } - if (!updateFile.renameTo(activeFile)) { - Log.w(TAG, "Could not create file " + activeFile); - return null; - } - } - } - } - - final String timestamp = checkTimestamp(dataDir); - if (timestamp == null) { - return null; - } - - deleteFiles(); - - if (!extractUpdate(dataDir)) { - return null; - } + deleteFiles(); - if (!extractAPK(dataDir)) { - return null; - } + if (!extractAPK(dataDir)) { + return null; + } - if (timestamp != null) { - try { - new File(dataDir, timestamp).createNewFile(); - } catch (IOException e) { - Log.w(TAG, "Failed to write resource timestamp"); - } + if (timestamp != null) { + try { + new File(dataDir, timestamp).createNewFile(); + } catch (IOException e) { + Log.w(TAG, "Failed to write resource timestamp"); } + } - return null; - - } finally { - if (resourceUpdater != null) { - resourceUpdater.getInstallationLock().unlock(); - } - } + return null; } } @@ -213,133 +175,6 @@ private boolean extractAPK(File dataDir) { return true; } - /// Returns true if successfully unpacked update resources or if there is no update, - /// otherwise deletes all resources and returns false. - private boolean extractUpdate(File dataDir) { - final AssetManager manager = mContext.getResources().getAssets(); - - ResourceUpdater resourceUpdater = FlutterMain.getResourceUpdater(); - if (resourceUpdater == null) { - return true; - } - - File updateFile = resourceUpdater.getInstalledPatch(); - if (!updateFile.exists()) { - return true; - } - - JSONObject manifest = resourceUpdater.readManifest(updateFile); - if (!resourceUpdater.validateManifest(manifest)) { - // Obsolete patch file, nothing to install. - return true; - } - - ZipFile zipFile; - try { - zipFile = new ZipFile(updateFile); - - } catch (IOException e) { - Log.w(TAG, "Exception unpacking resources: " + e.getMessage()); - deleteFiles(); - return false; - } - - for (String asset : mResources) { - String resource = null; - ZipEntry entry = null; - if (asset.endsWith(".so")) { - // Replicate library lookup logic. - for (String abi : SUPPORTED_ABIS) { - resource = "lib/" + abi + "/" + asset; - entry = zipFile.getEntry(resource); - if (entry == null) { - entry = zipFile.getEntry(resource + ".bzdiff40"); - if (entry == null) { - continue; - } - } - - // Stop after the first match. - break; - } - } - - if (entry == null) { - resource = "assets/" + asset; - entry = zipFile.getEntry(resource); - if (entry == null) { - entry = zipFile.getEntry(resource + ".bzdiff40"); - if (entry == null) { - continue; - } - } - } - - final File output = new File(dataDir, asset); - if (output.exists()) { - continue; - } - if (output.getParentFile() != null) { - output.getParentFile().mkdirs(); - } - - try { - if (entry.getName().endsWith(".bzdiff40")) { - ByteArrayOutputStream diff = new ByteArrayOutputStream(); - try (InputStream is = zipFile.getInputStream(entry)) { - copy(is, diff); - } - - ByteArrayOutputStream orig = new ByteArrayOutputStream(); - if (asset.endsWith(".so")) { - ZipFile apkFile = new ZipFile(getAPKPath()); - if (apkFile == null) { - throw new IOException("Could not find APK"); - } - - ZipEntry origEntry = apkFile.getEntry(resource); - if (origEntry == null) { - throw new IOException("Could not find APK resource " + resource); - } - - try (InputStream is = apkFile.getInputStream(origEntry)) { - copy(is, orig); - } - - } else { - try (InputStream is = manager.open(asset)) { - copy(is, orig); - } catch (FileNotFoundException e) { - throw new IOException("Could not find APK resource " + resource); - } - } - - try (OutputStream os = new FileOutputStream(output)) { - os.write(BSDiff.bspatch(orig.toByteArray(), diff.toByteArray())); - } - - } else { - try (InputStream is = zipFile.getInputStream(entry); - OutputStream os = new FileOutputStream(output)) { - copy(is, os); - } - } - - Log.i(TAG, "Extracted override resource " + entry.getName()); - - } catch (FileNotFoundException fnfe) { - continue; - - } catch (IOException ioe) { - Log.w(TAG, "Exception unpacking resources: " + ioe.getMessage()); - deleteFiles(); - return false; - } - } - - return true; - } - // Returns null if extracted resources are found and match the current APK version // and update version if any, otherwise returns the current APK and update version. private String checkTimestamp(File dataDir) { @@ -359,20 +194,6 @@ private String checkTimestamp(File dataDir) { String expectedTimestamp = TIMESTAMP_PREFIX + getVersionCode(packageInfo) + "-" + packageInfo.lastUpdateTime; - ResourceUpdater resourceUpdater = FlutterMain.getResourceUpdater(); - if (resourceUpdater != null) { - File patchFile = resourceUpdater.getInstalledPatch(); - JSONObject manifest = resourceUpdater.readManifest(patchFile); - if (resourceUpdater.validateManifest(manifest)) { - String patchNumber = manifest.optString("patchNumber", null); - if (patchNumber != null) { - expectedTimestamp += "-" + patchNumber + "-" + patchFile.lastModified(); - } else { - expectedTimestamp += "-" + patchFile.lastModified(); - } - } - } - final String[] existingTimestamps = getExistingTimestamps(dataDir); if (existingTimestamps == null) { diff --git a/shell/platform/android/io/flutter/view/ResourceUpdater.java b/shell/platform/android/io/flutter/view/ResourceUpdater.java deleted file mode 100644 index 30db64e14b1a8..0000000000000 --- a/shell/platform/android/io/flutter/view/ResourceUpdater.java +++ /dev/null @@ -1,374 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.view; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.res.AssetManager; -import android.os.AsyncTask; -import android.os.Bundle; -import android.util.Log; -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.Math; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Date; -import java.util.Scanner; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.zip.CRC32; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import org.json.JSONException; -import org.json.JSONObject; - -public final class ResourceUpdater { - private static final String TAG = "ResourceUpdater"; - - private static final int BUFFER_SIZE = 16 * 1024; - - // Controls when to check if a new patch is available for download, and start downloading. - // Note that by default the application will not block to wait for the download to finish. - // Patches are downloaded in the background, but the developer can also use [InstallMode] - // to control whether to block on download completion, in order to install patches sooner. - enum DownloadMode { - // Check for and download patch on application restart (but not necessarily apply it). - // This is the default setting which will also check for new patches least frequently. - ON_RESTART, - - // Check for and download patch on application resume (but not necessarily apply it). - // By definition, this setting will check for new patches both on restart and resume. - ON_RESUME - } - - // Controls when to check that a new patch has been downloaded and needs to be applied. - enum InstallMode { - // Wait for next application restart before applying downloaded patch. With this - // setting, the application will not block to wait for patch download to finish. - // The application can be restarted later either by the user, or by the system, - // for any reason, at which point the newly downloaded patch will get applied. - // This is the default setting, and is the least disruptive way to apply patches. - ON_NEXT_RESTART, - - // Apply patch as soon as it's downloaded. This will block to wait for new patch - // download to finish, and will immediately apply it. This setting increases the - // urgency with which patches are installed, but may also affect startup latency. - // For now, this setting is only effective when download happens during restart. - // Patches downloaded during resume will not get installed immediately as that - // requires force restarting the app (which might be implemented in the future). - IMMEDIATE - } - - /// Lock that prevents replacement of the install file by the downloader - /// while this file is being extracted, since these can happen in parallel. - Lock getInstallationLock() { - return installationLock; - } - - // Patch file that's fully installed and is ready to serve assets. - // This file represents the final stage in the installation process. - public File getInstalledPatch() { - return new File(context.getFilesDir().toString() + "/patch.zip"); - } - - // Patch file that's finished downloading and is ready to be installed. - // This is a separate file in order to prevent serving assets from patch - // that failed installing for any reason, such as mismatched APK version. - File getDownloadedPatch() { - return new File(getInstalledPatch().getPath() + ".install"); - } - - private class DownloadTask extends AsyncTask { - @Override - protected Void doInBackground(String... unused) { - try { - URL unresolvedURL = new URL(buildUpdateDownloadURL()); - - // Download to transient file to avoid extracting incomplete download. - File localFile = new File(getInstalledPatch().getPath() + ".download"); - - long startMillis = new Date().getTime(); - Log.i(TAG, "Checking for updates at " + unresolvedURL); - - HttpURLConnection connection = - (HttpURLConnection)unresolvedURL.openConnection(); - - long lastDownloadTime = Math.max( - getDownloadedPatch().lastModified(), - getInstalledPatch().lastModified()); - - if (lastDownloadTime != 0) { - Log.i(TAG, "Active update timestamp " + lastDownloadTime); - connection.setIfModifiedSince(lastDownloadTime); - } - - URL resolvedURL = connection.getURL(); - Log.i(TAG, "Resolved update URL " + resolvedURL); - - int responseCode = connection.getResponseCode(); - Log.i(TAG, "HTTP response code " + responseCode); - - if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) { - Log.i(TAG, "Latest update not found on server"); - return null; - } - - if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) { - Log.i(TAG, "Already have latest update"); - return null; - } - - try (InputStream input = connection.getInputStream()) { - Log.i(TAG, "Downloading update " + unresolvedURL); - try (OutputStream output = new FileOutputStream(localFile)) { - int count; - byte[] data = new byte[1024]; - while ((count = input.read(data)) != -1) { - output.write(data, 0, count); - } - - long totalMillis = new Date().getTime() - startMillis; - Log.i(TAG, "Update downloaded in " + totalMillis / 100 / 10. + "s"); - } - } - - // Wait renaming the file if extraction is in progress. - installationLock.lock(); - - try { - File updateFile = getDownloadedPatch(); - - // Graduate downloaded file as ready for installation. - if (updateFile.exists() && !updateFile.delete()) { - Log.w(TAG, "Could not delete file " + updateFile); - return null; - } - if (!localFile.renameTo(updateFile)) { - Log.w(TAG, "Could not create file " + updateFile); - return null; - } - - return null; - - } finally { - installationLock.unlock(); - } - - } catch (IOException e) { - Log.w(TAG, "Could not download update " + e.getMessage()); - return null; - } - } - } - - private final Context context; - private DownloadTask downloadTask; - private final Lock installationLock = new ReentrantLock(); - - public ResourceUpdater(Context context) { - this.context = context; - } - - private String getAPKVersion() { - try { - PackageManager packageManager = context.getPackageManager(); - PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); - return packageInfo == null ? null : Long.toString(ResourceExtractor.getVersionCode(packageInfo)); - - } catch (PackageManager.NameNotFoundException e) { - return null; - } - } - - private String buildUpdateDownloadURL() { - Bundle metaData; - try { - metaData = context.getPackageManager().getApplicationInfo( - context.getPackageName(), PackageManager.GET_META_DATA).metaData; - - } catch (PackageManager.NameNotFoundException e) { - throw new RuntimeException(e); - } - - if (metaData == null || metaData.getString("PatchServerURL") == null) { - return null; - } - - URI uri; - try { - uri = new URI(metaData.getString("PatchServerURL") + "/" + getAPKVersion() + ".zip"); - - } catch (URISyntaxException e) { - Log.w(TAG, "Invalid AndroidManifest.xml PatchServerURL: " + e.getMessage()); - return null; - } - - return uri.normalize().toString(); - } - - DownloadMode getDownloadMode() { - Bundle metaData; - try { - metaData = context.getPackageManager().getApplicationInfo( - context.getPackageName(), PackageManager.GET_META_DATA).metaData; - - } catch (PackageManager.NameNotFoundException e) { - throw new RuntimeException(e); - } - - if (metaData == null) { - return DownloadMode.ON_RESTART; - } - - String patchDownloadMode = metaData.getString("PatchDownloadMode"); - if (patchDownloadMode == null) { - return DownloadMode.ON_RESTART; - } - - try { - return DownloadMode.valueOf(patchDownloadMode); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Invalid PatchDownloadMode " + patchDownloadMode); - return DownloadMode.ON_RESTART; - } - } - - InstallMode getInstallMode() { - Bundle metaData; - try { - metaData = context.getPackageManager().getApplicationInfo( - context.getPackageName(), PackageManager.GET_META_DATA).metaData; - - } catch (PackageManager.NameNotFoundException e) { - throw new RuntimeException(e); - } - - if (metaData == null) { - return InstallMode.ON_NEXT_RESTART; - } - - String patchInstallMode = metaData.getString("PatchInstallMode"); - if (patchInstallMode == null) { - return InstallMode.ON_NEXT_RESTART; - } - - try { - return InstallMode.valueOf(patchInstallMode); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Invalid PatchInstallMode " + patchInstallMode); - return InstallMode.ON_NEXT_RESTART; - } - } - - /// Returns manifest JSON from ZIP file, or null if not found. - public JSONObject readManifest(File updateFile) { - if (!updateFile.exists()) { - return null; - } - - try { - ZipFile zipFile = new ZipFile(updateFile); - ZipEntry entry = zipFile.getEntry("manifest.json"); - if (entry == null) { - Log.w(TAG, "Invalid update file: " + updateFile); - return null; - } - - // Read and parse the entire JSON file as single operation. - Scanner scanner = new Scanner(zipFile.getInputStream(entry)); - return new JSONObject(scanner.useDelimiter("\\A").next()); - - } catch (IOException | JSONException e) { - Log.w(TAG, "Invalid update file: " + e); - return null; - } - } - - /// Returns true if the patch file was indeed built for this APK. - public boolean validateManifest(JSONObject manifest) { - if (manifest == null) { - return false; - } - - String buildNumber = manifest.optString("buildNumber", null); - if (buildNumber == null) { - Log.w(TAG, "Invalid update manifest: missing buildNumber"); - return false; - } - - if (!buildNumber.equals(getAPKVersion())) { - Log.w(TAG, "Outdated update file for build " + getAPKVersion()); - return false; - } - - String baselineChecksum = manifest.optString("baselineChecksum", null); - if (baselineChecksum == null) { - Log.w(TAG, "Invalid update manifest: missing baselineChecksum"); - return false; - } - - CRC32 checksum = new CRC32(); - String[] checksumFiles = { - "isolate_snapshot_data", - "isolate_snapshot_instr", - "flutter_assets/isolate_snapshot_data", - }; - for (String fn : checksumFiles) { - AssetManager manager = context.getResources().getAssets(); - try (InputStream is = manager.open(fn)) { - int count = 0; - byte[] buffer = new byte[BUFFER_SIZE]; - while ((count = is.read(buffer, 0, BUFFER_SIZE)) != -1) { - checksum.update(buffer, 0, count); - } - } catch (IOException e) { - // Skip missing files. - } - } - - if (!baselineChecksum.equals(String.valueOf(checksum.getValue()))) { - Log.w(TAG, "Mismatched update file for APK"); - return false; - } - - return true; - } - - void startUpdateDownloadOnce() { - if (downloadTask != null) { - return; - } - downloadTask = new DownloadTask(); - downloadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - void waitForDownloadCompletion() { - if (downloadTask == null) { - return; - } - try { - downloadTask.get(); - downloadTask = null; - } catch (CancellationException e) { - Log.w(TAG, "Download cancelled: " + e.getMessage()); - return; - } catch (ExecutionException e) { - Log.w(TAG, "Download exception: " + e.getMessage()); - return; - } catch (InterruptedException e) { - Log.w(TAG, "Download interrupted: " + e.getMessage()); - return; - } - } -}