From cbf3133e430df47c9b81d13a462066aeac4f296b Mon Sep 17 00:00:00 2001 From: Chirayu Desai Date: Tue, 4 Jul 2017 19:30:33 +0530 Subject: [PATCH 1/5] AppProvider: Use IS instead of = for suggestedApk calculation * For non apk files, the signature column would be NULL always, and in SQLite NULL = NULL is false, but NULL IS NULL is true. See http://www.sqlite.org/lang_expr.html Operators --- app/src/main/java/org/fdroid/fdroid/data/AppProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java index c35669d07..36426fc0d 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java @@ -1023,7 +1023,7 @@ public class AppProvider extends FDroidProvider { " LEFT JOIN " + installed + " ON (" + installed + "." + InstalledAppTable.Cols.PACKAGE_ID + " = " + app + "." + Cols.PACKAGE_ID + ") " + " WHERE " + app + "." + Cols.PACKAGE_ID + " = appForThisApk." + Cols.PACKAGE_ID + " AND " + - apk + "." + ApkTable.Cols.SIGNATURE + " = COALESCE(" + installed + "." + InstalledAppTable.Cols.SIGNATURE + ", " + apk + "." + ApkTable.Cols.SIGNATURE + ") AND " + + apk + "." + ApkTable.Cols.SIGNATURE + " IS COALESCE(" + installed + "." + InstalledAppTable.Cols.SIGNATURE + ", " + apk + "." + ApkTable.Cols.SIGNATURE + ") AND " + restrictToStable + " ( " + app + "." + Cols.IS_COMPATIBLE + " = 0 OR " + apk + "." + Cols.IS_COMPATIBLE + " = 1 ) ) " + " WHERE " + Cols.UPSTREAM_VERSION_CODE + " > 0 " + restrictToApp; @@ -1069,7 +1069,7 @@ public class AppProvider extends FDroidProvider { " LEFT JOIN " + installed + " ON (" + installed + "." + InstalledAppTable.Cols.PACKAGE_ID + " = " + app + "." + Cols.PACKAGE_ID + ") " + " WHERE " + app + "." + Cols.PACKAGE_ID + " = appForThisApk." + Cols.PACKAGE_ID + " AND " + - apk + "." + ApkTable.Cols.SIGNATURE + " = COALESCE(" + installed + "." + InstalledAppTable.Cols.SIGNATURE + ", " + apk + "." + ApkTable.Cols.SIGNATURE + ") AND " + + apk + "." + ApkTable.Cols.SIGNATURE + " IS COALESCE(" + installed + "." + InstalledAppTable.Cols.SIGNATURE + ", " + apk + "." + ApkTable.Cols.SIGNATURE + ") AND " + " ( " + app + "." + Cols.IS_COMPATIBLE + " = 0 OR " + apk + "." + ApkTable.Cols.IS_COMPATIBLE + " = 1 ) ) " + " WHERE " + restrictToApps; From 0d8b0c7fd41e3db1ef885d966bc8905fe40ca9f4 Mon Sep 17 00:00:00 2001 From: Chirayu Desai Date: Tue, 20 Jun 2017 16:01:56 +0530 Subject: [PATCH 2/5] Introduce FileInstaller, a way to handle media files from F-Droid * This installer is invoked when for non-apk/media files, and copies them to an appropriate folder on the sdcard. * We also introduce a FileInstallerActivity to ask for storage permissions at runtime, as needed by Android 6.0 and above, and handle the install/uninstall process. * A toast is shown with the install path after installation. TODO: * Manage Installed Apps screen doesn't show media files. --- app/src/main/AndroidManifest.xml | 1 + .../java/org/fdroid/fdroid/AppDetails2.java | 10 +- .../java/org/fdroid/fdroid/FDroidApp.java | 18 +- .../org/fdroid/fdroid/NotificationHelper.java | 8 +- .../main/java/org/fdroid/fdroid/data/Apk.java | 57 ++++++ .../main/java/org/fdroid/fdroid/data/App.java | 46 ++++- ...DummyInstaller.java => FileInstaller.java} | 35 +++- .../installer/FileInstallerActivity.java | 179 ++++++++++++++++++ .../installer/InstallManagerService.java | 2 +- .../fdroid/installer/InstallerFactory.java | 11 +- .../views/AppDetailsRecyclerViewAdapter.java | 4 +- .../views/apps/AppListItemController.java | 2 +- .../apps/StandardAppListItemController.java | 4 +- .../fdroid/views/main/MainActivity.java | 3 +- .../fdroid/views/swap/SwapAppsView.java | 2 +- app/src/main/res/values-af/strings.xml | 1 - app/src/main/res/values-ar/strings.xml | 1 - app/src/main/res/values-ast/strings.xml | 1 - app/src/main/res/values-be/strings.xml | 1 - app/src/main/res/values-ca/strings.xml | 1 - app/src/main/res/values-da/strings.xml | 1 - app/src/main/res/values-de/strings.xml | 1 - app/src/main/res/values-eo/strings.xml | 1 - app/src/main/res/values-es/strings.xml | 1 - app/src/main/res/values-eu/strings.xml | 1 - app/src/main/res/values-fa/strings.xml | 1 - app/src/main/res/values-fr/strings.xml | 1 - app/src/main/res/values-he/strings.xml | 1 - app/src/main/res/values-id/strings.xml | 1 - app/src/main/res/values-is/strings.xml | 1 - app/src/main/res/values-ja/strings.xml | 1 - app/src/main/res/values-ml/strings.xml | 1 - app/src/main/res/values-nb/strings.xml | 1 - app/src/main/res/values-nl/strings.xml | 1 - app/src/main/res/values-pl/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values-pt-rPT/strings.xml | 1 - app/src/main/res/values-ro/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 1 - app/src/main/res/values-sc/strings.xml | 1 - app/src/main/res/values-sk/strings.xml | 1 - app/src/main/res/values-sr/strings.xml | 1 - app/src/main/res/values-sv/strings.xml | 1 - app/src/main/res/values-tr/strings.xml | 1 - app/src/main/res/values-uk/strings.xml | 1 - app/src/main/res/values-zh-rCN/strings.xml | 1 - app/src/main/res/values-zh-rTW/strings.xml | 1 - app/src/main/res/values/strings.xml | 3 +- 48 files changed, 346 insertions(+), 71 deletions(-) rename app/src/main/java/org/fdroid/fdroid/installer/{DummyInstaller.java => FileInstaller.java} (50%) create mode 100644 app/src/main/java/org/fdroid/fdroid/installer/FileInstallerActivity.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4d2de069b..f6a6a23d5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -508,6 +508,7 @@ + diff --git a/app/src/main/java/org/fdroid/fdroid/AppDetails2.java b/app/src/main/java/org/fdroid/fdroid/AppDetails2.java index 8e23089ca..d62c43e36 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppDetails2.java +++ b/app/src/main/java/org/fdroid/fdroid/AppDetails2.java @@ -310,7 +310,7 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog shareIntent.putExtra(Intent.EXTRA_SUBJECT, app.name); shareIntent.putExtra(Intent.EXTRA_TEXT, app.name + " (" + app.summary + ") - https://f-droid.org/app/" + app.packageName); - boolean showNearbyItem = app.isInstalled() && fdroidApp.bluetoothAdapter != null; + boolean showNearbyItem = app.isInstalled(getApplicationContext()) && fdroidApp.bluetoothAdapter != null; ShareChooserDialog.createChooser((CoordinatorLayout) findViewById(R.id.rootCoordinator), this, this, shareIntent, showNearbyItem); return true; } else if (item.getItemId() == R.id.action_ignore_all) { @@ -778,8 +778,12 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog Apk apk = app.installedApk; if (apk == null) { // TODO ideally, app would be refreshed immediately after install, then this - // workaround would be unnecessary - apk = getInstalledApk(); + // workaround would be unnecessary - unless it is a media file + apk = app.getMediaApkifInstalled(getApplicationContext()); + if (apk == null) { + // When the app isn't a media file - the above workaround refers to this. + apk = getInstalledApk(); + } app.installedApk = apk; } Installer installer = InstallerFactory.create(this, apk); diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java index 94cca9eef..8047e0d0a 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java @@ -39,12 +39,12 @@ import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; + import com.nostra13.universalimageloader.cache.disc.impl.LimitedAgeDiskCache; import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; -import info.guardianproject.netcipher.NetCipher; -import info.guardianproject.netcipher.proxy.OrbotHelper; + import org.acra.ACRA; import org.acra.ReportingInteractionMode; import org.acra.annotation.ReportsCrashes; @@ -55,12 +55,11 @@ import org.fdroid.fdroid.compat.PRNGFixes; import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.InstalledAppProviderService; import org.fdroid.fdroid.data.Repo; -import org.fdroid.fdroid.installer.ApkFileProvider; import org.fdroid.fdroid.data.SanitizedFile; +import org.fdroid.fdroid.installer.ApkFileProvider; import org.fdroid.fdroid.installer.InstallHistoryService; import org.fdroid.fdroid.net.ImageLoaderForUIL; import org.fdroid.fdroid.net.WifiStateChangeService; -import sun.net.www.protocol.bluetooth.Handler; import java.io.IOException; import java.net.URL; @@ -69,6 +68,10 @@ import java.net.URLStreamHandlerFactory; import java.security.Security; import java.util.List; +import info.guardianproject.netcipher.NetCipher; +import info.guardianproject.netcipher.proxy.OrbotHelper; +import sun.net.www.protocol.bluetooth.Handler; + @ReportsCrashes(mailTo = "reports@f-droid.org", mode = ReportingInteractionMode.DIALOG, reportDialogClass = org.fdroid.fdroid.acra.CrashReportActivity.class, @@ -80,6 +83,8 @@ public class FDroidApp extends Application { public static final String SYSTEM_DIR_NAME = Environment.getRootDirectory().getAbsolutePath(); + private static FDroidApp instance; + // for the local repo on this device, all static since there is only one public static volatile int port; public static volatile String ipAddressString; @@ -204,6 +209,7 @@ public class FDroidApp extends Application { @Override public void onCreate() { super.onCreate(); + instance = this; if (BuildConfig.DEBUG) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() @@ -449,4 +455,8 @@ public class FDroidApp extends Application { public static boolean isUsingTor() { return useTor; } + + public static Context getInstance() { + return instance; + } } diff --git a/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java b/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java index 84bdc76ad..00ce60cbe 100644 --- a/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java +++ b/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java @@ -248,7 +248,7 @@ class NotificationHelper { case Downloading: return app.name; case ReadyToInstall: - return context.getString(app.isInstalled() ? R.string.notification_title_single_ready_to_install_update : R.string.notification_title_single_ready_to_install); + return context.getString(app.isInstalled(context) ? R.string.notification_title_single_ready_to_install_update : R.string.notification_title_single_ready_to_install); case Installing: return app.name; case Installed: @@ -264,7 +264,7 @@ class NotificationHelper { case UpdateAvailable: return app.name; case Downloading: - return context.getString(app.isInstalled() ? R.string.notification_content_single_downloading_update : R.string.notification_content_single_downloading, app.name); + return context.getString(app.isInstalled(context) ? R.string.notification_content_single_downloading_update : R.string.notification_content_single_downloading, app.name); case ReadyToInstall: return app.name; case Installing: @@ -282,9 +282,9 @@ class NotificationHelper { case UpdateAvailable: return context.getString(R.string.notification_title_summary_update_available); case Downloading: - return context.getString(app.isInstalled() ? R.string.notification_title_summary_downloading_update : R.string.notification_title_summary_downloading); + return context.getString(app.isInstalled(context) ? R.string.notification_title_summary_downloading_update : R.string.notification_title_summary_downloading); case ReadyToInstall: - return context.getString(app.isInstalled() ? R.string.notification_title_summary_ready_to_install_update : R.string.notification_title_summary_ready_to_install); + return context.getString(app.isInstalled(context) ? R.string.notification_title_summary_ready_to_install_update : R.string.notification_title_summary_ready_to_install); case Installing: return context.getString(R.string.notification_title_summary_installing); case Installed: diff --git a/app/src/main/java/org/fdroid/fdroid/data/Apk.java b/app/src/main/java/org/fdroid/fdroid/data/Apk.java index cbbbee3ad..28c12bf29 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Apk.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Apk.java @@ -2,15 +2,21 @@ package org.fdroid.fdroid.data; import android.annotation.TargetApi; import android.content.ContentValues; +import android.content.Context; import android.content.pm.PackageInfo; import android.database.Cursor; import android.os.Build; +import android.os.Environment; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.webkit.MimeTypeMap; + import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; + import org.fdroid.fdroid.RepoXMLHandler; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Schema.ApkTable.Cols; @@ -470,4 +476,55 @@ public class Apk extends ValueObject implements Comparable, Parcelable { } requestedPermissions = set.toArray(new String[set.size()]); } + + /** + * Get the install path for a "non-apk" media file + * Defaults to {@link android.os.Environment#DIRECTORY_DOWNLOADS} + * + * @return the install path for this {@link Apk} + */ + + public File getMediaInstallPath(Context context) { + File path = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS); // Default for all other non-apk/media files + String fileExtension = MimeTypeMap.getFileExtensionFromUrl(this.getUrl()); + if (TextUtils.isEmpty(fileExtension)) return path; + MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); + String[] mimeType = mimeTypeMap.getMimeTypeFromExtension(fileExtension).split("/"); + String topLevelType; + if (mimeType.length == 0) { + topLevelType = ""; + } else { + topLevelType = mimeType[0]; + } + if ("audio".equals(topLevelType)) { + path = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_MUSIC); + } else if ("image".equals(topLevelType)) { + path = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES); + } else if ("video".equals(topLevelType)) { + path = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_MOVIES); + // TODO support OsmAnd map files, other map apps? + //} else if (mimeTypeMap.hasExtension("map")) { // OsmAnd map files + //} else if (this.apkName.matches(".*.ota_[0-9]*.zip")) { // Over-The-Air update ZIP files + } else if (this.apkName.endsWith(".zip")) { // Over-The-Air update ZIP files + path = new File(context.getApplicationInfo().dataDir + "/ota"); + } + return path; + } + + public boolean isMediaInstalled(Context context) { + return new File(this.getMediaInstallPath(context), this.apkName).isFile(); + } + + /** + * Default to assuming apk if apkName is null since that has always been + * what we had. + * @return true if this is an apk instead of a non-apk/media file + */ + public boolean isApk() { + return this.apkName == null || this.apkName.endsWith(".apk"); + } } diff --git a/app/src/main/java/org/fdroid/fdroid/data/App.java b/app/src/main/java/org/fdroid/fdroid/data/App.java index e8e7621bc..58f209a36 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/App.java +++ b/app/src/main/java/org/fdroid/fdroid/data/App.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.commons.io.filefilter.RegexFileFilter; import org.fdroid.fdroid.AppFilter; import org.fdroid.fdroid.FDroidApp; +import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols; import org.xmlpull.v1.XmlPullParser; @@ -42,6 +43,7 @@ import java.util.Date; import java.util.Enumeration; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -853,8 +855,38 @@ public class App extends ValueObject implements Comparable, Parcelable { return values; } - public boolean isInstalled() { - return installedVersionCode > 0; + public boolean isInstalled(Context context) { + return isMediaInstalled(context) || installedVersionCode > 0; + } + + public boolean isMediaInstalled(Context context) { + return getMediaApkifInstalled(context) != null; + } + + /** + * Gets the installed media apk from all the apks of this {@link App}, if any. + * + * @return The installed media {@link Apk} if it exists, null otherwise. + */ + public Apk getMediaApkifInstalled(Context context) { + // This is always null for media files. We could skip the code below completely if it wasn't + if (this.installedApk != null && !this.installedApk.isApk() && this.installedApk.isMediaInstalled(context)) { + return this.installedApk; + } + // This code comes from AppDetailsRecyclerViewAdapter + final List apks = ApkProvider.Helper.findByPackageName(context, this.packageName); + for (final Apk apk : apks) { + boolean allowByCompatability = apk.compatible || Preferences.get().showIncompatibleVersions(); + boolean allowBySig = this.installedSig == null || TextUtils.equals(this.installedSig, apk.sig); + if (allowByCompatability && allowBySig) { + if (!apk.isApk()) { + if (apk.isMediaInstalled(context)) { + return apk; + } + } + } + } + return null; } /** @@ -966,11 +998,10 @@ public class App extends ValueObject implements Comparable, Parcelable { return 0; } - /** - * System apps aren't uninstallable, only their updates are. - */ public boolean isUninstallable(Context context) { - if (this.isInstalled()) { + if (this.isMediaInstalled(context)) { + return true; + } else if (this.isInstalled(context)) { PackageManager pm = context.getPackageManager(); ApplicationInfo appInfo; try { @@ -980,8 +1011,9 @@ public class App extends ValueObject implements Comparable, Parcelable { return false; } + // System apps aren't uninstallable. final boolean isSystem = (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; - return !isSystem && this.isInstalled(); + return !isSystem && this.isInstalled(context); } else { return false; } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/DummyInstaller.java b/app/src/main/java/org/fdroid/fdroid/installer/FileInstaller.java similarity index 50% rename from app/src/main/java/org/fdroid/fdroid/installer/DummyInstaller.java rename to app/src/main/java/org/fdroid/fdroid/installer/FileInstaller.java index ee5658391..edb82acd4 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/DummyInstaller.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/FileInstaller.java @@ -19,15 +19,16 @@ package org.fdroid.fdroid.installer; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.net.Uri; import org.fdroid.fdroid.data.Apk; -public class DummyInstaller extends Installer { +public class FileInstaller extends Installer { - public DummyInstaller(Context context, Apk apk) { + public FileInstaller(Context context, Apk apk) { super(context, apk); } @@ -43,17 +44,41 @@ public class DummyInstaller extends Installer { @Override public void installPackage(Uri localApkUri, Uri downloadUri) { - // Do nothing + installPackageInternal(localApkUri, downloadUri); } @Override protected void installPackageInternal(Uri localApkUri, Uri downloadUri) { - // Do nothing + Intent installIntent = new Intent(context, FileInstallerActivity.class); + installIntent.setAction(FileInstallerActivity.ACTION_INSTALL_FILE); + installIntent.putExtra(Installer.EXTRA_DOWNLOAD_URI, downloadUri); + installIntent.putExtra(Installer.EXTRA_APK, apk); + installIntent.setData(localApkUri); + + PendingIntent installPendingIntent = PendingIntent.getActivity( + context.getApplicationContext(), + localApkUri.hashCode(), + installIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + + sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_USER_INTERACTION, + installPendingIntent); } @Override protected void uninstallPackage() { - // Do nothing + sendBroadcastUninstall(Installer.ACTION_UNINSTALL_STARTED); + + Intent uninstallIntent = new Intent(context, FileInstallerActivity.class); + uninstallIntent.setAction(FileInstallerActivity.ACTION_UNINSTALL_FILE); + uninstallIntent.putExtra(Installer.EXTRA_APK, apk); + PendingIntent uninstallPendingIntent = PendingIntent.getActivity( + context.getApplicationContext(), + apk.packageName.hashCode(), + uninstallIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + + sendBroadcastUninstall(Installer.ACTION_UNINSTALL_USER_INTERACTION, uninstallPendingIntent); } @Override diff --git a/app/src/main/java/org/fdroid/fdroid/installer/FileInstallerActivity.java b/app/src/main/java/org/fdroid/fdroid/installer/FileInstallerActivity.java new file mode 100644 index 000000000..2e51f267f --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/installer/FileInstallerActivity.java @@ -0,0 +1,179 @@ +package org.fdroid.fdroid.installer; + +import android.Manifest; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.FragmentActivity; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AlertDialog; +import android.view.ContextThemeWrapper; +import android.widget.Toast; + +import org.apache.commons.io.FileUtils; +import org.fdroid.fdroid.FDroidApp; +import org.fdroid.fdroid.R; +import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.data.Apk; + +import java.io.File; +import java.io.IOException; + +public class FileInstallerActivity extends FragmentActivity { + + private static final String TAG = "FileInstallerActivity"; + private static final int MY_PERMISSIONS_REQUEST_STORAGE = 1; + + static final String ACTION_INSTALL_FILE + = "org.fdroid.fdroid.installer.FileInstaller.action.INSTALL_PACKAGE"; + static final String ACTION_UNINSTALL_FILE + = "org.fdroid.fdroid.installer.FileInstaller.action.UNINSTALL_PACKAGE"; + + private FileInstallerActivity activity; + + // for the broadcasts + private FileInstaller installer; + + private Apk apk; + private Uri localApkUri; + private Uri downloadUri; + + private int act = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + activity = this; + Intent intent = getIntent(); + String action = intent.getAction(); + localApkUri = intent.getData(); + downloadUri = intent.getParcelableExtra(Installer.EXTRA_DOWNLOAD_URI); + apk = intent.getParcelableExtra(Installer.EXTRA_APK); + installer = new FileInstaller(this, apk); + if (ACTION_INSTALL_FILE.equals(action)) { + if (hasStoragePermission()) { + installPackage(localApkUri, downloadUri, apk); + } else { + requestPermission(); + act = 1; + } + } else if (ACTION_UNINSTALL_FILE.equals(action)) { + if (hasStoragePermission()) { + uninstallPackage(apk); + } else { + requestPermission(); + act = 2; + } + } else { + throw new IllegalStateException("Intent action not specified!"); + } + + } + + private boolean hasStoragePermission() { + return ContextCompat.checkSelfPermission(this, + Manifest.permission.WRITE_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED; + } + + private void requestPermission() { + if (!hasStoragePermission()) { + if (ActivityCompat.shouldShowRequestPermissionRationale(this, + Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + showDialog(); + } else { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + MY_PERMISSIONS_REQUEST_STORAGE); + } + } + } + + private void showDialog() { + + // hack to get theme applied (which is not automatically applied due to activity's Theme.NoDisplay + ContextThemeWrapper theme = new ContextThemeWrapper(this, FDroidApp.getCurThemeResId()); + + final AlertDialog.Builder builder = new AlertDialog.Builder(theme); + builder.setMessage(R.string.app_permission_storage) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + ActivityCompat.requestPermissions(activity, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + MY_PERMISSIONS_REQUEST_STORAGE); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + if (act == 1) { + installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED); + } else if (act == 2) { + installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED); + } + finish(); + } + }) + .create().show(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, + String[] permissions, int[] grantResults) { + switch (requestCode) { + case MY_PERMISSIONS_REQUEST_STORAGE: + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (act == 1) { + installPackage(localApkUri, downloadUri, apk); + } else if (act == 2) { + uninstallPackage(apk); + } + } else { + if (act == 1) { + installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED); + } else if (act == 2) { + installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED); + } + } + finish(); + } + } + + private void installPackage(Uri localApkUri, Uri downloadUri, Apk apk) { + Utils.debugLog(TAG, "Installing: " + localApkUri.getPath()); + installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_STARTED); + File path = apk.getMediaInstallPath(activity.getApplicationContext()); + path.mkdirs(); + try { + FileUtils.copyFileToDirectory(new File(localApkUri.getPath()), path); + } catch (IOException e) { + Utils.debugLog(TAG, "Failed to copy: " + e.getMessage()); + installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED); + } + if (apk.isMediaInstalled(activity.getApplicationContext())) { // Copying worked + Utils.debugLog(TAG, "Copying worked: " + localApkUri.getPath()); + Toast.makeText(this, String.format(this.getString(R.string.app_installed_media), path.toString()), + Toast.LENGTH_LONG).show(); + installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_COMPLETE); + } else { + installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED); + } + finish(); + } + + private void uninstallPackage(Apk apk) { + if (apk.isMediaInstalled(activity.getApplicationContext())) { + File file = new File(apk.getMediaInstallPath(activity.getApplicationContext()), apk.apkName); + if (!file.delete()) { + installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED); + return; + } + } + installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_COMPLETE); + finish(); + } +} diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java index b228a2fea..c8589d058 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java @@ -343,7 +343,7 @@ public class InstallManagerService extends Service { appUpdateStatusManager.updateApk(downloadUrl, AppUpdateStatusManager.Status.Installed, null); Apk apkComplete = appUpdateStatusManager.getApk(downloadUrl); - if (apkComplete != null) { + if (apkComplete != null && apkComplete.isApk()) { try { PackageManagerCompat.setInstaller(context, getPackageManager(), apkComplete.packageName); } catch (SecurityException e) { diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallerFactory.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallerFactory.java index 066cec1d3..072b1de74 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallerFactory.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallerFactory.java @@ -22,8 +22,7 @@ package org.fdroid.fdroid.installer; import android.content.Context; import android.text.TextUtils; -import android.widget.Toast; -import org.fdroid.fdroid.R; + import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Apk; @@ -47,11 +46,9 @@ public class InstallerFactory { Installer installer; - if (apk.apkName != null && !apk.apkName.endsWith(".apk")) { - String msg = context.getString(R.string.install_error_not_yet_supported, apk.apkName); - Toast.makeText(context, msg, Toast.LENGTH_LONG).show(); - Utils.debugLog(TAG, msg); - installer = new DummyInstaller(context, apk); + if (!apk.isApk()) { + Utils.debugLog(TAG, "Using FileInstaller for non-apk file"); + installer = new FileInstaller(context, apk); } else if (PrivilegedInstaller.isDefault(context)) { Utils.debugLog(TAG, "privileged extension correctly installed -> PrivilegedInstaller"); installer = new PrivilegedInstaller(context, apk); diff --git a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java index 46bb5cb39..1ddb1fd80 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java @@ -478,14 +478,14 @@ public class AppDetailsRecyclerViewAdapter if (callbacks.isAppDownloading()) { buttonPrimaryView.setText(R.string.downloading); buttonPrimaryView.setEnabled(false); - } else if (!app.isInstalled() && suggestedApk != null) { + } else if (!app.isInstalled(context) && suggestedApk != null) { // Check count > 0 due to incompatible apps resulting in an empty list. callbacks.disableAndroidBeam(); // Set Install button and hide second button buttonPrimaryView.setText(R.string.menu_install); buttonPrimaryView.setOnClickListener(onInstallClickListener); buttonPrimaryView.setEnabled(true); - } else if (app.isInstalled()) { + } else if (app.isInstalled(context)) { callbacks.enableAndroidBeam(); if (app.canAndWantToUpdate(context) && suggestedApk != null) { buttonPrimaryView.setText(R.string.menu_upgrade); diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java index 0fd8f9f7c..93c2f7489 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java +++ b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java @@ -322,7 +322,7 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder { } protected AppListItemState getViewStateReadyToInstall(@NonNull App app) { - int actionButtonLabel = app.isInstalled() + int actionButtonLabel = app.isInstalled(activity.getApplicationContext()) ? R.string.app__install_downloaded_update : R.string.menu_install; diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/StandardAppListItemController.java b/app/src/main/java/org/fdroid/fdroid/views/apps/StandardAppListItemController.java index 35b102795..dcfa73e9d 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/apps/StandardAppListItemController.java +++ b/app/src/main/java/org/fdroid/fdroid/views/apps/StandardAppListItemController.java @@ -35,7 +35,7 @@ public class StandardAppListItemController extends AppListItemController { private CharSequence getStatusText(@NonNull App app) { if (!app.compatible) { return activity.getString(R.string.app_incompatible); - } else if (app.isInstalled()) { + } else if (app.isInstalled(activity.getApplicationContext())) { if (app.canAndWantToUpdate(activity)) { return activity.getString(R.string.app_version_x_available, app.getSuggestedVersionName()); } else { @@ -47,7 +47,7 @@ public class StandardAppListItemController extends AppListItemController { } private boolean shouldShowInstall(@NonNull App app) { - boolean installable = app.canAndWantToUpdate(activity) || !app.isInstalled(); + boolean installable = app.canAndWantToUpdate(activity) || !app.isInstalled(activity.getApplicationContext()); boolean shouldAllow = app.compatible && !app.isFiltered(); return installable && shouldAllow; diff --git a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java index 3ac28cf41..47704dfd9 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java @@ -16,9 +16,11 @@ import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.ViewGroup; import android.widget.Toast; + import com.ashokvarma.bottomnavigation.BadgeItem; import com.ashokvarma.bottomnavigation.BottomNavigationBar; import com.ashokvarma.bottomnavigation.BottomNavigationItem; + import org.fdroid.fdroid.AppDetails2; import org.fdroid.fdroid.AppUpdateStatusManager; import org.fdroid.fdroid.FDroidApp; @@ -388,5 +390,4 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB } } }; - } \ No newline at end of file diff --git a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java index c38264c10..664ed1d08 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java +++ b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java @@ -346,7 +346,7 @@ public class SwapAppsView extends ListView implements btnInstall.setVisibility(View.VISIBLE); statusIncompatible.setVisibility(View.GONE); statusInstalled.setVisibility(View.GONE); - } else if (app.isInstalled()) { + } else if (app.isInstalled(getContext())) { btnInstall.setVisibility(View.GONE); statusIncompatible.setVisibility(View.GONE); statusInstalled.setVisibility(View.VISIBLE); diff --git a/app/src/main/res/values-af/strings.xml b/app/src/main/res/values-af/strings.xml index 28cfd1ebd..2dd5c2763 100644 --- a/app/src/main/res/values-af/strings.xml +++ b/app/src/main/res/values-af/strings.xml @@ -489,7 +489,6 @@ Kyk na alle %d - Lêertipe kan nog nie geïnstalleer word nie: %s Vandag opgedateer %1$s dag gelede opgedateer diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 0161c72fd..aa67ad5c6 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -460,7 +460,6 @@ أضف مصادر أخرى للتطبيقات الرخصة: %s الملف المطلوب غير موجود. - نوع الملف لا يمكن بعد تثبيته: %s جاري التحميل، اكتمل %1$d%% التصنيف %1$s diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml index 406e60dd3..ee77d857e 100644 --- a/app/src/main/res/values-ast/strings.xml +++ b/app/src/main/res/values-ast/strings.xml @@ -460,7 +460,6 @@ Ver toles %d - Entá nun puede instalase\'l tipu de ficheru: %s ¿Nun tienes Internet? ¡Descarga apps de xente cercana! Alcontrar persones cercanes Les dos partes necesiten %1$s pa usar la cercanía. diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index ba5e514f3..81340becb 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -489,7 +489,6 @@ Адмяніць спампоўку Спампоўка, %1$d%% скончана Ліцэнзія: %s - Тып файла не можа быць усталяваны: %s %1$d абнаўленне %1$d абнаўленні diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index f408d26fa..d249a3d53 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -479,7 +479,6 @@ Veure\'ls tots %d - El tipus de fitxer no es pot instal·lar: %s Descarregant, %1$d%% completat %1$d actualització diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 138214d62..eb2cd79d3 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -431,7 +431,6 @@ Vis alle %d - Kan endnu ikke installere filtypen: %s Ingen internet? Hent apps fra folk i nærheden af dig! Find folk i nærheden af mig Begge parter skal have %1$s for at benytte i nærheden. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c51ca7c35..ad2b5443e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -481,7 +481,6 @@ Herunterladen abbrechen wird heruntergeladen, %1$d%% vervollständigt Lizenz: %s - Dateityp kann nicht installiert werden: %s +%1$d weitere … +%1$d weitere … diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 9855fb756..eed014584 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -456,7 +456,6 @@ Nuligi elŝutadon Elŝutado, %1$d%% kompleta Permesilo: %s - Dosier-tipo ne povas ankoraŭ esti instalita: %s +%1$d pli… +1$d pli… diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 8260d85fc..568ff8bda 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -473,7 +473,6 @@ Cancelar descarga Descargando, %1$d%% completado Licencia: %s - No se puede instalar el tipo de fichero: %s +%1$d más… +%1$d más… diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 67f62912f..8768a0074 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -488,7 +488,6 @@ Ez dago kategoriarik erakusteko - Fitxategi mota ezin da oraindik instalatu: %s Internetik ez? Eskuratu aplikazioak inguruko jendearengandik! Aurkitu inguruko jendea Biek %1$s erabili behar dute inguruan elkar aurkitzeko. diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index a142cf845..d3e782d86 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -436,7 +436,6 @@ %1$sd روز پیش به‌روز شده‌ پروانه: %s - این نوع فایل نمی تواند نصب شود: %s +%1$d بیشتر… +%1$d بیشتر… diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 249f115d0..d98bb1643 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -496,7 +496,6 @@ Mis à jour aujourd\'hui Annuler le téléchargement Licence: %s - Ce type de fichier ne peut être installé pour l\'instant: %s Téléchargement, %1$d%% complété +%1$d autre… diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 02cbea67a..e605ff245 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -409,7 +409,6 @@ הצגת כל ה־%d - עדיין לא ניתן להתקין את סוג הקובץ: %s אין חיבור לאינטרנט? ניתן להוריד יישומונים מאנשים בקרבתך! חיפוש אנשים בקרבתי שני הצדדים צריכים %1$s כדי להשתמש בקרבה. diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 420da896d..ba328a144 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -466,7 +466,6 @@ Batalkan unduhan Mengunduh, %1$d%% selesai Lisensi: %s - Tipe berkas belum dapat dipasang: %s +%1$d lainnya… diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index 1f6dffe99..b1b349700 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -502,7 +502,6 @@ Þetta forrit er með eiginleika sem ekki er víst að þér líki við. Neikvæðir eiginleikar Umbeðin skrá fannst ekki. - Skráartegundina er ekki enn hægt að setja upp: %s Ekkert Internet? Fáðu forrit frá fólki í nágrenninu! Finna fólk nálægt mér Báðir aðilar þurfa %1$s til að nota í nálægð. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index cdb0e45b7..5a56bcb37 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -437,7 +437,6 @@ ダウンロードをキャンセル ダウンロード中、%1$d%% 完了 ライセンス: %s - ファイルの種類はまだインストールできません: %s +%1$d さらに… diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 73ebdb994..46202236a 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -293,7 +293,6 @@ അഭിനന്ദനങ്ങൾ! നിങ്ങളുടെ പ്രയോഗങ്ങളെല്ലാം കാലികമാണ് (അല്ലെങ്കിൽ നിങ്ങളുടെ സംഭരണികള്‍ കാലഹരണപ്പെട്ടതാണ്). ഒരു അജ്ഞാത പിശക് കാരണം സ്ഥാപിക്കല്‍ പരാജയപ്പെട്ടു കാരണം ഒരു അജ്ഞാത പിശക് ഒഴിവാക്കല്‍ പരാജയപ്പെട്ടു - ഫയൽ തരം സ്ഥാപിക്കാന്‍ കഴിയില്ല: %s വിപുലീകരണത്തിന്റെ ഒപ്പ് തെറ്റാണ്! ഒരു ബഗ് റിപ്പോർട്ട് സൃഷ്ടിക്കുക! പ്രത്യേക അനുമതികൾ വിപുലീകരണത്തിന് നൽകിയിട്ടില്ല! ഒരു ബഗ് റിപ്പോർട്ട് സൃഷ്ടിക്കുക! പ്രത്യേക അനുമതി ആവശ്യമുള്ള എഫ്-ഡ്രോയ്ഡ് വിപുലീകരണം സ്ഥാപിച്ചു diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 4d87ba6e3..0ca8934f0 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -479,7 +479,6 @@ Laster ned, %1$d%% fullført Lisens: %s - Filtypen kan ikke installeres enda: %s +%1$d til… +%1$d til… diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index c5a6cd838..15b5683eb 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -450,7 +450,6 @@ Annuleer download Downloaden, %1$d%% voltooid Licentie: %s - Bestandstype kan nog niet geïnstalleerd worden: %s +%1$d meer… +%1$d meer… diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a23360d15..8c05a31f6 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -467,7 +467,6 @@ Anuluj pobieranie Pobieranie, %1$d%% ukończono Licencja: %s - Typ pliku nie może być jeszcze zainstalowany: %s %1$d Aktualizacja %1$d Aktualizacje diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 2d09b04f9..9138f404b 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -482,7 +482,6 @@ Cancelar download Baixando, %1$d%% completo Licença: %s - Tipo de arquivo ainda não pode ser instalado: %s +%1$d mais… +%1$d mais… diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 1565628b9..17bc8d7a7 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -489,7 +489,6 @@ Vídeo Licença: %s - Este tipo de ficheiro ainda não pode ser instalado: %s A descarregar, %1$d%% completo Cancelar descarga de %s diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 717d19446..4081523e8 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -438,7 +438,6 @@ Anulează descărcarea Video Licență: %s - Acest tip de fișier nu se poate încă instala: %s Nu ai acces la Internet? Descarcă aplicații de la persoanele de lângă tine! Găsește persoane lângă mine Ambele persoane au nevoie de %1$s pentru a putea folosi această opțiune. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 476496c10..21e6343af 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -486,7 +486,6 @@ Отменить скачивание Скачано %1$d%% Лицензия: %s - Тип файла пока не может быть установлен: %s %1$d обновление %1$d обновления diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 748f16326..688e54794 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -493,7 +493,6 @@ Firma s\'iscarrigamentu Iscarrighende, %1$d%% cumpridu Litzèntzia: %s - Sa casta de documentu non podet èssere installada: %s +%1$d àtera… àteras +%1$d… diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 2bc358eec..7f714cb58 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -462,7 +462,6 @@ Dostupných %d aplikácií - Typ súboru zatiaľ nie je možné nainštalovať: %s Žiadny Internet? Stiahnite si aplikácie od ľudí v blízkosti vás! Nájdi ľudí blízko mňa Obidve strany potrebujú %1$s na použitie v okolí. diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 10b7bf932..32ccc76d4 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -486,7 +486,6 @@ Откажи преузимање Лиценца: %s Преузимам, %1$d%% завршено - Тип фајла још не може бити инсталиран: %s од %s +још %1$d… diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 229c66120..55a75502c 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -466,7 +466,6 @@ Uppdaterad idag Avbryt hämtning Licens: %s - Filtypen kan inte ännu installeras: %s Hämtar, %1$d%% färdigt +%1$d till… diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 3d7ccde7f..cca8966f8 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -463,7 +463,6 @@ İndirmeyi iptal et İndiriliyor, %%%1$d tamamlandı Lisans: %s - Dosya türü henüz kurulamaz: %s +%1$d daha… +%1$d daha… diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 9cf386102..58a469fba 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -466,7 +466,6 @@ Усіх %d застосунків - Цей вид файлу не може бути встановленим: %s Обидві частини потребують %1$s для користання поблизу. Стягування, %1$d%% записано diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 2f2f734c9..2c6485fd0 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -388,7 +388,6 @@ 查看全部 %d - 文件类型无法安装:%s 没有连上互联网?请从附近的 F-Droid 用户那里获取应用! 查找附近的人 双方都需要 %1$s 才可使用附近的人功能。 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 5f020614e..6afe4a00e 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -448,7 +448,6 @@ 取消下載 正在下载的 %1$d%% 完成 授權:%s - 還未安裝檔案類型:%s +%1$d 更多… diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4a8e56659..a907ea613 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -73,6 +73,8 @@ Added on %s Cancel download Update + File installed to %s + F-Droid needs the storage permission to install this to storage. Please allow it on the next screen to proceed with installation. Downloading %1$s %1$s installed @@ -330,7 +332,6 @@ Failed to install due to an unknown error Failed to uninstall due to an unknown error - File type cannot yet be installed: %s The signature of the extension is wrong! Please create a bug report! From 06bcf75f9b95000b83a00ca5842f40e0e09cfd10 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 6 Jul 2017 23:07:23 +0200 Subject: [PATCH 3/5] put the faster check first in App.isInstalled() A > is much faster than all the stuff that isMediaInstalled() has to do, so put > first, so that if it is true, isMediaInstalled() is not executed. --- app/src/main/java/org/fdroid/fdroid/data/App.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/App.java b/app/src/main/java/org/fdroid/fdroid/data/App.java index 58f209a36..49cc65a02 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/App.java +++ b/app/src/main/java/org/fdroid/fdroid/data/App.java @@ -856,7 +856,7 @@ public class App extends ValueObject implements Comparable, Parcelable { } public boolean isInstalled(Context context) { - return isMediaInstalled(context) || installedVersionCode > 0; + return installedVersionCode > 0 || isMediaInstalled(context); } public boolean isMediaInstalled(Context context) { From c5a1b11315c1271812e6882f0a542c3a5cf9b0ba Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 6 Jul 2017 23:29:54 +0200 Subject: [PATCH 4/5] allow spaces in file names This will be more important as people work with media, since it is quite common to use spaces in filenames generated by humans. Media files will not be built by fdroid, so most likely, they will have human-generated names. --- app/src/main/java/org/fdroid/fdroid/data/SanitizedFile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/SanitizedFile.java b/app/src/main/java/org/fdroid/fdroid/data/SanitizedFile.java index 5df4fb933..0e6d46e40 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/SanitizedFile.java +++ b/app/src/main/java/org/fdroid/fdroid/data/SanitizedFile.java @@ -15,7 +15,7 @@ public class SanitizedFile extends File { * Removes anything that is not an alpha numeric character, or one of "-", ".", or "_". */ public static String sanitizeFileName(String name) { - return name.replaceAll("[^A-Za-z0-9-._]", ""); + return name.replaceAll("[^A-Za-z0-9-._ ]", ""); } /** From 7dbf03c435571ca6f8e8e2e16a78173548c0574b Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Fri, 7 Jul 2017 00:02:39 +0200 Subject: [PATCH 5/5] Apk.isMediaInstalled() needs to check using sanitized file names The install process automatically sanitizes filenames to avoid exploits that put attack code in the filename. Media files are also installed using this logic, so the installed check needs to use sanitized file names to be accurate. --- app/src/main/java/org/fdroid/fdroid/data/Apk.java | 2 +- .../test/java/org/fdroid/fdroid/data/SanitizedFileTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/Apk.java b/app/src/main/java/org/fdroid/fdroid/data/Apk.java index 28c12bf29..0668c8663 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Apk.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Apk.java @@ -516,7 +516,7 @@ public class Apk extends ValueObject implements Comparable, Parcelable { } public boolean isMediaInstalled(Context context) { - return new File(this.getMediaInstallPath(context), this.apkName).isFile(); + return new File(this.getMediaInstallPath(context), SanitizedFile.sanitizeFileName(this.apkName)).isFile(); } /** diff --git a/app/src/test/java/org/fdroid/fdroid/data/SanitizedFileTest.java b/app/src/test/java/org/fdroid/fdroid/data/SanitizedFileTest.java index a1415c425..fc4dca32b 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/SanitizedFileTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/SanitizedFileTest.java @@ -37,11 +37,11 @@ public class SanitizedFileTest { assertEquals("/tmp/blah/safe", safeSanitized.getAbsolutePath()); assertEquals("/tmp/blah/safe-and_bleh.boo", nonEvilSanitized.getAbsolutePath()); - assertEquals("/tmp/blah/rmetcshadow", evilSanitized.getAbsolutePath()); + assertEquals("/tmp/blah/rm etcshadow", evilSanitized.getAbsolutePath()); assertEquals("safe", safeSanitized.getName()); assertEquals("safe-and_bleh.boo", nonEvilSanitized.getName()); - assertEquals("rmetcshadow", evilSanitized.getName()); + assertEquals("rm etcshadow", evilSanitized.getName()); }