Merge branch 'swap-and-installer-improvements' into 'master'
Swap and installer improvements See merge request fdroid/fdroidclient!733
This commit is contained in:
commit
795dd0dbf7
@ -161,15 +161,15 @@ dependencies {
|
|||||||
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.8.7'
|
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.8.7'
|
||||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.8.7'
|
implementation 'com.fasterxml.jackson.core:jackson-databind:2.8.7'
|
||||||
|
|
||||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.59'
|
implementation 'org.bouncycastle:bcprov-jdk15on:1.60'
|
||||||
fullImplementation 'org.bouncycastle:bcpkix-jdk15on:1.59'
|
fullImplementation 'org.bouncycastle:bcpkix-jdk15on:1.60'
|
||||||
fullImplementation 'cc.mvdan.accesspoint:library:0.2.0'
|
fullImplementation 'cc.mvdan.accesspoint:library:0.2.0'
|
||||||
fullImplementation 'org.jmdns:jmdns:3.5.3'
|
fullImplementation 'org.jmdns:jmdns:3.5.3'
|
||||||
fullImplementation 'org.nanohttpd:nanohttpd:2.3.1'
|
fullImplementation 'org.nanohttpd:nanohttpd:2.3.1'
|
||||||
|
|
||||||
testImplementation 'org.robolectric:robolectric:3.8'
|
testImplementation 'org.robolectric:robolectric:3.8'
|
||||||
testImplementation "com.android.support.test:monitor:1.0.2"
|
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 'junit:junit:4.12'
|
||||||
testImplementation 'org.mockito:mockito-core:2.7.22'
|
testImplementation 'org.mockito:mockito-core:2.7.22'
|
||||||
|
|
||||||
|
@ -147,10 +147,24 @@ public final class LocalRepoManager {
|
|||||||
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
|
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
|
||||||
new FileOutputStream(indexHtml)));
|
new FileOutputStream(indexHtml)));
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (App app : apps.values()) {
|
||||||
|
builder.append("<li><a href=\"/fdroid/repo/")
|
||||||
|
.append(app.installedApk.apkName)
|
||||||
|
.append("\"><img width=\"32\" height=\"32\" src=\"/fdroid/repo/icons/")
|
||||||
|
.append(app.packageName)
|
||||||
|
.append("_")
|
||||||
|
.append(app.installedApk.versionCode)
|
||||||
|
.append(".png\">")
|
||||||
|
.append(app.name)
|
||||||
|
.append("</a></li>\n");
|
||||||
|
}
|
||||||
|
|
||||||
String line;
|
String line;
|
||||||
while ((line = in.readLine()) != null) {
|
while ((line = in.readLine()) != null) {
|
||||||
line = line.replaceAll("\\{\\{REPO_URL\\}\\}", repoAddress);
|
line = line.replaceAll("\\{\\{REPO_URL\\}\\}", repoAddress);
|
||||||
line = line.replaceAll("\\{\\{CLIENT_URL\\}\\}", fdroidClientURL);
|
line = line.replaceAll("\\{\\{CLIENT_URL\\}\\}", fdroidClientURL);
|
||||||
|
line = line.replaceAll("\\{\\{APP_LIST\\}\\}", builder.toString());
|
||||||
out.write(line);
|
out.write(line);
|
||||||
}
|
}
|
||||||
in.close();
|
in.close();
|
||||||
|
@ -257,7 +257,9 @@ public class StartSwapView extends RelativeLayout implements SwapWorkflowActivit
|
|||||||
textBluetoothVisible.setText(textResource);
|
textBluetoothVisible.setText(textResource);
|
||||||
|
|
||||||
bluetoothSwitch = (SwitchCompat) findViewById(R.id.switch_bluetooth);
|
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);
|
bluetoothSwitch.setOnCheckedChangeListener(onBluetoothSwitchToggled);
|
||||||
setBluetoothSwitchState(getManager().isBluetoothDiscoverable(), true);
|
setBluetoothSwitchState(getManager().isBluetoothDiscoverable(), true);
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@
|
|||||||
android:grantUriPermissions="true">
|
android:grantUriPermissions="true">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/install_history_file_provider"/>
|
android:resource="@xml/installer_file_provider"/>
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
@ -71,6 +71,28 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
details {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul > 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 {
|
#download-from-web {
|
||||||
padding-left: 2em;
|
padding-left: 2em;
|
||||||
padding-right: 2em;
|
padding-right: 2em;
|
||||||
@ -105,5 +127,12 @@
|
|||||||
<img src="swap-tick-not-done.png" class="tick not-done" alt="Not done" />
|
<img src="swap-tick-not-done.png" class="tick not-done" alt="Not done" />
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
<br/><br/><br/><br/>
|
||||||
|
<details>
|
||||||
|
<summary>Available Apps</summary>
|
||||||
|
<ul>
|
||||||
|
{{APP_LIST}}
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -580,7 +580,6 @@ public class FDroidApp extends Application {
|
|||||||
|
|
||||||
String bluetoothPackageName = null;
|
String bluetoothPackageName = null;
|
||||||
String className = null;
|
String className = null;
|
||||||
boolean found = false;
|
|
||||||
Intent sendBt = null;
|
Intent sendBt = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -599,20 +598,19 @@ public class FDroidApp extends Application {
|
|||||||
if ("com.android.bluetooth".equals(bluetoothPackageName)
|
if ("com.android.bluetooth".equals(bluetoothPackageName)
|
||||||
|| "com.mediatek.bluetooth".equals(bluetoothPackageName)) {
|
|| "com.mediatek.bluetooth".equals(bluetoothPackageName)) {
|
||||||
className = info.activityInfo.name;
|
className = info.activityInfo.name;
|
||||||
found = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
Log.e(TAG, "Could not get application info to send via bluetooth", e);
|
Log.e(TAG, "Could not get application info to send via bluetooth", e);
|
||||||
found = false;
|
className = null;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Exception toLog = new RuntimeException("Error preparing file to send via Bluetooth", e);
|
Exception toLog = new RuntimeException("Error preparing file to send via Bluetooth", e);
|
||||||
ACRA.getErrorReporter().handleException(toLog, false);
|
ACRA.getErrorReporter().handleException(toLog, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sendBt != null) {
|
if (sendBt != null) {
|
||||||
if (found) {
|
if (className != null) {
|
||||||
sendBt.setClassName(bluetoothPackageName, className);
|
sendBt.setClassName(bluetoothPackageName, className);
|
||||||
activity.startActivity(sendBt);
|
activity.startActivity(sendBt);
|
||||||
} else {
|
} else {
|
||||||
|
@ -37,14 +37,18 @@ import java.io.IOException;
|
|||||||
* either locally or for sending via bluetooth.
|
* either locally or for sending via bluetooth.
|
||||||
* <p/>
|
* <p/>
|
||||||
* APK handling for installations:
|
* APK handling for installations:
|
||||||
* 1. APKs are downloaded into a cache directory that is either created on SD card
|
* <ol>
|
||||||
|
* <li>APKs are downloaded into a cache directory that is either created on SD card
|
||||||
* <i>"/Android/data/[app_package_name]/cache/apks"</i> (if card is mounted and app has
|
* <i>"/Android/data/[app_package_name]/cache/apks"</i> (if card is mounted and app has
|
||||||
* appropriate permission) or on device's file system depending incoming parameters.
|
* appropriate permission) or on device's file system depending incoming parameters</li>
|
||||||
* 2. Before installation, the APK is copied into the private data directory of the F-Droid,
|
* <li>Before installation, the APK is copied into the private data directory of the F-Droid,
|
||||||
* <i>"/data/data/[app_package_name]/files/install-$random.apk"</i>.
|
* <i>"/data/data/[app_package_name]/files/install-$random.apk"</i></li>
|
||||||
* 3. The hash of the file is checked against the expected hash from the repository
|
* <li>The hash of the file is checked against the expected hash from the repository</li>
|
||||||
* 4. For Android < 7, a file Uri pointing to the File is returned, for Android >= 7,
|
* <li>For {@link Build.VERSION_CODES#M < android-23}, a {@code file://} {@link Uri}
|
||||||
* a content Uri is returned using support lib's FileProvider.
|
* 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}</li>
|
||||||
|
* </ol>
|
||||||
*/
|
*/
|
||||||
public class ApkFileProvider extends FileProvider {
|
public class ApkFileProvider extends FileProvider {
|
||||||
|
|
||||||
@ -52,7 +56,7 @@ public class ApkFileProvider extends FileProvider {
|
|||||||
|
|
||||||
public static Uri getSafeUri(Context context, PackageInfo packageInfo) throws IOException {
|
public static Uri getSafeUri(Context context, PackageInfo packageInfo) throws IOException {
|
||||||
SanitizedFile tempApkFile = ApkCache.copyInstalledApkToFiles(context, packageInfo);
|
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,
|
context.grantUriPermission(PrivilegedInstaller.PRIVILEGED_EXTENSION_PACKAGE_NAME,
|
||||||
apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
context.grantUriPermission("com.android.bluetooth", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
context.grantUriPermission("com.android.bluetooth", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
context.grantUriPermission("com.mediatek.bluetooth", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
return apkUri;
|
return apkUri;
|
||||||
|
} else {
|
||||||
|
tempFile.setReadable(true, false);
|
||||||
|
return Uri.fromFile(tempFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
tempFile.setReadable(true, false);
|
|
||||||
|
|
||||||
return Uri.fromFile(tempFile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ import android.net.Uri;
|
|||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import org.fdroid.fdroid.BuildConfig;
|
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.data.Apk;
|
import org.fdroid.fdroid.data.Apk;
|
||||||
|
|
||||||
@ -47,8 +46,7 @@ import java.util.List;
|
|||||||
public class InstallHistoryService extends IntentService {
|
public class InstallHistoryService extends IntentService {
|
||||||
public static final String TAG = "InstallHistoryService";
|
public static final String TAG = "InstallHistoryService";
|
||||||
|
|
||||||
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".installer";
|
public static final Uri LOG_URI = Uri.parse("content://" + Installer.AUTHORITY + "/install_history/all");
|
||||||
public static final Uri LOG_URI = Uri.parse("content://" + AUTHORITY + "/install_history/all");
|
|
||||||
|
|
||||||
private static BroadcastReceiver broadcastReceiver;
|
private static BroadcastReceiver broadcastReceiver;
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ import android.os.PatternMatcher;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import org.fdroid.fdroid.BuildConfig;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.data.Apk;
|
import org.fdroid.fdroid.data.Apk;
|
||||||
import org.fdroid.fdroid.data.ApkProvider;
|
import org.fdroid.fdroid.data.ApkProvider;
|
||||||
@ -51,6 +52,8 @@ public abstract class Installer {
|
|||||||
final Context context;
|
final Context context;
|
||||||
final Apk apk;
|
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_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_COMPLETE = "org.fdroid.fdroid.installer.Installer.action.INSTALL_COMPLETE";
|
||||||
public static final String ACTION_INSTALL_INTERRUPTED = "org.fdroid.fdroid.installer.Installer.action.INSTALL_INTERRUPTED";
|
public static final String ACTION_INSTALL_INTERRUPTED = "org.fdroid.fdroid.installer.Installer.action.INSTALL_INTERRUPTED";
|
||||||
|
@ -7,10 +7,12 @@ import android.content.DialogInterface;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.support.annotation.DrawableRes;
|
import android.support.annotation.DrawableRes;
|
||||||
import android.support.annotation.LayoutRes;
|
import android.support.annotation.LayoutRes;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v4.content.FileProvider;
|
||||||
import android.support.v4.view.ViewCompat;
|
import android.support.v4.view.ViewCompat;
|
||||||
import android.support.v4.widget.TextViewCompat;
|
import android.support.v4.widget.TextViewCompat;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
@ -28,6 +30,7 @@ import android.util.Log;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
@ -35,6 +38,7 @@ import android.widget.ProgressBar;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.fdroid.fdroid.Preferences;
|
import org.fdroid.fdroid.Preferences;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.Utils;
|
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.App;
|
||||||
import org.fdroid.fdroid.data.InstalledAppProvider;
|
import org.fdroid.fdroid.data.InstalledAppProvider;
|
||||||
import org.fdroid.fdroid.data.RepoProvider;
|
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.AppDiff;
|
||||||
import org.fdroid.fdroid.privileged.views.AppSecurityPermissions;
|
import org.fdroid.fdroid.privileged.views.AppSecurityPermissions;
|
||||||
import org.fdroid.fdroid.views.main.MainActivity;
|
import org.fdroid.fdroid.views.main.MainActivity;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -512,9 +518,42 @@ public class AppDetailsRecyclerViewAdapter
|
|||||||
buttonPrimaryView.setText(R.string.menu_upgrade);
|
buttonPrimaryView.setText(R.string.menu_upgrade);
|
||||||
buttonPrimaryView.setOnClickListener(onUpgradeClickListener);
|
buttonPrimaryView.setOnClickListener(onUpgradeClickListener);
|
||||||
} else {
|
} else {
|
||||||
|
Apk mediaApk = app.getMediaApkifInstalled(context);
|
||||||
if (context.getPackageManager().getLaunchIntentForPackage(app.packageName) != null) {
|
if (context.getPackageManager().getLaunchIntentForPackage(app.packageName) != null) {
|
||||||
buttonPrimaryView.setText(R.string.menu_launch);
|
buttonPrimaryView.setText(R.string.menu_launch);
|
||||||
buttonPrimaryView.setOnClickListener(onLaunchClickListener);
|
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 {
|
} else {
|
||||||
buttonPrimaryView.setVisibility(View.GONE);
|
buttonPrimaryView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import android.support.annotation.Nullable;
|
|||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.data.App;
|
import org.fdroid.fdroid.data.App;
|
||||||
import org.fdroid.fdroid.data.Schema;
|
import org.fdroid.fdroid.data.Schema;
|
||||||
@ -58,4 +57,13 @@ class InstalledAppListAdapter extends RecyclerView.Adapter<InstalledAppListItemC
|
|||||||
this.cursor = cursor;
|
this.cursor = cursor;
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public App getItem(int position) {
|
||||||
|
if (cursor == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
cursor.moveToPosition(position);
|
||||||
|
return new App(cursor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,17 +22,20 @@ package org.fdroid.fdroid.views.installed;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.app.ShareCompat;
|
||||||
import android.support.v4.content.CursorLoader;
|
import android.support.v4.content.CursorLoader;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.data.App;
|
||||||
import org.fdroid.fdroid.data.AppProvider;
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
import org.fdroid.fdroid.data.Schema;
|
import org.fdroid.fdroid.data.Schema;
|
||||||
|
|
||||||
@ -100,4 +103,35 @@ public class InstalledAppsActivity extends AppCompatActivity implements LoaderMa
|
|||||||
adapter.setApps(null);
|
adapter.setApps(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.installed_apps, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.menu_share:
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
stringBuilder.append("packageName,versionCode,versionName\n");
|
||||||
|
for (int i = 0; i < adapter.getItemCount(); i++) {
|
||||||
|
App app = adapter.getItem(i);
|
||||||
|
if (app != null) {
|
||||||
|
stringBuilder.append(app.packageName).append(',')
|
||||||
|
.append(app.installedVersionCode).append(',')
|
||||||
|
.append(app.installedVersionName).append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ShareCompat.IntentBuilder intentBuilder = ShareCompat.IntentBuilder.from(this)
|
||||||
|
.setSubject(getString(R.string.send_installed_apps))
|
||||||
|
.setChooserTitle(R.string.send_installed_apps)
|
||||||
|
.setText(stringBuilder.toString())
|
||||||
|
.setType("text/csv");
|
||||||
|
startActivity(intentBuilder.getIntent());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
9
app/src/main/res/menu/installed_apps.xml
Normal file
9
app/src/main/res/menu/installed_apps.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_share"
|
||||||
|
android:icon="@drawable/ic_share_white"
|
||||||
|
android:title="@string/menu_share"
|
||||||
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
|
</menu>
|
@ -110,6 +110,8 @@ This often occurs with apps installed via Google Play or other sources, if they
|
|||||||
<string name="app_list__dismiss_downloading_app">Download canceled</string>
|
<string name="app_list__dismiss_downloading_app">Download canceled</string>
|
||||||
|
|
||||||
<string name="installed_apps__activity_title">Installed Apps</string>
|
<string name="installed_apps__activity_title">Installed Apps</string>
|
||||||
|
<string name="send_installed_apps">Share installed apps</string>
|
||||||
|
<string name="send_installed_apps_csv">Apps installed by F-Droid as CSV file</string>
|
||||||
<string name="installed_app__updates_ignored">Updates ignored</string>
|
<string name="installed_app__updates_ignored">Updates ignored</string>
|
||||||
<string name="installed_app__updates_ignored_for_suggested_version">Updates ignored for Version %1$s</string>
|
<string name="installed_app__updates_ignored_for_suggested_version">Updates ignored for Version %1$s</string>
|
||||||
<!-- The inline download button shown in the "Updates" screen only uses an icon and so requires
|
<!-- The inline download button shown in the "Updates" screen only uses an icon and so requires
|
||||||
@ -171,6 +173,7 @@ This often occurs with apps installed via Google Play or other sources, if they
|
|||||||
<string name="menu_add_repo">New Repository</string>
|
<string name="menu_add_repo">New Repository</string>
|
||||||
|
|
||||||
<string name="menu_launch">Run</string>
|
<string name="menu_launch">Run</string>
|
||||||
|
<string name="menu_open">Open</string>
|
||||||
<string name="menu_share">Share</string>
|
<string name="menu_share">Share</string>
|
||||||
<string name="menu_install">Install</string>
|
<string name="menu_install">Install</string>
|
||||||
<string name="menu_uninstall">Uninstall</string>
|
<string name="menu_uninstall">Uninstall</string>
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<paths>
|
|
||||||
<cache-path
|
|
||||||
name="install_history"
|
|
||||||
path="install_history" />
|
|
||||||
</paths>
|
|
5
app/src/main/res/xml/installer_file_provider.xml
Normal file
5
app/src/main/res/xml/installer_file_provider.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths>
|
||||||
|
<cache-path name="install_history" path="install_history"/>
|
||||||
|
<external-path name="external" path="/"/>
|
||||||
|
</paths>
|
Loading…
x
Reference in New Issue
Block a user