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!