Skip to content

Commit

Permalink
Remove untrusted sources installation flow.
Browse files Browse the repository at this point in the history
This model of installation involved a user having to lower the security level
of their device to allow apps to install APKs. It was only used for developer
and preview purposes. It's no longer necessary so clean things up.

BUG=708274

Review-Url: https://codereview.chromium.org/2791983003
Cr-Commit-Position: refs/heads/master@{#462462}
  • Loading branch information
yfriedman authored and Commit bot committed Apr 6, 2017
1 parent 9ac1d89 commit a8d6359
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 529 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

package org.chromium.chrome.browser.webapps;

import android.content.Context;
import android.os.StrictMode;
import android.provider.Settings;

import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
Expand Down Expand Up @@ -37,6 +35,8 @@ public static void init() {

public static void initForTesting(boolean enabled) {
sEnabledForTesting = enabled;
sGooglePlayInstallState = enabled ? GooglePlayInstallState.SUPPORTED
: GooglePlayInstallState.NO_PLAY_SERVICES;
}

public static boolean isEnabled() {
Expand All @@ -47,13 +47,7 @@ public static boolean isEnabled() {

// Returns whether updating the WebAPK is enabled.
public static boolean areUpdatesEnabled() {
if (!isEnabled()) return false;

// Updating a WebAPK without going through Google Play requires "installation from unknown
// sources" to be enabled. It is confusing for a user to see a dialog asking them to enable
// "installation from unknown sources" when they are in the middle of using the WebAPK (as
// opposed to after requesting to add a WebAPK to the homescreen).
return installingFromUnknownSourcesAllowed() || canUseGooglePlayToInstallWebApk();
return canInstallWebApk();
}

/** Computes the GooglePlayInstallState. */
Expand All @@ -73,23 +67,10 @@ private static int computeGooglePlayInstallState() {
return GooglePlayInstallState.SUPPORTED;
}

/**
* Returns whether installing WebAPKs from Google Play is possible.
* If {@link sCanUseGooglePlayInstall} hasn't been set yet, it returns false immediately and
* calls the Google Play Install API to update {@link sCanUseGooglePlayInstall} asynchronously.
*/
private static boolean canUseGooglePlayToInstallWebApk() {
return getGooglePlayInstallState() == GooglePlayInstallState.SUPPORTED;
}

/**
* Returns whether installing WebAPKs is possible either from "unknown resources" or Google
* Play.
*/
/** Returns whether installing WebAPKs is possible. */
@CalledByNative
private static boolean canInstallWebApk() {
return isEnabled()
&& (canUseGooglePlayToInstallWebApk() || nativeCanInstallFromUnknownSources());
return isEnabled() && getGooglePlayInstallState() == GooglePlayInstallState.SUPPORTED;
}

@CalledByNative
Expand Down Expand Up @@ -136,21 +117,5 @@ public static void cacheEnabledStateForNextLaunch() {
}
}

/**
* Returns whether the user has enabled installing apps from sources other than the Google Play
* Store.
*/
private static boolean installingFromUnknownSourcesAllowed() {
Context applicationContext = ContextUtils.getApplicationContext();
try {
return Settings.Secure.getInt(applicationContext.getContentResolver(),
Settings.Secure.INSTALL_NON_MARKET_APPS)
== 1;
} catch (Settings.SettingNotFoundException e) {
return false;
}
}

private static native boolean nativeCanLaunchRendererInWebApkProcess();
private static native boolean nativeCanInstallFromUnknownSources();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,14 @@

package org.chromium.chrome.browser.webapps;

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Looper;

import org.chromium.base.ApplicationState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.Callback;
import org.chromium.base.ContentUriUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.browser.AppHooks;
import org.chromium.chrome.browser.ShortcutHelper;
import org.chromium.chrome.browser.banners.InstallerDelegate;
import org.chromium.chrome.browser.metrics.WebApkUma;
import org.chromium.chrome.browser.util.IntentUtils;

import java.io.File;

/**
* Java counterpart to webapk_installer.h
Expand All @@ -33,27 +21,15 @@
public class WebApkInstaller {
private static final String TAG = "WebApkInstaller";

/** The WebAPK's package name. */
private String mWebApkPackageName;

/** Monitors for application state changes. */
private ApplicationStatus.ApplicationStateListener mListener;

/** Monitors installation progress. */
private InstallerDelegate mInstallTask;

/** Whether a homescreen shortcut should be added on success. */
private boolean mAddHomescreenShortcut;

/** Weak pointer to the native WebApkInstaller. */
private long mNativePointer;

/** Talks to Google Play to install WebAPKs. */
private GooglePlayWebApkInstallDelegate mGooglePlayWebApkInstallDelegate;
private final GooglePlayWebApkInstallDelegate mInstallDelegate;

private WebApkInstaller(long nativePtr) {
mNativePointer = nativePtr;
mGooglePlayWebApkInstallDelegate = AppHooks.get().getGooglePlayWebApkInstallDelegate();
mInstallDelegate = AppHooks.get().getGooglePlayWebApkInstallDelegate();
}

@CalledByNative
Expand All @@ -63,50 +39,19 @@ private static WebApkInstaller create(long nativePtr) {

@CalledByNative
private void destroy() {
if (mListener != null) {
ApplicationStatus.unregisterApplicationStateListener(mListener);
}
mListener = null;
mNativePointer = 0;
}

/**
* Installs WebAPK via "unsigned sources" using APK downloaded to {@link filePath}.
* @param filePath File to install.
* @param packageName Package name to install WebAPK at.
*/
@CalledByNative
private void installDownloadedWebApkAsync(String filePath, String packageName) {
mAddHomescreenShortcut = true;
mWebApkPackageName = packageName;

// Start monitoring the installation.
PackageManager packageManager = ContextUtils.getApplicationContext().getPackageManager();
mInstallTask = new InstallerDelegate(Looper.getMainLooper(), packageManager,
createInstallerDelegateObserver(), mWebApkPackageName);
mInstallTask.start();
// Start monitoring the application state changes.
mListener = createApplicationStateListener();
ApplicationStatus.registerApplicationStateListener(mListener);

// Notify native only if the intent could not be delivered. If the intent was delivered
// successfully, notify native once InstallerDelegate has determined whether the install
// was successful.
if (!installOrUpdateDownloadedWebApkImpl(filePath)) {
notify(WebApkInstallResult.FAILURE);
}
}

/**
* Installs a WebAPK from Google Play and monitors the installation.
* Installs a WebAPK and monitors the installation.
* @param packageName The package name of the WebAPK to install.
* @param version The version of WebAPK to install.
* @param title The title of the WebAPK to display during installation.
* @param token The token from WebAPK Server.
* @param url The start URL of the WebAPK to install.
*/
@CalledByNative
private void installWebApkFromGooglePlayAsync(
private void installWebApkAsync(
String packageName, int version, String title, String token, String url) {
// Check whether the WebAPK package is already installed. The WebAPK may have been installed
// by another Chrome version (e.g. Chrome Dev). We have to do this check because the Play
Expand All @@ -116,7 +61,7 @@ private void installWebApkFromGooglePlayAsync(
return;
}

if (mGooglePlayWebApkInstallDelegate == null) {
if (mInstallDelegate == null) {
notify(WebApkInstallResult.FAILURE);
WebApkUma.recordGooglePlayInstallResult(
WebApkUma.GOOGLE_PLAY_INSTALL_FAILED_NO_DELEGATE);
Expand All @@ -129,53 +74,27 @@ public void onResult(Integer result) {
WebApkInstaller.this.notify(result);
}
};
mGooglePlayWebApkInstallDelegate.installAsync(
packageName, version, title, token, url, callback);
mInstallDelegate.installAsync(packageName, version, title, token, url, callback);
}

private void notify(@WebApkInstallResult.WebApkInstallResultEnum int result) {
if (mListener != null) {
ApplicationStatus.unregisterApplicationStateListener(mListener);
mListener = null;
}
mInstallTask = null;
if (mNativePointer != 0) {
nativeOnInstallFinished(mNativePointer, result);
}
if (mAddHomescreenShortcut && result == WebApkInstallResult.SUCCESS) {
ShortcutHelper.addWebApkShortcut(
ContextUtils.getApplicationContext(), mWebApkPackageName);
}
}

/**
* Updates WebAPK via "unsigned sources" using APK downloaded to {@link filePath}.
* @param filePath File to update.
*/
@CalledByNative
private void updateUsingDownloadedWebApkAsync(String filePath) {
if (!installOrUpdateDownloadedWebApkImpl(filePath)) {
notify(WebApkInstallResult.FAILURE);
return;
}

// We can't use InstallerDelegate to detect whether updates are successful. If there was no
// error in delivering the intent, assume that the update will be successful.
notify(WebApkInstallResult.SUCCESS);
}

/**
* Updates a WebAPK using Google Play.
* Updates a WebAPK installation.
* @param packageName The package name of the WebAPK to install.
* @param version The version of WebAPK to install.
* @param title The title of the WebAPK to display during installation.
* @param token The token from WebAPK Server.
* @param url The start URL of the WebAPK to install.
*/
@CalledByNative
private void updateAsyncFromGooglePlay(
private void updateAsync(
String packageName, int version, String title, String token, String url) {
if (mGooglePlayWebApkInstallDelegate == null) {
if (mInstallDelegate == null) {
notify(WebApkInstallResult.FAILURE);
return;
}
Expand All @@ -186,65 +105,7 @@ public void onResult(Integer result) {
WebApkInstaller.this.notify(result);
}
};
mGooglePlayWebApkInstallDelegate.updateAsync(
packageName, version, title, token, url, callback);
}

/**
* Sends intent to Android to show prompt to install or update downloaded WebAPK.
* @param filePath File to install.
*/
private boolean installOrUpdateDownloadedWebApkImpl(String filePath) {
Context context = ContextUtils.getApplicationContext();
Intent intent;
File pathToInstall = new File(filePath);

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
intent = new Intent(Intent.ACTION_VIEW);
Uri fileUri = Uri.fromFile(pathToInstall);
intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} else {
Uri source = ContentUriUtils.getContentUriFromFile(pathToInstall);
intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setData(source);
}
return IntentUtils.safeStartActivity(context, intent);
}

private InstallerDelegate.Observer createInstallerDelegateObserver() {
return new InstallerDelegate.Observer() {
@Override
public void onInstallFinished(InstallerDelegate task, boolean success) {
if (mInstallTask != task) return;
// TODO(pkotwicz): Return WebApkInstallResult.PROBABLE_FAILURE if the install
// timed out.
WebApkInstaller.this.notify(
success ? WebApkInstallResult.SUCCESS : WebApkInstallResult.FAILURE);
}
};
}

private ApplicationStatus.ApplicationStateListener createApplicationStateListener() {
return new ApplicationStatus.ApplicationStateListener() {
@Override
public void onApplicationStateChange(int newState) {
if (!ApplicationStatus.hasVisibleActivities()) return;
/**
* Currently WebAPKs aren't installed by Play. A user can cancel the installation
* when the Android native installation dialog shows. The only way to detect the
* user cancelling the installation is to check whether the WebAPK is installed
* when Chrome is resumed. The monitoring of application state changes will be
* removed once WebAPKs are installed by Play.
*/
if (newState == ApplicationState.HAS_RUNNING_ACTIVITIES
&& !isWebApkInstalled(mWebApkPackageName)) {
WebApkInstaller.this.notify(WebApkInstallResult.PROBABLE_FAILURE);
return;
}
}
};
mInstallDelegate.updateAsync(packageName, version, title, token, url, callback);
}

private boolean isWebApkInstalled(String packageName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
private final WebApkActivity mActivity;

/** The WebappDataStorage with cached data about prior update requests. */
private WebappDataStorage mStorage;
private final WebappDataStorage mStorage;

private WebApkUpdateDataFetcher mFetcher;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;

import org.junit.Before;
Expand Down Expand Up @@ -236,7 +235,7 @@ private static ManifestData defaultManifestData() {
manifestData.name = NAME;
manifestData.shortName = SHORT_NAME;

manifestData.iconUrlToMurmur2HashMap = new HashMap<String, String>();
manifestData.iconUrlToMurmur2HashMap = new HashMap<>();
manifestData.iconUrlToMurmur2HashMap.put(ICON_URL, ICON_MURMUR2_HASH);

manifestData.bestIconUrl = ICON_URL;
Expand Down Expand Up @@ -331,9 +330,6 @@ public void setUp() {
ChromeWebApkHost.initForTesting(true);

registerWebApk(defaultManifestData(), WebApkVersion.CURRENT_SHELL_APK_VERSION);
Settings.Secure.putInt(RuntimeEnvironment.application.getContentResolver(),
Settings.Secure.INSTALL_NON_MARKET_APPS, 1);

mClock = new MockClock();
WebappDataStorage.setClockForTests(mClock);

Expand Down
7 changes: 1 addition & 6 deletions chrome/browser/android/shortcut_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,7 @@ void ShortcutHelper::InstallWebApkWithSkBitmap(
const WebApkInstallService::FinishCallback& callback) {
WebApkInstallService::Get(web_contents->GetBrowserContext())
->InstallAsync(info, icon_bitmap, callback);
// Don't record metric for users who install WebAPKs via "unsigned sources"
// flow.
if (ChromeWebApkHost::GetGooglePlayInstallState() ==
GooglePlayInstallState::SUPPORTED) {
webapk::TrackGooglePlayInstallState(GooglePlayInstallState::SUPPORTED);
}
webapk::TrackGooglePlayInstallState(GooglePlayInstallState::SUPPORTED);
}

void ShortcutHelper::ShowWebApkInstallInProgressToast() {
Expand Down
9 changes: 0 additions & 9 deletions chrome/browser/android/webapk/chrome_webapk_host.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,3 @@ jboolean CanLaunchRendererInWebApkProcess(
chrome::android::kImprovedA2HS, kLaunchRendererInWebApkProcess) ==
"true";
}

// static
jboolean CanInstallFromUnknownSources(
JNIEnv* env,
const base::android::JavaParamRef<jclass>& clazz) {
return base::FeatureList::GetInstance()->IsFeatureOverriddenFromCommandLine(
chrome::android::kImprovedA2HS.name,
base::FeatureList::OVERRIDE_ENABLE_FEATURE);
}
3 changes: 1 addition & 2 deletions chrome/browser/android/webapk/chrome_webapk_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ class ChromeWebApkHost {
// Registers JNI hooks.
static bool Register(JNIEnv* env);

// Returns whether installing WebApk is possible either from "unknown sources"
// or Google Play.
// Returns whether installing WebApk is possible.
static bool CanInstallWebApk();

// Returns the state of installing a WebAPK from Google Play.
Expand Down
Loading

0 comments on commit a8d6359

Please sign in to comment.