diff --git a/app/build.gradle b/app/build.gradle index 1e9133f80..a821ba29b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -161,15 +161,15 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-annotations:2.8.7' implementation 'com.fasterxml.jackson.core:jackson-databind:2.8.7' - implementation 'org.bouncycastle:bcprov-jdk15on:1.59' - fullImplementation 'org.bouncycastle:bcpkix-jdk15on:1.59' + implementation 'org.bouncycastle:bcprov-jdk15on:1.60' + fullImplementation 'org.bouncycastle:bcpkix-jdk15on:1.60' fullImplementation 'cc.mvdan.accesspoint:library:0.2.0' fullImplementation 'org.jmdns:jmdns:3.5.3' fullImplementation 'org.nanohttpd:nanohttpd:2.3.1' testImplementation 'org.robolectric:robolectric:3.8' testImplementation "com.android.support.test:monitor:1.0.2" - testImplementation 'org.bouncycastle:bcprov-jdk15on:1.59' + testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.7.22' diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java b/app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java index 6256b6209..b7a730766 100644 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java +++ b/app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java @@ -147,10 +147,24 @@ public final class LocalRepoManager { BufferedWriter out = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(indexHtml))); + StringBuilder builder = new StringBuilder(); + for (App app : apps.values()) { + builder.append("
  • ") + .append(app.name) + .append("
  • \n"); + } + String line; while ((line = in.readLine()) != null) { line = line.replaceAll("\\{\\{REPO_URL\\}\\}", repoAddress); line = line.replaceAll("\\{\\{CLIENT_URL\\}\\}", fdroidClientURL); + line = line.replaceAll("\\{\\{APP_LIST\\}\\}", builder.toString()); out.write(line); } in.close(); diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/StartSwapView.java b/app/src/full/java/org/fdroid/fdroid/views/swap/StartSwapView.java index 1891468cd..03aeeb2ac 100644 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/StartSwapView.java +++ b/app/src/full/java/org/fdroid/fdroid/views/swap/StartSwapView.java @@ -257,7 +257,9 @@ public class StartSwapView extends RelativeLayout implements SwapWorkflowActivit textBluetoothVisible.setText(textResource); bluetoothSwitch = (SwitchCompat) findViewById(R.id.switch_bluetooth); - Utils.debugLog(TAG, getManager().isBluetoothDiscoverable() ? "Initially marking switch as checked, because Bluetooth is discoverable." : "Initially marking switch as not-checked, because Bluetooth is not discoverable."); + Utils.debugLog(TAG, getManager().isBluetoothDiscoverable() + ? "Initially marking switch as checked, because Bluetooth is discoverable." + : "Initially marking switch as not-checked, because Bluetooth is not discoverable."); bluetoothSwitch.setOnCheckedChangeListener(onBluetoothSwitchToggled); setBluetoothSwitchState(getManager().isBluetoothDiscoverable(), true); diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d098cdc47..354df5dd0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -129,7 +129,7 @@ android:grantUriPermissions="true"> + android:resource="@xml/installer_file_provider"/> li { + padding: 1em 0; + } + + ul > li a { + font-size: xx-large; + text-decoration: none; + color: #fff; + } + + ul > li a img { + padding-right: 0.5em; + } + #download-from-web { padding-left: 2em; padding-right: 2em; @@ -105,5 +127,12 @@ Not done +



    +
    + Available Apps +
      + {{APP_LIST}} +
    +
    diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java index 6844d0934..90a673fbd 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java @@ -580,7 +580,6 @@ public class FDroidApp extends Application { String bluetoothPackageName = null; String className = null; - boolean found = false; Intent sendBt = null; try { @@ -599,20 +598,19 @@ public class FDroidApp extends Application { if ("com.android.bluetooth".equals(bluetoothPackageName) || "com.mediatek.bluetooth".equals(bluetoothPackageName)) { className = info.activityInfo.name; - found = true; break; } } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Could not get application info to send via bluetooth", e); - found = false; + className = null; } catch (IOException e) { Exception toLog = new RuntimeException("Error preparing file to send via Bluetooth", e); ACRA.getErrorReporter().handleException(toLog, false); } if (sendBt != null) { - if (found) { + if (className != null) { sendBt.setClassName(bluetoothPackageName, className); activity.startActivity(sendBt); } else { diff --git a/app/src/main/java/org/fdroid/fdroid/installer/ApkFileProvider.java b/app/src/main/java/org/fdroid/fdroid/installer/ApkFileProvider.java index ceb903cf9..c1f9fe615 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/ApkFileProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/ApkFileProvider.java @@ -37,14 +37,18 @@ import java.io.IOException; * either locally or for sending via bluetooth. *

    * APK handling for installations: - * 1. APKs are downloaded into a cache directory that is either created on SD card + *

      + *
    1. APKs are downloaded into a cache directory that is either created on SD card * "/Android/data/[app_package_name]/cache/apks" (if card is mounted and app has - * appropriate permission) or on device's file system depending incoming parameters. - * 2. Before installation, the APK is copied into the private data directory of the F-Droid, - * "/data/data/[app_package_name]/files/install-$random.apk". - * 3. The hash of the file is checked against the expected hash from the repository - * 4. For Android < 7, a file Uri pointing to the File is returned, for Android >= 7, - * a content Uri is returned using support lib's FileProvider. + * appropriate permission) or on device's file system depending incoming parameters
    2. + *
    3. Before installation, the APK is copied into the private data directory of the F-Droid, + * "/data/data/[app_package_name]/files/install-$random.apk"
    4. + *
    5. The hash of the file is checked against the expected hash from the repository
    6. + *
    7. For {@link Build.VERSION_CODES#M < android-23}, a {@code file://} {@link Uri} + * pointing to the {@link File} is returned, for {@link Build.VERSION_CODES#M >= android-23}, + * a {@code content://} {@code Uri} is returned using support lib's + * {@link FileProvider}
    8. + *
    */ public class ApkFileProvider extends FileProvider { @@ -52,7 +56,7 @@ public class ApkFileProvider extends FileProvider { public static Uri getSafeUri(Context context, PackageInfo packageInfo) throws IOException { SanitizedFile tempApkFile = ApkCache.copyInstalledApkToFiles(context, packageInfo); - return getSafeUri(context, tempApkFile, Build.VERSION.SDK_INT >= 24); + return getSafeUri(context, tempApkFile, Build.VERSION.SDK_INT >= 23); } /** @@ -89,12 +93,12 @@ public class ApkFileProvider extends FileProvider { context.grantUriPermission(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME, apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); context.grantUriPermission("com.android.bluetooth", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + context.grantUriPermission("com.mediatek.bluetooth", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); return apkUri; + } else { + tempFile.setReadable(true, false); + return Uri.fromFile(tempFile); } - - tempFile.setReadable(true, false); - - return Uri.fromFile(tempFile); } } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java index 7c280c8db..de13a6cb0 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java @@ -28,7 +28,6 @@ import android.net.Uri; import android.os.Process; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; -import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Apk; @@ -47,8 +46,7 @@ import java.util.List; public class InstallHistoryService extends IntentService { public static final String TAG = "InstallHistoryService"; - public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".installer"; - public static final Uri LOG_URI = Uri.parse("content://" + AUTHORITY + "/install_history/all"); + public static final Uri LOG_URI = Uri.parse("content://" + Installer.AUTHORITY + "/install_history/all"); private static BroadcastReceiver broadcastReceiver; diff --git a/app/src/main/java/org/fdroid/fdroid/installer/Installer.java b/app/src/main/java/org/fdroid/fdroid/installer/Installer.java index 817f1fa53..40b2e1775 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/Installer.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/Installer.java @@ -31,6 +31,7 @@ import android.os.PatternMatcher; import android.support.annotation.NonNull; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; +import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.ApkProvider; @@ -51,6 +52,8 @@ public abstract class Installer { final Context context; final Apk apk; + public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".installer"; + public static final String ACTION_INSTALL_STARTED = "org.fdroid.fdroid.installer.Installer.action.INSTALL_STARTED"; public static final String ACTION_INSTALL_COMPLETE = "org.fdroid.fdroid.installer.Installer.action.INSTALL_COMPLETE"; public static final String ACTION_INSTALL_INTERRUPTED = "org.fdroid.fdroid.installer.Installer.action.INSTALL_INTERRUPTED"; 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 eb64af686..fba91932c 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java @@ -7,10 +7,12 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; import android.net.Uri; +import android.os.Build; import android.support.annotation.DrawableRes; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; +import android.support.v4.content.FileProvider; import android.support.v4.view.ViewCompat; import android.support.v4.widget.TextViewCompat; import android.support.v7.app.AlertDialog; @@ -28,6 +30,7 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.webkit.MimeTypeMap; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; @@ -35,6 +38,7 @@ import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.nostra13.universalimageloader.core.ImageLoader; +import org.apache.commons.io.FilenameUtils; import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; @@ -43,10 +47,12 @@ import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.InstalledAppProvider; import org.fdroid.fdroid.data.RepoProvider; +import org.fdroid.fdroid.installer.Installer; import org.fdroid.fdroid.privileged.views.AppDiff; import org.fdroid.fdroid.privileged.views.AppSecurityPermissions; import org.fdroid.fdroid.views.main.MainActivity; +import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -512,9 +518,42 @@ public class AppDetailsRecyclerViewAdapter buttonPrimaryView.setText(R.string.menu_upgrade); buttonPrimaryView.setOnClickListener(onUpgradeClickListener); } else { + Apk mediaApk = app.getMediaApkifInstalled(context); if (context.getPackageManager().getLaunchIntentForPackage(app.packageName) != null) { buttonPrimaryView.setText(R.string.menu_launch); buttonPrimaryView.setOnClickListener(onLaunchClickListener); + } else if (!app.isApk && mediaApk != null) { + final File installedFile = new File(mediaApk.getMediaInstallPath(context), mediaApk.apkName); + if (!installedFile.toString().startsWith(context.getApplicationInfo().dataDir)) { + final Intent viewIntent = new Intent(Intent.ACTION_VIEW); + Uri uri = FileProvider.getUriForFile(context, Installer.AUTHORITY, installedFile); + String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension( + FilenameUtils.getExtension(installedFile.getName())); + viewIntent.setDataAndType(uri, mimeType); + if (Build.VERSION.SDK_INT < 19) { + viewIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } else { + viewIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + } + if (context.getPackageManager().queryIntentActivities(viewIntent, 0).size() > 0) { + buttonPrimaryView.setText(R.string.menu_open); + buttonPrimaryView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + try { + context.startActivity(viewIntent); + } catch (ActivityNotFoundException e) { + e.printStackTrace(); + } + } + }); + } else { + buttonPrimaryView.setVisibility(View.GONE); + } + } else { + buttonPrimaryView.setVisibility(View.GONE); + } } else { buttonPrimaryView.setVisibility(View.GONE); } diff --git a/app/src/main/java/org/fdroid/fdroid/views/installed/InstalledAppListAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/installed/InstalledAppListAdapter.java index 9dec761a8..438fdc3f4 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/installed/InstalledAppListAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/installed/InstalledAppListAdapter.java @@ -6,7 +6,6 @@ import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewGroup; - import org.fdroid.fdroid.R; import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.Schema; @@ -58,4 +57,13 @@ class InstalledAppListAdapter extends RecyclerView.Adapter + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0ebf4b3b8..069afd50e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -110,6 +110,8 @@ This often occurs with apps installed via Google Play or other sources, if they Download canceled Installed Apps + Share installed apps + Apps installed by F-Droid as CSV file Updates ignored Updates ignored for Version %1$s