blob: c129ce772c0fff5854f7c5391bf583880a1ac82f [file] [log] [blame]
// Copyright 2019 The Chromium 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 org.chromium.components.module_installer.engine;
import android.content.Context;
import android.content.pm.PackageManager;
import com.google.android.play.core.splitcompat.SplitCompat;
import com.google.android.play.core.splitcompat.ingestion.Verifier;
import org.chromium.base.BuildInfo;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.task.AsyncTask;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Engine that looks for module APKs on the device's disk instead of invoking the Play core API to
* install a feature module. This backend is used for testing purposes where the module is not
* uploaded to the Play server.
*/
class FakeEngine extends SplitCompatEngine {
private static final String TAG = "FakeEngine";
private static final String MODULES_SRC_DIRECTORY_PATH = "/data/local/tmp/modules";
@Override
public void installDeferred(String moduleName) {
// This is currently not supported by fake installs.
}
/**
* Copies {MODULES_SRC_DIRECTORY_PATH}/|package name|/|moduleName|.apk to the folder where
* SplitCompat expects downloaded modules to be. Then calls SplitCompat to emulate the module.
*
* We copy the module so that this works on non-rooted devices. The path SplitCompat expects
* module to be is not accessible without rooting.
*/
@Override
public void install(String moduleName, InstallListener listener) {
ThreadUtils.assertOnUiThread();
new AsyncTask<Boolean>() {
@Override
protected Boolean doInBackground() {
return installInternal(moduleName);
}
@Override
protected void onPostExecute(Boolean success) {
notifyListener(listener, success);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private boolean installInternal(String moduleName) {
Context context = ContextUtils.getApplicationContext();
long versionCode = BuildInfo.getInstance().versionCode;
// Get list of all files at path where SplitCompat looks for downloaded modules.
// May change in future releases of the Play Core SDK.
File srcModuleDir = new File(MODULES_SRC_DIRECTORY_PATH, context.getPackageName());
if (!srcModuleDir.exists()) {
// As fallback, check the parent directory (this is the old behavior).
// TODO(huangs): Delete this fallback once catapult/.../device_utils.py is updated.
srcModuleDir = new File(MODULES_SRC_DIRECTORY_PATH);
if (!srcModuleDir.exists()) {
Log.e(TAG, "Modules source directory does not exist");
return false;
}
}
if (!srcModuleDir.canRead()) {
Log.e(TAG, "Cannot read modules source directory");
return false;
}
if (!srcModuleDir.isDirectory()) {
Log.e(TAG, "Modules source directory is not a directory");
return false;
}
File[] srcModuleFiles = srcModuleDir.listFiles();
if (srcModuleFiles == null) {
Log.e(TAG, "Cannot get list of files in modules source directory");
return false;
}
// Check if any apks for the module are actually installed.
boolean no_module_apks_installed = true;
for (File srcModuleFile : srcModuleFiles) {
// Take only source APK files of the specified module.
String srcModuleFileName = srcModuleFile.getName();
if (srcModuleFileName.endsWith(".apk") && srcModuleFileName.startsWith(moduleName)) {
// Construct destination file corresponding to each source file.
File dstModuleFile = joinPaths(context.getFilesDir().getPath(), "splitcompat",
Long.toString(versionCode), "unverified-splits", srcModuleFileName);
// NOTE: Need to give Chrome storage permission for this to work.
try {
dstModuleFile.getParentFile().mkdirs();
} catch (SecurityException e) {
Log.e(TAG, "Failed to create module dir %s", dstModuleFile.getName(), e);
return false;
}
try (FileInputStream istream = new FileInputStream(srcModuleFile);
FileOutputStream ostream = new FileOutputStream(dstModuleFile)) {
ostream.getChannel().transferFrom(
istream.getChannel(), 0, istream.getChannel().size());
if (srcModuleFileName.equals(moduleName + ".apk")) {
// Base apk of the module must be installed for install
// to be successful.
no_module_apks_installed = false;
}
} catch (RuntimeException | IOException e) {
Log.e(TAG, "Failed to install module apk %s", dstModuleFile.getName(), e);
return false;
}
}
}
if (no_module_apks_installed) {
Log.e(TAG, "Did not find any module APKs");
return false;
}
// Check that the module's signature matches Chrome's.
try {
Verifier verifier = new Verifier(context);
if (!verifier.verifySplits()) {
return false;
}
} catch (IOException | PackageManager.NameNotFoundException e) {
return false;
}
// Tell SplitCompat to do a full emulation of the module.
return SplitCompat.fullInstall(context);
}
private File joinPaths(String... paths) {
File result = new File("");
for (String path : paths) {
result = new File(result, path);
}
return result;
}
}