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.
This commit is contained in:
parent
cbf3133e43
commit
0d8b0c7fd4
@ -508,6 +508,7 @@
|
||||
</activity>
|
||||
|
||||
<activity android:name=".AboutActivity" android:theme="@style/Theme.AppCompat.Light.Dialog" />
|
||||
<activity android:name=".installer.FileInstallerActivity" android:theme="@style/AppThemeTransparent" />
|
||||
|
||||
</application>
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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<Apk>, 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");
|
||||
}
|
||||
}
|
||||
|
@ -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<App>, 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<Apk> 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<App>, 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<App>, 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;
|
||||
}
|
||||
|
@ -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
|
@ -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();
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -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);
|
||||
|
@ -489,7 +489,6 @@
|
||||
<item quantity="other">Kyk na alle %d</item>
|
||||
</plurals>
|
||||
|
||||
<string name="install_error_not_yet_supported">Lêertipe kan nog nie geïnstalleer word nie: %s</string>
|
||||
<string name="details_last_updated_today">Vandag opgedateer</string>
|
||||
<plurals name="details_last_update_days">
|
||||
<item quantity="one">%1$s dag gelede opgedateer</item>
|
||||
|
@ -460,7 +460,6 @@
|
||||
<string name="repositories_summary">أضف مصادر أخرى للتطبيقات</string>
|
||||
<string name="menu_license">الرخصة: %s</string>
|
||||
<string name="download_404">الملف المطلوب غير موجود.</string>
|
||||
<string name="install_error_not_yet_supported">نوع الملف لا يمكن بعد تثبيته: %s</string>
|
||||
<string name="app__tts__downloading_progress">جاري التحميل، اكتمل %1$d%%</string>
|
||||
<string name="tts_category_name">التصنيف %1$s</string>
|
||||
|
||||
|
@ -460,7 +460,6 @@
|
||||
<item quantity="other">Ver toles %d</item>
|
||||
</plurals>
|
||||
|
||||
<string name="install_error_not_yet_supported">Entá nun puede instalase\'l tipu de ficheru: %s</string>
|
||||
<string name="nearby_splash__download_apps_from_people_nearby">¿Nun tienes Internet? ¡Descarga apps de xente cercana!</string>
|
||||
<string name="nearby_splash__find_people_button">Alcontrar persones cercanes</string>
|
||||
<string name="nearby_splash__both_parties_need_fdroid">Les dos partes necesiten %1$s pa usar la cercanía.</string>
|
||||
|
@ -489,7 +489,6 @@
|
||||
<string name="app__tts__cancel_download">Адмяніць спампоўку</string>
|
||||
<string name="app__tts__downloading_progress">Спампоўка, %1$d%% скончана</string>
|
||||
<string name="menu_license">Ліцэнзія: %s</string>
|
||||
<string name="install_error_not_yet_supported">Тып файла не можа быць усталяваны: %s</string>
|
||||
<plurals name="notification_summary_updates">
|
||||
<item quantity="one">%1$d абнаўленне</item>
|
||||
<item quantity="few">%1$d абнаўленні</item>
|
||||
|
@ -479,7 +479,6 @@
|
||||
<item quantity="other">Veure\'ls tots %d</item>
|
||||
</plurals>
|
||||
|
||||
<string name="install_error_not_yet_supported">El tipus de fitxer no es pot instal·lar: %s</string>
|
||||
<string name="app__tts__downloading_progress">Descarregant, %1$d%% completat</string>
|
||||
<plurals name="notification_summary_updates">
|
||||
<item quantity="one">%1$d actualització</item>
|
||||
|
@ -431,7 +431,6 @@
|
||||
<item quantity="other">Vis alle %d</item>
|
||||
</plurals>
|
||||
|
||||
<string name="install_error_not_yet_supported">Kan endnu ikke installere filtypen: %s</string>
|
||||
<string name="nearby_splash__download_apps_from_people_nearby">Ingen internet? Hent apps fra folk i nærheden af dig!</string>
|
||||
<string name="nearby_splash__find_people_button">Find folk i nærheden af mig</string>
|
||||
<string name="nearby_splash__both_parties_need_fdroid">Begge parter skal have %1$s for at benytte i nærheden.</string>
|
||||
|
@ -481,7 +481,6 @@
|
||||
<string name="app__tts__cancel_download">Herunterladen abbrechen</string>
|
||||
<string name="app__tts__downloading_progress">wird heruntergeladen, %1$d%% vervollständigt</string>
|
||||
<string name="menu_license">Lizenz: %s</string>
|
||||
<string name="install_error_not_yet_supported">Dateityp kann nicht installiert werden: %s</string>
|
||||
<plurals name="notification_summary_more">
|
||||
<item quantity="one">+%1$d weitere …</item>
|
||||
<item quantity="other">+%1$d weitere …</item>
|
||||
|
@ -456,7 +456,6 @@
|
||||
<string name="app__tts__cancel_download">Nuligi elŝutadon</string>
|
||||
<string name="app__tts__downloading_progress">Elŝutado, %1$d%% kompleta</string>
|
||||
<string name="menu_license">Permesilo: %s</string>
|
||||
<string name="install_error_not_yet_supported">Dosier-tipo ne povas ankoraŭ esti instalita: %s</string>
|
||||
<plurals name="notification_summary_more">
|
||||
<item quantity="one">+%1$d pli…</item>
|
||||
<item quantity="other">+1$d pli…</item>
|
||||
|
@ -473,7 +473,6 @@
|
||||
<string name="app__tts__cancel_download">Cancelar descarga</string>
|
||||
<string name="app__tts__downloading_progress">Descargando, %1$d%% completado</string>
|
||||
<string name="menu_license">Licencia: %s</string>
|
||||
<string name="install_error_not_yet_supported">No se puede instalar el tipo de fichero: %s</string>
|
||||
<plurals name="notification_summary_more">
|
||||
<item quantity="one">+%1$d más…</item>
|
||||
<item quantity="other">+%1$d más…</item>
|
||||
|
@ -488,7 +488,6 @@
|
||||
|
||||
<string name="categories__empty_state__no_categories">Ez dago kategoriarik erakusteko</string>
|
||||
|
||||
<string name="install_error_not_yet_supported">Fitxategi mota ezin da oraindik instalatu: %s</string>
|
||||
<string name="nearby_splash__download_apps_from_people_nearby">Internetik ez? Eskuratu aplikazioak inguruko jendearengandik!</string>
|
||||
<string name="nearby_splash__find_people_button">Aurkitu inguruko jendea</string>
|
||||
<string name="nearby_splash__both_parties_need_fdroid">Biek %1$s erabili behar dute inguruan elkar aurkitzeko.</string>
|
||||
|
@ -436,7 +436,6 @@
|
||||
<item quantity="other">%1$sd روز پیش بهروز شده</item>
|
||||
</plurals>
|
||||
<string name="menu_license">پروانه: %s</string>
|
||||
<string name="install_error_not_yet_supported">این نوع فایل نمی تواند نصب شود: %s</string>
|
||||
<plurals name="notification_summary_more">
|
||||
<item quantity="one">+%1$d بیشتر…</item>
|
||||
<item quantity="other">+%1$d بیشتر…</item>
|
||||
|
@ -496,7 +496,6 @@
|
||||
<string name="details_last_updated_today">Mis à jour aujourd\'hui</string>
|
||||
<string name="app__tts__cancel_download">Annuler le téléchargement</string>
|
||||
<string name="menu_license">Licence: %s</string>
|
||||
<string name="install_error_not_yet_supported">Ce type de fichier ne peut être installé pour l\'instant: %s</string>
|
||||
<string name="app__tts__downloading_progress">Téléchargement, %1$d%% complété</string>
|
||||
<plurals name="notification_summary_more">
|
||||
<item quantity="one">+%1$d autre…</item>
|
||||
|
@ -409,7 +409,6 @@
|
||||
<item quantity="other">הצגת כל ה־%d</item>
|
||||
</plurals>
|
||||
|
||||
<string name="install_error_not_yet_supported">עדיין לא ניתן להתקין את סוג הקובץ: %s</string>
|
||||
<string name="nearby_splash__download_apps_from_people_nearby">אין חיבור לאינטרנט? ניתן להוריד יישומונים מאנשים בקרבתך!</string>
|
||||
<string name="nearby_splash__find_people_button">חיפוש אנשים בקרבתי</string>
|
||||
<string name="nearby_splash__both_parties_need_fdroid">שני הצדדים צריכים %1$s כדי להשתמש בקרבה.</string>
|
||||
|
@ -466,7 +466,6 @@
|
||||
<string name="app__tts__cancel_download">Batalkan unduhan</string>
|
||||
<string name="app__tts__downloading_progress">Mengunduh, %1$d%% selesai</string>
|
||||
<string name="menu_license">Lisensi: %s</string>
|
||||
<string name="install_error_not_yet_supported">Tipe berkas belum dapat dipasang: %s</string>
|
||||
<plurals name="notification_summary_more">
|
||||
<item quantity="other">+%1$d lainnya…</item>
|
||||
</plurals>
|
||||
|
@ -502,7 +502,6 @@
|
||||
<string name="antifeatureswarning">Þetta forrit er með eiginleika sem ekki er víst að þér líki við.</string>
|
||||
<string name="antifeatures">Neikvæðir eiginleikar</string>
|
||||
<string name="download_404">Umbeðin skrá fannst ekki.</string>
|
||||
<string name="install_error_not_yet_supported">Skráartegundina er ekki enn hægt að setja upp: %s</string>
|
||||
<string name="nearby_splash__download_apps_from_people_nearby">Ekkert Internet? Fáðu forrit frá fólki í nágrenninu!</string>
|
||||
<string name="nearby_splash__find_people_button">Finna fólk nálægt mér</string>
|
||||
<string name="nearby_splash__both_parties_need_fdroid">Báðir aðilar þurfa %1$s til að nota í nálægð.</string>
|
||||
|
@ -437,7 +437,6 @@
|
||||
<string name="app__tts__cancel_download">ダウンロードをキャンセル</string>
|
||||
<string name="app__tts__downloading_progress">ダウンロード中、%1$d%% 完了</string>
|
||||
<string name="menu_license">ライセンス: %s</string>
|
||||
<string name="install_error_not_yet_supported">ファイルの種類はまだインストールできません: %s</string>
|
||||
<plurals name="notification_summary_more">
|
||||
<item quantity="other">+%1$d さらに…</item>
|
||||
</plurals>
|
||||
|
@ -293,7 +293,6 @@
|
||||
<string name="empty_can_update_app_list">അഭിനന്ദനങ്ങൾ! നിങ്ങളുടെ പ്രയോഗങ്ങളെല്ലാം കാലികമാണ് (അല്ലെങ്കിൽ നിങ്ങളുടെ സംഭരണികള് കാലഹരണപ്പെട്ടതാണ്).</string>
|
||||
<string name="install_error_unknown">ഒരു അജ്ഞാത പിശക് കാരണം സ്ഥാപിക്കല് പരാജയപ്പെട്ടു</string>
|
||||
<string name="uninstall_error_unknown">കാരണം ഒരു അജ്ഞാത പിശക് ഒഴിവാക്കല് പരാജയപ്പെട്ടു</string>
|
||||
<string name="install_error_not_yet_supported">ഫയൽ തരം സ്ഥാപിക്കാന് കഴിയില്ല: %s</string>
|
||||
<string name="system_install_denied_signature">വിപുലീകരണത്തിന്റെ ഒപ്പ് തെറ്റാണ്! ഒരു ബഗ് റിപ്പോർട്ട് സൃഷ്ടിക്കുക!</string>
|
||||
<string name="system_install_denied_permissions">പ്രത്യേക അനുമതികൾ വിപുലീകരണത്തിന് നൽകിയിട്ടില്ല! ഒരു ബഗ് റിപ്പോർട്ട് സൃഷ്ടിക്കുക!</string>
|
||||
<string name="system_install_post_success">പ്രത്യേക അനുമതി ആവശ്യമുള്ള എഫ്-ഡ്രോയ്ഡ് വിപുലീകരണം സ്ഥാപിച്ചു</string>
|
||||
|
@ -479,7 +479,6 @@
|
||||
|
||||
<string name="app__tts__downloading_progress">Laster ned, %1$d%% fullført</string>
|
||||
<string name="menu_license">Lisens: %s</string>
|
||||
<string name="install_error_not_yet_supported">Filtypen kan ikke installeres enda: %s</string>
|
||||
<plurals name="notification_summary_more">
|
||||
<item quantity="one">+%1$d til…</item>
|
||||
<item quantity="other">+%1$d til…</item>
|
||||
|
@ -450,7 +450,6 @@
|
||||
<string name="app__tts__cancel_download">Annuleer download</string>
|
||||
<string name="app__tts__downloading_progress">Downloaden, %1$d%% voltooid</string>
|
||||
<string name="menu_license">Licentie: %s</string>
|
||||
<string name="install_error_not_yet_supported">Bestandstype kan nog niet geïnstalleerd worden: %s</string>
|
||||
<plurals name="notification_summary_more">
|
||||
<item quantity="one">+%1$d meer…</item>
|
||||
<item quantity="other">+%1$d meer…</item>
|
||||
|
@ -467,7 +467,6 @@
|
||||
<string name="app__tts__cancel_download">Anuluj pobieranie</string>
|
||||
<string name="app__tts__downloading_progress">Pobieranie, %1$d%% ukończono</string>
|
||||
<string name="menu_license">Licencja: %s</string>
|
||||
<string name="install_error_not_yet_supported">Typ pliku nie może być jeszcze zainstalowany: %s</string>
|
||||
<plurals name="notification_summary_updates">
|
||||
<item quantity="one">%1$d Aktualizacja</item>
|
||||
<item quantity="few">%1$d Aktualizacje</item>
|
||||
|
@ -482,7 +482,6 @@
|
||||
<string name="app__tts__cancel_download">Cancelar download</string>
|
||||
<string name="app__tts__downloading_progress">Baixando, %1$d%% completo</string>
|
||||
<string name="menu_license">Licença: %s</string>
|
||||
<string name="install_error_not_yet_supported">Tipo de arquivo ainda não pode ser instalado: %s</string>
|
||||
<plurals name="notification_summary_more">
|
||||
<item quantity="one">+%1$d mais…</item>
|
||||
<item quantity="other">+%1$d mais…</item>
|
||||
|
@ -489,7 +489,6 @@
|
||||
|
||||
<string name="menu_video">Vídeo</string>
|
||||
<string name="menu_license">Licença: %s</string>
|
||||
<string name="install_error_not_yet_supported">Este tipo de ficheiro ainda não pode ser instalado: %s</string>
|
||||
<string name="app__tts__downloading_progress">A descarregar, %1$d%% completo</string>
|
||||
<string name="app__tts__cancel_download">Cancelar descarga</string>
|
||||
<string name="by_author_format">de %s</string>
|
||||
|
@ -438,7 +438,6 @@
|
||||
<string name="app__tts__cancel_download">Anulează descărcarea</string>
|
||||
<string name="menu_video">Video</string>
|
||||
<string name="menu_license">Licență: %s</string>
|
||||
<string name="install_error_not_yet_supported">Acest tip de fișier nu se poate încă instala: %s</string>
|
||||
<string name="nearby_splash__download_apps_from_people_nearby">Nu ai acces la Internet? Descarcă aplicații de la persoanele de lângă tine!</string>
|
||||
<string name="nearby_splash__find_people_button">Găsește persoane lângă mine</string>
|
||||
<string name="nearby_splash__both_parties_need_fdroid">Ambele persoane au nevoie de %1$s pentru a putea folosi această opțiune.</string>
|
||||
|
@ -486,7 +486,6 @@
|
||||
<string name="app__tts__cancel_download">Отменить скачивание</string>
|
||||
<string name="app__tts__downloading_progress">Скачано %1$d%%</string>
|
||||
<string name="menu_license">Лицензия: %s</string>
|
||||
<string name="install_error_not_yet_supported">Тип файла пока не может быть установлен: %s</string>
|
||||
<plurals name="notification_summary_updates">
|
||||
<item quantity="one">%1$d обновление</item>
|
||||
<item quantity="few">%1$d обновления</item>
|
||||
|
@ -493,7 +493,6 @@
|
||||
<string name="app__tts__cancel_download">Firma s\'iscarrigamentu</string>
|
||||
<string name="app__tts__downloading_progress">Iscarrighende, %1$d%% cumpridu</string>
|
||||
<string name="menu_license">Litzèntzia: %s</string>
|
||||
<string name="install_error_not_yet_supported">Sa casta de documentu non podet èssere installada: %s</string>
|
||||
<plurals name="notification_summary_more">
|
||||
<item quantity="one">+%1$d àtera…</item>
|
||||
<item quantity="other">àteras +%1$d…</item>
|
||||
|
@ -462,7 +462,6 @@
|
||||
<item quantity="other">Dostupných %d aplikácií</item>
|
||||
</plurals>
|
||||
|
||||
<string name="install_error_not_yet_supported">Typ súboru zatiaľ nie je možné nainštalovať: %s</string>
|
||||
<string name="nearby_splash__download_apps_from_people_nearby">Žiadny Internet? Stiahnite si aplikácie od ľudí v blízkosti vás!</string>
|
||||
<string name="nearby_splash__find_people_button">Nájdi ľudí blízko mňa</string>
|
||||
<string name="nearby_splash__both_parties_need_fdroid">Obidve strany potrebujú %1$s na použitie v okolí.</string>
|
||||
|
@ -486,7 +486,6 @@
|
||||
<string name="app__tts__cancel_download">Откажи преузимање</string>
|
||||
<string name="menu_license">Лиценца: %s</string>
|
||||
<string name="app__tts__downloading_progress">Преузимам, %1$d%% завршено</string>
|
||||
<string name="install_error_not_yet_supported">Тип фајла још не може бити инсталиран: %s</string>
|
||||
<string name="by_author_format">од %s</string>
|
||||
<plurals name="notification_summary_more">
|
||||
<item quantity="one">+још %1$d…</item>
|
||||
|
@ -466,7 +466,6 @@
|
||||
<string name="details_last_updated_today">Uppdaterad idag</string>
|
||||
<string name="app__tts__cancel_download">Avbryt hämtning</string>
|
||||
<string name="menu_license">Licens: %s</string>
|
||||
<string name="install_error_not_yet_supported">Filtypen kan inte ännu installeras: %s</string>
|
||||
<string name="app__tts__downloading_progress">Hämtar, %1$d%% färdigt</string>
|
||||
<plurals name="notification_summary_more">
|
||||
<item quantity="one">+%1$d till…</item>
|
||||
|
@ -463,7 +463,6 @@
|
||||
<string name="app__tts__cancel_download">İndirmeyi iptal et</string>
|
||||
<string name="app__tts__downloading_progress">İndiriliyor, %%%1$d tamamlandı</string>
|
||||
<string name="menu_license">Lisans: %s</string>
|
||||
<string name="install_error_not_yet_supported">Dosya türü henüz kurulamaz: %s</string>
|
||||
<plurals name="notification_summary_more">
|
||||
<item quantity="one">+%1$d daha…</item>
|
||||
<item quantity="other">+%1$d daha…</item>
|
||||
|
@ -466,7 +466,6 @@
|
||||
<item quantity="many">Усіх %d застосунків</item>
|
||||
</plurals>
|
||||
|
||||
<string name="install_error_not_yet_supported">Цей вид файлу не може бути встановленим: %s</string>
|
||||
<string name="nearby_splash__both_parties_need_fdroid">Обидві частини потребують %1$s для користання поблизу.</string>
|
||||
|
||||
<string name="app__tts__downloading_progress">Стягування, %1$d%% записано</string>
|
||||
|
@ -388,7 +388,6 @@
|
||||
<item quantity="other">查看全部 %d</item>
|
||||
</plurals>
|
||||
|
||||
<string name="install_error_not_yet_supported">文件类型无法安装:%s</string>
|
||||
<string name="nearby_splash__download_apps_from_people_nearby">没有连上互联网?请从附近的 F-Droid 用户那里获取应用!</string>
|
||||
<string name="nearby_splash__find_people_button">查找附近的人</string>
|
||||
<string name="nearby_splash__both_parties_need_fdroid">双方都需要 %1$s 才可使用附近的人功能。</string>
|
||||
|
@ -448,7 +448,6 @@
|
||||
<string name="app__tts__cancel_download">取消下載</string>
|
||||
<string name="app__tts__downloading_progress">正在下载的 %1$d%% 完成</string>
|
||||
<string name="menu_license">授權:%s</string>
|
||||
<string name="install_error_not_yet_supported">還未安裝檔案類型:%s</string>
|
||||
<plurals name="notification_summary_more">
|
||||
<item quantity="other">+%1$d 更多…</item>
|
||||
</plurals>
|
||||
|
@ -73,6 +73,8 @@
|
||||
<string name="added_on">Added on %s</string>
|
||||
<string name="app__tts__cancel_download">Cancel download</string>
|
||||
<string name="app__install_downloaded_update">Update</string>
|
||||
<string name="app_installed_media">File installed to %s</string>
|
||||
<string name="app_permission_storage">F-Droid needs the storage permission to install this to storage. Please allow it on the next screen to proceed with installation.</string>
|
||||
|
||||
<string name="app_list__name__downloading_in_progress">Downloading %1$s</string>
|
||||
<string name="app_list__name__successfully_installed">%1$s installed</string>
|
||||
@ -330,7 +332,6 @@
|
||||
</string>
|
||||
<string name="install_error_unknown">Failed to install due to an unknown error</string>
|
||||
<string name="uninstall_error_unknown">Failed to uninstall due to an unknown error</string>
|
||||
<string name="install_error_not_yet_supported">File type cannot yet be installed: %s</string>
|
||||
<string name="system_install_denied_signature">The signature of the extension is wrong! Please create a bug
|
||||
report!
|
||||
</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user