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.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.support.v4.content.FileProvider;
|
import android.support.v4.content.FileProvider;
|
||||||
|
|
||||||
import org.fdroid.fdroid.BuildConfig;
|
import org.fdroid.fdroid.BuildConfig;
|
||||||
import org.fdroid.fdroid.data.Apk;
|
import org.fdroid.fdroid.data.Apk;
|
||||||
import org.fdroid.fdroid.data.SanitizedFile;
|
import org.fdroid.fdroid.data.SanitizedFile;
|
||||||
@ -34,7 +33,8 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
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/>
|
* <p/>
|
||||||
* APK handling for installations:
|
* APK handling for installations:
|
||||||
* 1. APKs are downloaded into a cache directory that is either created on SD card
|
* 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
|
* Copies the APK into private data directory of F-Droid and returns a
|
||||||
* to be used for installation.
|
* {@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 {
|
throws IOException {
|
||||||
File apkFile = new File(localApkUri.getPath());
|
File apkFile = new File(localApkUri.getPath());
|
||||||
SanitizedFile tempApkFile = ApkCache.copyApkFromCacheToFiles(context, apkFile, expectedApk);
|
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) {
|
if (useContentUri) {
|
||||||
// return a content Uri using support libs FileProvider
|
Uri apkUri = getUriForFile(context, AUTHORITY, tempFile);
|
||||||
Uri apkUri = getUriForFile(context, AUTHORITY, tempApkFile);
|
|
||||||
context.grantUriPermission("org.fdroid.fdroid.privileged", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
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);
|
context.grantUriPermission("com.android.bluetooth", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
return apkUri;
|
return apkUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need the apk to be world readable, so that the installer is able to read it.
|
tempFile.setReadable(true, false);
|
||||||
// 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);
|
|
||||||
|
|
||||||
return Uri.fromFile(tempApkFile);
|
return Uri.fromFile(tempFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,6 @@ import android.app.PendingIntent;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.data.Apk;
|
import org.fdroid.fdroid.data.Apk;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,10 +80,4 @@ public class DefaultInstaller extends Installer {
|
|||||||
protected boolean isUnattended() {
|
protected boolean isUnattended() {
|
||||||
return false;
|
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.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import org.fdroid.fdroid.BuildConfig;
|
import org.fdroid.fdroid.BuildConfig;
|
||||||
import org.fdroid.fdroid.data.Apk;
|
import org.fdroid.fdroid.data.Apk;
|
||||||
import org.fdroid.fdroid.privileged.install.InstallExtensionDialogActivity;
|
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
|
* 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/>
|
* <p/>
|
||||||
* This is installer requires user interaction and thus install/uninstall directly
|
* This is installer requires user interaction and thus install/uninstall directly
|
||||||
* return PendingIntents.
|
* 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 {
|
public class ExtensionInstaller extends Installer {
|
||||||
|
|
||||||
ExtensionInstaller(Context context, Apk apk) {
|
ExtensionInstaller(Context context, Apk apk) {
|
||||||
@ -94,9 +98,4 @@ public class ExtensionInstaller extends Installer {
|
|||||||
protected boolean isUnattended() {
|
protected boolean isUnattended() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean supportsContentUri() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -85,10 +85,4 @@ public class FileInstaller extends Installer {
|
|||||||
protected boolean isUnattended() {
|
protected boolean isUnattended() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean supportsContentUri() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -30,8 +30,6 @@ import android.os.Build;
|
|||||||
import android.os.PatternMatcher;
|
import android.os.PatternMatcher;
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.data.Apk;
|
import org.fdroid.fdroid.data.Apk;
|
||||||
import org.fdroid.fdroid.data.ApkProvider;
|
import org.fdroid.fdroid.data.ApkProvider;
|
||||||
@ -144,7 +142,7 @@ public abstract class Installer {
|
|||||||
PackageManager pm = context.getPackageManager();
|
PackageManager pm = context.getPackageManager();
|
||||||
if (Build.VERSION.SDK_INT >= 24 && (
|
if (Build.VERSION.SDK_INT >= 24 && (
|
||||||
pm.getInstallerPackageName(apk.packageName).equals("com.android.packageinstaller")
|
pm.getInstallerPackageName(apk.packageName).equals("com.android.packageinstaller")
|
||||||
|| pm.getInstallerPackageName(apk.packageName).equals("com.google.android.packageinstaller"))) {
|
|| pm.getInstallerPackageName(apk.packageName).equals("com.google.android.packageinstaller"))) {
|
||||||
Utils.debugLog(TAG, "Falling back to default installer for uninstall");
|
Utils.debugLog(TAG, "Falling back to default installer for uninstall");
|
||||||
Intent intent = new Intent(context, DefaultInstallerActivity.class);
|
Intent intent = new Intent(context, DefaultInstallerActivity.class);
|
||||||
intent.setAction(DefaultInstallerActivity.ACTION_UNINSTALL_PACKAGE);
|
intent.setAction(DefaultInstallerActivity.ACTION_UNINSTALL_PACKAGE);
|
||||||
@ -195,7 +193,7 @@ public abstract class Installer {
|
|||||||
sendBroadcastUninstall(action, pendingIntent, null);
|
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);
|
Uri uri = Uri.fromParts("package", apk.packageName, null);
|
||||||
|
|
||||||
Intent intent = new Intent(action);
|
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 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
|
* @param downloadUri serves as the unique ID for all actions related to the
|
||||||
* installation of that specific APK
|
* 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) {
|
public void installPackage(Uri localApkUri, Uri downloadUri) {
|
||||||
Uri sanitizedUri;
|
Uri sanitizedUri;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// move apk file to private directory for installation and check hash
|
sanitizedUri = ApkFileProvider.getSafeUri(context, localApkUri, apk);
|
||||||
sanitizedUri = ApkFileProvider.getSafeUri(context, localApkUri, apk, supportsContentUri());
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, e.getMessage(), e);
|
Utils.debugLog(TAG, e.getMessage(), e);
|
||||||
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage());
|
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -259,7 +266,7 @@ public abstract class Installer {
|
|||||||
ApkVerifier apkVerifier = new ApkVerifier(context, localApkUri, apk);
|
ApkVerifier apkVerifier = new ApkVerifier(context, localApkUri, apk);
|
||||||
apkVerifier.verifyApk();
|
apkVerifier.verifyApk();
|
||||||
} catch (ApkVerifier.ApkVerificationException e) {
|
} catch (ApkVerifier.ApkVerificationException e) {
|
||||||
Log.e(TAG, e.getMessage(), e);
|
Utils.debugLog(TAG, e.getMessage(), e);
|
||||||
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage());
|
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage());
|
||||||
return;
|
return;
|
||||||
} catch (ApkVerifier.ApkPermissionUnequalException e) {
|
} catch (ApkVerifier.ApkPermissionUnequalException e) {
|
||||||
@ -267,17 +274,10 @@ public abstract class Installer {
|
|||||||
// and an unattended installer is used, a wrong permission screen
|
// and an unattended installer is used, a wrong permission screen
|
||||||
// has been shown, thus fallback to AOSP DefaultInstaller!
|
// has been shown, thus fallback to AOSP DefaultInstaller!
|
||||||
if (isUnattended()) {
|
if (isUnattended()) {
|
||||||
Log.e(TAG, e.getMessage(), e);
|
Utils.debugLog(TAG, e.getMessage(), e);
|
||||||
Log.e(TAG, "Falling back to AOSP DefaultInstaller!");
|
Utils.debugLog(TAG, "Falling back to AOSP DefaultInstaller!");
|
||||||
DefaultInstaller defaultInstaller = new DefaultInstaller(context, apk);
|
DefaultInstaller defaultInstaller = new DefaultInstaller(context, apk);
|
||||||
// https://code.google.com/p/android/issues/detail?id=205827
|
defaultInstaller.installPackageInternal(sanitizedUri, downloadUri);
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -298,10 +298,4 @@ public abstract class Installer {
|
|||||||
* uninstall activities, without the system enforcing a user prompt.
|
* uninstall activities, without the system enforcing a user prompt.
|
||||||
*/
|
*/
|
||||||
protected abstract boolean isUnattended();
|
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.ServiceConnection;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@ -404,9 +403,4 @@ public class PrivilegedInstaller extends Installer {
|
|||||||
protected boolean isUnattended() {
|
protected boolean isUnattended() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean supportsContentUri() {
|
|
||||||
return Build.VERSION.SDK_INT >= 24;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user