Merge branch 'final-1.1-fixes' into 'master'
Final 1.1 fixes Closes #1310 See merge request fdroid/fdroidclient!658
This commit is contained in:
commit
51b22d50b6
@ -25,7 +25,6 @@ import android.content.pm.PackageInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.v4.content.FileProvider;
|
||||
|
||||
import org.fdroid.fdroid.BuildConfig;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.SanitizedFile;
|
||||
@ -34,7 +33,8 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This class has helper methods for preparing apks for installation.
|
||||
* Helper methods for preparing APKs and arbitrary files for installation,
|
||||
* either locally or for sending via bluetooth.
|
||||
* <p/>
|
||||
* APK handling for installations:
|
||||
* 1. APKs are downloaded into a cache directory that is either created on SD card
|
||||
@ -56,34 +56,44 @@ public class ApkFileProvider extends FileProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the APK into private data directory of F-Droid and returns a "file" or "content" Uri
|
||||
* to be used for installation.
|
||||
* Copies the APK into private data directory of F-Droid and returns a
|
||||
* {@code file://} or {@code content://} URI to be used for the
|
||||
* actual installation process. Only APKs will ever use a {@code content://}
|
||||
* URI, any other file will always use a {@code file://} URI since F-Droid
|
||||
* itself handles their whole installation process.
|
||||
*/
|
||||
public static Uri getSafeUri(Context context, Uri localApkUri, Apk expectedApk, boolean useContentUri)
|
||||
public static Uri getSafeUri(Context context, Uri localApkUri, Apk expectedApk)
|
||||
throws IOException {
|
||||
File apkFile = new File(localApkUri.getPath());
|
||||
SanitizedFile tempApkFile = ApkCache.copyApkFromCacheToFiles(context, apkFile, expectedApk);
|
||||
return getSafeUri(context, tempApkFile, useContentUri);
|
||||
return getSafeUri(context, tempApkFile,
|
||||
Build.VERSION.SDK_INT >= 24 && expectedApk.isApk());
|
||||
|
||||
}
|
||||
|
||||
private static Uri getSafeUri(Context context, SanitizedFile tempApkFile, boolean useContentUri) {
|
||||
/**
|
||||
* Return a {@link Uri} for all install processes to install this package
|
||||
* from. This supports APKs and all other supported files. It also
|
||||
* supports all installation methods, e.g. default, privileged, etc.
|
||||
* It can return either a {@code content://} or {@code file://} URI.
|
||||
* <p>
|
||||
* APKs need to be world readable, so that the Android system installer
|
||||
* is able to read it. Saving it into external storage to send it to the
|
||||
* installer have access is insecure, because apps with permission to write
|
||||
* to the external storage can overwrite the app between F-Droid asking for
|
||||
* it to be installed and the installer actually installing it.
|
||||
*/
|
||||
private static Uri getSafeUri(Context context, SanitizedFile tempFile, boolean useContentUri) {
|
||||
if (useContentUri) {
|
||||
// return a content Uri using support libs FileProvider
|
||||
Uri apkUri = getUriForFile(context, AUTHORITY, tempApkFile);
|
||||
Uri apkUri = getUriForFile(context, AUTHORITY, tempFile);
|
||||
context.grantUriPermission("org.fdroid.fdroid.privileged", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
context.grantUriPermission("com.android.bluetooth", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
return apkUri;
|
||||
}
|
||||
|
||||
// Need the apk to be world readable, so that the installer is able to read it.
|
||||
// Note that saving it into external storage for the purpose of letting the installer
|
||||
// have access is insecure, because apps with permission to write to the external
|
||||
// storage can overwrite the app between F-Droid asking for it to be installed and
|
||||
// the installer actually installing it.
|
||||
tempApkFile.setReadable(true, false);
|
||||
tempFile.setReadable(true, false);
|
||||
|
||||
return Uri.fromFile(tempApkFile);
|
||||
return Uri.fromFile(tempFile);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,8 +24,6 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
|
||||
/**
|
||||
@ -82,10 +80,4 @@ public class DefaultInstaller extends Installer {
|
||||
protected boolean isUnattended() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsContentUri() {
|
||||
// Android N only supports content Uris
|
||||
return Build.VERSION.SDK_INT >= 24;
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.fdroid.fdroid.BuildConfig;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.privileged.install.InstallExtensionDialogActivity;
|
||||
@ -33,11 +32,16 @@ import java.io.File;
|
||||
|
||||
/**
|
||||
* Special Installer that is only useful to install the Privileged Extension apk
|
||||
* as a privileged app into the system partition of Android.
|
||||
* as a privileged app into the system partition of Android. It is deprecated
|
||||
* because it cannot work on Android versions newer than {@code android-20} or so,
|
||||
* due to increased SELinux enforcement that restricts what even root can do.
|
||||
* <p/>
|
||||
* This is installer requires user interaction and thus install/uninstall directly
|
||||
* return PendingIntents.
|
||||
*
|
||||
* @see <a href="https://www.androidauthority.com/chainfire-rooting-android-lollipop-541458/">Chainfire talks Android Lollipop and the future of rooting</a>
|
||||
*/
|
||||
@Deprecated
|
||||
public class ExtensionInstaller extends Installer {
|
||||
|
||||
ExtensionInstaller(Context context, Apk apk) {
|
||||
@ -94,9 +98,4 @@ public class ExtensionInstaller extends Installer {
|
||||
protected boolean isUnattended() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsContentUri() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -85,10 +85,4 @@ public class FileInstaller extends Installer {
|
||||
protected boolean isUnattended() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsContentUri() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -30,8 +30,6 @@ import android.os.Build;
|
||||
import android.os.PatternMatcher;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.ApkProvider;
|
||||
@ -195,7 +193,7 @@ public abstract class Installer {
|
||||
sendBroadcastUninstall(action, pendingIntent, null);
|
||||
}
|
||||
|
||||
void sendBroadcastUninstall(String action, PendingIntent pendingIntent, String errorMessage) {
|
||||
private void sendBroadcastUninstall(String action, PendingIntent pendingIntent, String errorMessage) {
|
||||
Uri uri = Uri.fromParts("package", apk.packageName, null);
|
||||
|
||||
Intent intent = new Intent(action);
|
||||
@ -236,20 +234,29 @@ public abstract class Installer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Install apk
|
||||
* Install apk given the URI that points to the local APK file, and the
|
||||
* download URI to identify which session this belongs to. This first
|
||||
* moves the APK file to private directory for the installation process
|
||||
* to read from. Then the hash of the APK is checked against the
|
||||
* {@link Apk} instance provided when this {@code Installer} object was
|
||||
* instantiated. The list of permissions in the APK file and the
|
||||
* {@code Apk} instance are compared, if they do not match, then the user
|
||||
* is prompted with the system installer dialog, which shows all the
|
||||
* permissions that the APK is requesting.
|
||||
*
|
||||
* @param localApkUri points to the local copy of the APK to be installed
|
||||
* @param downloadUri serves as the unique ID for all actions related to the
|
||||
* installation of that specific APK
|
||||
* @see InstallManagerService
|
||||
* @see <a href="https://issuetracker.google.com/issues/37091886">ACTION_INSTALL_PACKAGE Fails For Any Possible Uri</a>
|
||||
*/
|
||||
public void installPackage(Uri localApkUri, Uri downloadUri) {
|
||||
Uri sanitizedUri;
|
||||
|
||||
try {
|
||||
// move apk file to private directory for installation and check hash
|
||||
sanitizedUri = ApkFileProvider.getSafeUri(context, localApkUri, apk, supportsContentUri());
|
||||
sanitizedUri = ApkFileProvider.getSafeUri(context, localApkUri, apk);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, e.getMessage(), e);
|
||||
Utils.debugLog(TAG, e.getMessage(), e);
|
||||
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage());
|
||||
return;
|
||||
}
|
||||
@ -259,7 +266,7 @@ public abstract class Installer {
|
||||
ApkVerifier apkVerifier = new ApkVerifier(context, localApkUri, apk);
|
||||
apkVerifier.verifyApk();
|
||||
} catch (ApkVerifier.ApkVerificationException e) {
|
||||
Log.e(TAG, e.getMessage(), e);
|
||||
Utils.debugLog(TAG, e.getMessage(), e);
|
||||
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage());
|
||||
return;
|
||||
} catch (ApkVerifier.ApkPermissionUnequalException e) {
|
||||
@ -267,17 +274,10 @@ public abstract class Installer {
|
||||
// and an unattended installer is used, a wrong permission screen
|
||||
// has been shown, thus fallback to AOSP DefaultInstaller!
|
||||
if (isUnattended()) {
|
||||
Log.e(TAG, e.getMessage(), e);
|
||||
Log.e(TAG, "Falling back to AOSP DefaultInstaller!");
|
||||
Utils.debugLog(TAG, e.getMessage(), e);
|
||||
Utils.debugLog(TAG, "Falling back to AOSP DefaultInstaller!");
|
||||
DefaultInstaller defaultInstaller = new DefaultInstaller(context, apk);
|
||||
// https://code.google.com/p/android/issues/detail?id=205827
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
// content scheme for N and above
|
||||
defaultInstaller.installPackageInternal(sanitizedUri, downloadUri);
|
||||
} else {
|
||||
// file scheme for below N
|
||||
defaultInstaller.installPackageInternal(localApkUri, downloadUri);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -298,10 +298,4 @@ public abstract class Installer {
|
||||
* uninstall activities, without the system enforcing a user prompt.
|
||||
*/
|
||||
protected abstract boolean isUnattended();
|
||||
|
||||
/**
|
||||
* @return true if the Installer supports content Uris and not just file Uris
|
||||
*/
|
||||
protected abstract boolean supportsContentUri();
|
||||
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
@ -404,9 +403,4 @@ public class PrivilegedInstaller extends Installer {
|
||||
protected boolean isUnattended() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsContentUri() {
|
||||
return Build.VERSION.SDK_INT >= 24;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user