Redesign PrivilegedInstaller
* use new local broadcasts * show permission screen before download * display permission screen as dialog
This commit is contained in:
parent
592cd0424a
commit
2776b86050
@ -316,12 +316,18 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".privileged.views.InstallConfirmActivity"
|
android:name=".privileged.views.InstallConfirmActivity"
|
||||||
android:label="@string/menu_install"
|
android:label="@string/menu_install"
|
||||||
|
android:theme="@style/MinWithDialogBaseThemeLight"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
android:parentActivityName=".FDroid"
|
android:parentActivityName=".FDroid"
|
||||||
android:configChanges="layoutDirection|locale" >
|
android:configChanges="layoutDirection|locale" >
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value=".FDroid" />
|
android:value=".FDroid" />
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".privileged.views.UninstallDialogActivity"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:theme="@style/AppThemeTransparent" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".views.ManageReposActivity"
|
android:name=".views.ManageReposActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
@ -79,7 +79,6 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
|||||||
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
|
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
|
||||||
|
|
||||||
import org.fdroid.fdroid.Utils.CommaSeparatedList;
|
import org.fdroid.fdroid.Utils.CommaSeparatedList;
|
||||||
import org.fdroid.fdroid.compat.PackageManagerCompat;
|
|
||||||
import org.fdroid.fdroid.data.Apk;
|
import org.fdroid.fdroid.data.Apk;
|
||||||
import org.fdroid.fdroid.data.ApkProvider;
|
import org.fdroid.fdroid.data.ApkProvider;
|
||||||
import org.fdroid.fdroid.data.App;
|
import org.fdroid.fdroid.data.App;
|
||||||
@ -88,6 +87,7 @@ 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.installer.Installer;
|
||||||
import org.fdroid.fdroid.installer.InstallManagerService;
|
import org.fdroid.fdroid.installer.InstallManagerService;
|
||||||
|
import org.fdroid.fdroid.installer.InstallerFactory;
|
||||||
import org.fdroid.fdroid.installer.InstallerService;
|
import org.fdroid.fdroid.installer.InstallerService;
|
||||||
import org.fdroid.fdroid.net.Downloader;
|
import org.fdroid.fdroid.net.Downloader;
|
||||||
import org.fdroid.fdroid.net.DownloaderService;
|
import org.fdroid.fdroid.net.DownloaderService;
|
||||||
@ -101,6 +101,8 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
private static final String TAG = "AppDetails";
|
private static final String TAG = "AppDetails";
|
||||||
|
|
||||||
private static final int REQUEST_ENABLE_BLUETOOTH = 2;
|
private static final int REQUEST_ENABLE_BLUETOOTH = 2;
|
||||||
|
private static final int REQUEST_PERMISSION_DIALOG = 3;
|
||||||
|
private static final int REQUEST_UNINSTALL_DIALOG = 4;
|
||||||
|
|
||||||
public static final String EXTRA_APPID = "appid";
|
public static final String EXTRA_APPID = "appid";
|
||||||
public static final String EXTRA_FROM = "from";
|
public static final String EXTRA_FROM = "from";
|
||||||
@ -983,6 +985,19 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initiateInstall(Apk apk) {
|
private void initiateInstall(Apk apk) {
|
||||||
|
Installer installer = InstallerFactory.create(this);
|
||||||
|
Intent intent = installer.getPermissionScreen(apk);
|
||||||
|
if (intent != null) {
|
||||||
|
// permission screen required
|
||||||
|
Utils.debugLog(TAG, "permission screen required");
|
||||||
|
startActivityForResult(intent, REQUEST_PERMISSION_DIALOG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
startInstall(apk);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startInstall(Apk apk) {
|
||||||
activeDownloadUrlString = apk.getUrl();
|
activeDownloadUrlString = apk.getUrl();
|
||||||
registerDownloaderReceivers();
|
registerDownloaderReceivers();
|
||||||
headerFragment.startProgress();
|
headerFragment.startProgress();
|
||||||
@ -990,9 +1005,22 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void uninstallApk(String packageName) {
|
private void uninstallApk(String packageName) {
|
||||||
|
Installer installer = InstallerFactory.create(this);
|
||||||
|
Intent intent = installer.getUninstallScreen(packageName);
|
||||||
|
if (intent != null) {
|
||||||
|
// uninstall screen required
|
||||||
|
Utils.debugLog(TAG, "screen screen required");
|
||||||
|
startActivityForResult(intent, REQUEST_UNINSTALL_DIALOG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
startUninstall();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startUninstall() {
|
||||||
localBroadcastManager.registerReceiver(uninstallReceiver,
|
localBroadcastManager.registerReceiver(uninstallReceiver,
|
||||||
Installer.getUninstallIntentFilter(packageName));
|
Installer.getUninstallIntentFilter(app.packageName));
|
||||||
InstallerService.uninstall(context, packageName);
|
InstallerService.uninstall(context, app.packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void launchApk(String packageName) {
|
private void launchApk(String packageName) {
|
||||||
@ -1016,6 +1044,18 @@ public class AppDetails extends AppCompatActivity {
|
|||||||
case REQUEST_ENABLE_BLUETOOTH:
|
case REQUEST_ENABLE_BLUETOOTH:
|
||||||
fdroidApp.sendViaBluetooth(this, resultCode, app.packageName);
|
fdroidApp.sendViaBluetooth(this, resultCode, app.packageName);
|
||||||
break;
|
break;
|
||||||
|
case REQUEST_PERMISSION_DIALOG:
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
Uri uri = data.getData();
|
||||||
|
Apk apk = ApkProvider.Helper.find(this, uri, ApkProvider.DataColumns.ALL);
|
||||||
|
startInstall(apk);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case REQUEST_UNINSTALL_DIALOG:
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
startUninstall();
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +125,23 @@ public class FDroidApp extends Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void applyDialogTheme(Activity activity) {
|
||||||
|
activity.setTheme(getCurDialogThemeResId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getCurDialogThemeResId() {
|
||||||
|
switch (curTheme) {
|
||||||
|
case light:
|
||||||
|
return R.style.MinWithDialogBaseThemeLight;
|
||||||
|
case dark:
|
||||||
|
return R.style.MinWithDialogBaseThemeDark;
|
||||||
|
case night:
|
||||||
|
return R.style.MinWithDialogBaseThemeDark;
|
||||||
|
default:
|
||||||
|
return R.style.MinWithDialogBaseThemeLight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void enableSpongyCastle() {
|
public static void enableSpongyCastle() {
|
||||||
Security.addProvider(SPONGYCASTLE_PROVIDER);
|
Security.addProvider(SPONGYCASTLE_PROVIDER);
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,7 @@ import java.security.cert.CertificateEncodingException;
|
|||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Formatter;
|
import java.util.Formatter;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -457,6 +458,19 @@ public final class Utils {
|
|||||||
return splitter.iterator();
|
return splitter.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ArrayList<String> toArrayList() {
|
||||||
|
ArrayList<String> out = new ArrayList<>();
|
||||||
|
for (String element : this) {
|
||||||
|
out.add(element);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] toArray() {
|
||||||
|
ArrayList<String> list = toArrayList();
|
||||||
|
return list.toArray(new String[list.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean contains(String v) {
|
public boolean contains(String v) {
|
||||||
for (final String s : this) {
|
for (final String s : this) {
|
||||||
if (s.equals(v)) {
|
if (s.equals(v)) {
|
||||||
|
@ -107,8 +107,12 @@ public class ApkProvider extends FDroidProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Apk find(Context context, String packageName, int versionCode, String[] projection) {
|
public static Apk find(Context context, String packageName, int versionCode, String[] projection) {
|
||||||
ContentResolver resolver = context.getContentResolver();
|
|
||||||
final Uri uri = getContentUri(packageName, versionCode);
|
final Uri uri = getContentUri(packageName, versionCode);
|
||||||
|
return find(context, uri, projection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Apk find(Context context, Uri uri, String[] projection) {
|
||||||
|
ContentResolver resolver = context.getContentResolver();
|
||||||
Cursor cursor = resolver.query(uri, projection, null, null, null);
|
Cursor cursor = resolver.query(uri, projection, null, null, null);
|
||||||
Apk apk = null;
|
Apk apk = null;
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
|
@ -85,4 +85,9 @@ public class DefaultInstaller extends Installer {
|
|||||||
sendBroadcastUninstall(packageName,
|
sendBroadcastUninstall(packageName,
|
||||||
Installer.ACTION_UNINSTALL_USER_INTERACTION, uninstallPendingIntent);
|
Installer.ACTION_UNINSTALL_USER_INTERACTION, uninstallPendingIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isUnattended() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,4 +96,9 @@ public class ExtensionInstaller extends Installer {
|
|||||||
// don't use broadcasts for the rest of this special installer
|
// don't use broadcasts for the rest of this special installer
|
||||||
sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_COMPLETE);
|
sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_COMPLETE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isUnattended() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,8 @@ import org.fdroid.fdroid.data.ApkProvider;
|
|||||||
import org.fdroid.fdroid.data.SanitizedFile;
|
import org.fdroid.fdroid.data.SanitizedFile;
|
||||||
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.privileged.views.InstallConfirmActivity;
|
||||||
|
import org.fdroid.fdroid.privileged.views.UninstallDialogActivity;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -153,48 +155,55 @@ public abstract class Installer {
|
|||||||
return Uri.fromFile(sanitizedApkFile);
|
return Uri.fromFile(sanitizedApkFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PendingIntent getPermissionScreen(Apk apk) {
|
public Intent getPermissionScreen(Apk apk) {
|
||||||
// old code:
|
if (!isUnattended()) {
|
||||||
// Uri packageUri = Uri.fromFile(apkFile);
|
return null;
|
||||||
// int count = newPermissionCount(packageUri);
|
}
|
||||||
// if (count < 0) {
|
|
||||||
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
|
||||||
// InstallerCallback.ERROR_CODE_CANNOT_PARSE);
|
|
||||||
|
|
||||||
// install_error_cannot_parse
|
int count = newPermissionCount(apk);
|
||||||
|
if (count > 0) {
|
||||||
|
Uri uri = ApkProvider.getContentUri(apk);
|
||||||
|
Intent intent = new Intent(mContext, InstallConfirmActivity.class);
|
||||||
|
intent.setData(uri);
|
||||||
|
|
||||||
// return;
|
return intent;
|
||||||
// }
|
} else {
|
||||||
// if (count > 0) {
|
// no permission screen needed!
|
||||||
// Intent intent = new Intent(mContext, InstallConfirmActivity.class);
|
return null;
|
||||||
// intent.setData(packageUri);
|
}
|
||||||
// mActivity.startActivityForResult(intent, REQUEST_CONFIRM_PERMS);
|
|
||||||
// } else {
|
|
||||||
// try {
|
|
||||||
// doInstallPackageInternal(packageUri);
|
|
||||||
// } catch (InstallFailedException e) {
|
|
||||||
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
|
||||||
// InstallerCallback.ERROR_CODE_OTHER);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int newPermissionCount(Apk apk) {
|
||||||
|
// TODO: requires targetSdk in Apk class/database
|
||||||
|
// boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion
|
||||||
|
// >= Build.VERSION_CODES.M;
|
||||||
|
// if (supportsRuntimePermissions) {
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
|
||||||
private int newPermissionCount(Uri packageUri) {
|
AppDiff appDiff = new AppDiff(mContext.getPackageManager(), apk);
|
||||||
AppDiff appDiff = new AppDiff(mContext.getPackageManager(), packageUri);
|
|
||||||
if (appDiff.mPkgInfo == null) {
|
if (appDiff.mPkgInfo == null) {
|
||||||
// could not get diff because we couldn't parse the package
|
// could not get diff because we couldn't parse the package
|
||||||
return -1;
|
throw new RuntimeException("cannot parse!");
|
||||||
}
|
}
|
||||||
AppSecurityPermissions perms = new AppSecurityPermissions(mContext, appDiff.mPkgInfo);
|
AppSecurityPermissions perms = new AppSecurityPermissions(mContext, appDiff.mPkgInfo);
|
||||||
if (appDiff.mInstalledAppInfo != null) {
|
if (appDiff.mInstalledAppInfo != null) {
|
||||||
// update to an existing app
|
// update to an existing app
|
||||||
return perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW);
|
return perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW);
|
||||||
}
|
}
|
||||||
// default: even if there aren't any permissions, we want to make the
|
// new app install
|
||||||
// user always confirm installing new apps
|
return perms.getPermissionCount(AppSecurityPermissions.WHICH_ALL);
|
||||||
return 1;
|
}
|
||||||
|
|
||||||
|
public Intent getUninstallScreen(String packageName) {
|
||||||
|
if (!isUnattended()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(mContext, UninstallDialogActivity.class);
|
||||||
|
intent.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName);
|
||||||
|
|
||||||
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -287,4 +296,6 @@ public abstract class Installer {
|
|||||||
|
|
||||||
protected abstract void uninstallPackage(String packageName);
|
protected abstract void uninstallPackage(String packageName);
|
||||||
|
|
||||||
|
protected abstract boolean isUnattended();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -238,7 +238,7 @@ public class PrivilegedInstaller extends Installer {
|
|||||||
private static final HashMap<Integer, String> sUninstallReturnCodes;
|
private static final HashMap<Integer, String> sUninstallReturnCodes;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// Descriptions extrgacted from the source code comments in AOSP
|
// Descriptions extracted from the source code comments in AOSP
|
||||||
sUninstallReturnCodes = new HashMap<>();
|
sUninstallReturnCodes = new HashMap<>();
|
||||||
sUninstallReturnCodes.put(DELETE_SUCCEEDED,
|
sUninstallReturnCodes.put(DELETE_SUCCEEDED,
|
||||||
"Success");
|
"Success");
|
||||||
@ -324,6 +324,7 @@ public class PrivilegedInstaller extends Installer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void installPackage(final Uri uri, final Uri originatingUri, String packageName) {
|
protected void installPackage(final Uri uri, final Uri originatingUri, String packageName) {
|
||||||
|
sendBroadcastInstall(uri, originatingUri, Installer.ACTION_INSTALL_STARTED);
|
||||||
|
|
||||||
final Uri sanitizedUri;
|
final Uri sanitizedUri;
|
||||||
try {
|
try {
|
||||||
@ -374,6 +375,8 @@ public class PrivilegedInstaller extends Installer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void uninstallPackage(final String packageName) {
|
protected void uninstallPackage(final String packageName) {
|
||||||
|
sendBroadcastUninstall(packageName, Installer.ACTION_UNINSTALL_STARTED);
|
||||||
|
|
||||||
ApplicationInfo appInfo;
|
ApplicationInfo appInfo;
|
||||||
try {
|
try {
|
||||||
//noinspection WrongConstant (lint is actually wrong here!)
|
//noinspection WrongConstant (lint is actually wrong here!)
|
||||||
@ -466,33 +469,9 @@ public class PrivilegedInstaller extends Installer {
|
|||||||
Context.BIND_AUTO_CREATE);
|
Context.BIND_AUTO_CREATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override
|
@Override
|
||||||
// public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
|
protected boolean isUnattended() {
|
||||||
// switch (requestCode) {
|
return true;
|
||||||
// case REQUEST_CONFIRM_PERMS:
|
}
|
||||||
// if (resultCode == Activity.RESULT_OK) {
|
|
||||||
// final Uri packageUri = data.getData();
|
|
||||||
// try {
|
|
||||||
// doInstallPackageInternal(packageUri);
|
|
||||||
// } catch (InstallFailedException e) {
|
|
||||||
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
|
||||||
// InstallerCallback.ERROR_CODE_OTHER);
|
|
||||||
// }
|
|
||||||
// } else if (resultCode == InstallConfirmActivity.RESULT_CANNOT_PARSE) {
|
|
||||||
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
|
||||||
// InstallerCallback.ERROR_CODE_CANNOT_PARSE);
|
|
||||||
|
|
||||||
// install_error_cannot_parse
|
|
||||||
|
|
||||||
// } else { // Activity.RESULT_CANCELED
|
|
||||||
// mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
|
||||||
// InstallerCallback.ERROR_CODE_CANCELED);
|
|
||||||
// }
|
|
||||||
// return true;
|
|
||||||
// default:
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,18 @@
|
|||||||
|
|
||||||
package org.fdroid.fdroid.privileged.views;
|
package org.fdroid.fdroid.privileged.views;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.data.Apk;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
public class AppDiff {
|
public class AppDiff {
|
||||||
|
|
||||||
private final PackageManager mPm;
|
private final PackageManager mPm;
|
||||||
@ -30,6 +37,29 @@ public class AppDiff {
|
|||||||
|
|
||||||
public ApplicationInfo mInstalledAppInfo;
|
public ApplicationInfo mInstalledAppInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor based on F-Droids Apk object
|
||||||
|
*/
|
||||||
|
public AppDiff(PackageManager mPm, Apk apk) {
|
||||||
|
this.mPm = mPm;
|
||||||
|
|
||||||
|
if (apk.permissions == null) {
|
||||||
|
throw new RuntimeException("apk.permissions is null");
|
||||||
|
}
|
||||||
|
mPkgInfo = new PackageInfo();
|
||||||
|
mPkgInfo.packageName = apk.packageName;
|
||||||
|
mPkgInfo.applicationInfo = new ApplicationInfo();
|
||||||
|
|
||||||
|
// TODO: duplicate code with Permission.fdroidToAndroid
|
||||||
|
ArrayList<String> permissionsFixed = new ArrayList<>();
|
||||||
|
for (String perm : apk.permissions.toArrayList()) {
|
||||||
|
permissionsFixed.add("android.permission." + perm);
|
||||||
|
}
|
||||||
|
mPkgInfo.requestedPermissions = permissionsFixed.toArray(new String[permissionsFixed.size()]);
|
||||||
|
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
public AppDiff(PackageManager mPm, Uri mPackageURI) {
|
public AppDiff(PackageManager mPm, Uri mPackageURI) {
|
||||||
this.mPm = mPm;
|
this.mPm = mPm;
|
||||||
|
|
||||||
@ -55,7 +85,7 @@ public class AppDiff {
|
|||||||
String pkgName = mPkgInfo.packageName;
|
String pkgName = mPkgInfo.packageName;
|
||||||
// Check if there is already a package on the device with this name
|
// Check if there is already a package on the device with this name
|
||||||
// but it has been renamed to something else.
|
// but it has been renamed to something else.
|
||||||
final String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] {pkgName});
|
final String[] oldName = mPm.canonicalToCurrentPackageNames(new String[]{pkgName});
|
||||||
if (oldName != null && oldName.length > 0 && oldName[0] != null) {
|
if (oldName != null && oldName.length > 0 && oldName[0] != null) {
|
||||||
pkgName = oldName[0];
|
pkgName = oldName[0];
|
||||||
mPkgInfo.packageName = pkgName;
|
mPkgInfo.packageName = pkgName;
|
||||||
|
@ -235,8 +235,7 @@ public class AppSecurityPermissions {
|
|||||||
try {
|
try {
|
||||||
installedPkgInfo = mPm.getPackageInfo(info.packageName,
|
installedPkgInfo = mPm.getPackageInfo(info.packageName,
|
||||||
PackageManager.GET_PERMISSIONS);
|
PackageManager.GET_PERMISSIONS);
|
||||||
} catch (NameNotFoundException e) {
|
} catch (NameNotFoundException ignored) {
|
||||||
throw new RuntimeException("NameNotFoundException during GET_PERMISSIONS!");
|
|
||||||
}
|
}
|
||||||
extractPerms(info, permSet, installedPkgInfo);
|
extractPerms(info, permSet, installedPkgInfo);
|
||||||
}
|
}
|
||||||
|
@ -18,16 +18,16 @@
|
|||||||
|
|
||||||
package org.fdroid.fdroid.privileged.views;
|
package org.fdroid.fdroid.privileged.views;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.DialogInterface.OnCancelListener;
|
import android.content.DialogInterface.OnCancelListener;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.Bitmap;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.FragmentActivity;
|
||||||
import android.support.v4.view.ViewPager;
|
import android.support.v4.view.ViewPager;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -38,15 +38,23 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TabHost;
|
import android.widget.TabHost;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||||
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
|
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
|
||||||
|
|
||||||
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.Apk;
|
||||||
|
import org.fdroid.fdroid.data.ApkProvider;
|
||||||
|
import org.fdroid.fdroid.data.App;
|
||||||
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NOTES:
|
* NOTES:
|
||||||
* Parts are based on AOSP src/com/android/packageinstaller/PackageInstallerActivity.java
|
* Parts are based on AOSP src/com/android/packageinstaller/PackageInstallerActivity.java
|
||||||
* latest included commit: c23d802958158d522e7350321ad9ac6d43013883
|
* latest included commit: c23d802958158d522e7350321ad9ac6d43013883
|
||||||
*/
|
*/
|
||||||
public class InstallConfirmActivity extends Activity implements OnCancelListener, OnClickListener {
|
public class InstallConfirmActivity extends FragmentActivity implements OnCancelListener, OnClickListener {
|
||||||
|
|
||||||
public static final int RESULT_CANNOT_PARSE = RESULT_FIRST_USER + 1;
|
public static final int RESULT_CANNOT_PARSE = RESULT_FIRST_USER + 1;
|
||||||
|
|
||||||
@ -67,16 +75,27 @@ public class InstallConfirmActivity extends Activity implements OnCancelListener
|
|||||||
private static final String TAB_ID_ALL = "all";
|
private static final String TAB_ID_ALL = "all";
|
||||||
private static final String TAB_ID_NEW = "new";
|
private static final String TAB_ID_NEW = "new";
|
||||||
|
|
||||||
|
private App mApp;
|
||||||
|
|
||||||
|
private final DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder()
|
||||||
|
.cacheInMemory(true)
|
||||||
|
.cacheOnDisk(true)
|
||||||
|
.imageScaleType(ImageScaleType.NONE)
|
||||||
|
.showImageOnLoading(R.drawable.ic_repo_app_default)
|
||||||
|
.showImageForEmptyUri(R.drawable.ic_repo_app_default)
|
||||||
|
.bitmapConfig(Bitmap.Config.RGB_565)
|
||||||
|
.build();
|
||||||
|
|
||||||
private void startInstallConfirm() {
|
private void startInstallConfirm() {
|
||||||
|
|
||||||
final Drawable appIcon = mAppDiff.mPkgInfo.applicationInfo.loadIcon(mPm);
|
|
||||||
final String appLabel = (String) mAppDiff.mPkgInfo.applicationInfo.loadLabel(mPm);
|
|
||||||
|
|
||||||
View appSnippet = findViewById(R.id.app_snippet);
|
View appSnippet = findViewById(R.id.app_snippet);
|
||||||
((ImageView) appSnippet.findViewById(R.id.app_icon)).setImageDrawable(appIcon);
|
TextView appName = (TextView) appSnippet.findViewById(R.id.app_name);
|
||||||
((TextView) appSnippet.findViewById(R.id.app_name)).setText(appLabel);
|
ImageView appIcon = (ImageView) appSnippet.findViewById(R.id.app_icon);
|
||||||
|
|
||||||
TabHost tabHost = (TabHost) findViewById(android.R.id.tabhost);
|
TabHost tabHost = (TabHost) findViewById(android.R.id.tabhost);
|
||||||
|
|
||||||
|
appName.setText(mApp.name);
|
||||||
|
ImageLoader.getInstance().displayImage(mApp.iconUrlLarge, appIcon,
|
||||||
|
displayImageOptions);
|
||||||
|
|
||||||
tabHost.setup();
|
tabHost.setup();
|
||||||
ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
|
ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
|
||||||
TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
|
TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
|
||||||
@ -136,7 +155,7 @@ public class InstallConfirmActivity extends Activity implements OnCancelListener
|
|||||||
: R.string.install_confirm_update_no_perms;
|
: R.string.install_confirm_update_no_perms;
|
||||||
} else {
|
} else {
|
||||||
// This is a new application with no permissions.
|
// This is a new application with no permissions.
|
||||||
msg = R.string.install_confirm_no_perms;
|
throw new RuntimeException("no permissions requested. This screen should not appear!");
|
||||||
}
|
}
|
||||||
tabHost.setVisibility(View.GONE);
|
tabHost.setVisibility(View.GONE);
|
||||||
findViewById(R.id.filler).setVisibility(View.VISIBLE);
|
findViewById(R.id.filler).setVisibility(View.VISIBLE);
|
||||||
@ -171,20 +190,28 @@ public class InstallConfirmActivity extends Activity implements OnCancelListener
|
|||||||
protected void onCreate(Bundle icicle) {
|
protected void onCreate(Bundle icicle) {
|
||||||
super.onCreate(icicle);
|
super.onCreate(icicle);
|
||||||
|
|
||||||
((FDroidApp) getApplication()).applyTheme(this);
|
((FDroidApp) getApplication()).applyDialogTheme(this);
|
||||||
|
|
||||||
mPm = getPackageManager();
|
mPm = getPackageManager();
|
||||||
|
|
||||||
intent = getIntent();
|
intent = getIntent();
|
||||||
Uri packageURI = intent.getData();
|
Uri uri = intent.getData();
|
||||||
|
Apk apk = ApkProvider.Helper.find(this, uri, ApkProvider.DataColumns.ALL);
|
||||||
|
mApp = AppProvider.Helper.findByPackageName(getContentResolver(), apk.packageName);
|
||||||
|
|
||||||
mAppDiff = new AppDiff(mPm, packageURI);
|
mAppDiff = new AppDiff(mPm, apk);
|
||||||
if (mAppDiff.mPkgInfo == null) {
|
if (mAppDiff.mPkgInfo == null) {
|
||||||
setResult(RESULT_CANNOT_PARSE, intent);
|
setResult(RESULT_CANNOT_PARSE, intent);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
setContentView(R.layout.install_start);
|
setContentView(R.layout.install_start);
|
||||||
|
|
||||||
|
// increase dialog to full width for now
|
||||||
|
// TODO: create a better design and minimum width for tablets
|
||||||
|
getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
|
||||||
mInstallConfirm = findViewById(R.id.install_confirm_panel);
|
mInstallConfirm = findViewById(R.id.install_confirm_panel);
|
||||||
mInstallConfirm.setVisibility(View.INVISIBLE);
|
mInstallConfirm.setVisibility(View.INVISIBLE);
|
||||||
|
|
||||||
|
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 3
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.fdroid.fdroid.privileged.views;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.FragmentActivity;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.view.ContextThemeWrapper;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.installer.Installer;
|
||||||
|
|
||||||
|
public class UninstallDialogActivity extends FragmentActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
final Intent intent = getIntent();
|
||||||
|
final String packageName = intent.getStringExtra(Installer.EXTRA_PACKAGE_NAME);
|
||||||
|
|
||||||
|
PackageManager pm = getPackageManager();
|
||||||
|
|
||||||
|
ApplicationInfo appInfo;
|
||||||
|
try {
|
||||||
|
//noinspection WrongConstant (lint is actually wrong here!)
|
||||||
|
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
throw new RuntimeException("Failed to get ApplicationInfo for uninstalling");
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean isSystem = (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
|
||||||
|
final boolean isUpdate = (appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
|
||||||
|
|
||||||
|
if (isSystem && !isUpdate) {
|
||||||
|
// Cannot remove system apps unless we're uninstalling updates
|
||||||
|
throw new RuntimeException("Cannot remove system apps unless we're uninstalling updates");
|
||||||
|
}
|
||||||
|
|
||||||
|
int messageId;
|
||||||
|
if (isUpdate) {
|
||||||
|
messageId = R.string.uninstall_update_confirm;
|
||||||
|
} else {
|
||||||
|
messageId = R.string.uninstall_confirm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.setTitle(appInfo.loadLabel(pm));
|
||||||
|
builder.setIcon(appInfo.loadIcon(pm));
|
||||||
|
builder.setPositiveButton(android.R.string.ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
Intent data = new Intent();
|
||||||
|
data.putExtra(Installer.EXTRA_PACKAGE_NAME, packageName);
|
||||||
|
setResult(Activity.RESULT_OK, intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setNegativeButton(android.R.string.cancel,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
setResult(Activity.RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setOnCancelListener(
|
||||||
|
new DialogInterface.OnCancelListener() {
|
||||||
|
@Override
|
||||||
|
public void onCancel(DialogInterface dialog) {
|
||||||
|
setResult(Activity.RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setMessage(messageId);
|
||||||
|
builder.create().show();
|
||||||
|
}
|
||||||
|
}
|
@ -21,37 +21,35 @@
|
|||||||
user before it is installed.
|
user before it is installed.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
android:layout_width="match_parent"
|
||||||
android:orientation="vertical"
|
android:layout_height="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:orientation="vertical">
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/install_confirm"
|
android:id="@+id/install_confirm"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/install_confirm"
|
android:paddingLeft="16dp"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:paddingRight="16dp"
|
||||||
android:paddingLeft="16dp"
|
android:paddingTop="4dip"
|
||||||
android:paddingRight="16dp"
|
android:text="@string/install_confirm"
|
||||||
android:paddingTop="4dip" />
|
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/divider"
|
android:id="@+id/divider"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:background="?android:attr/dividerHorizontal"
|
android:background="?android:attr/dividerHorizontal"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/filler"
|
android:id="@+id/filler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:visibility="gone">
|
android:visibility="gone"></FrameLayout>
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<TabHost
|
<TabHost
|
||||||
android:id="@android:id/tabhost"
|
android:id="@android:id/tabhost"
|
||||||
@ -60,24 +58,28 @@
|
|||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<HorizontalScrollView android:id="@+id/tabscontainer"
|
<HorizontalScrollView
|
||||||
|
android:id="@+id/tabscontainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/tab_unselected_holo"
|
android:background="@drawable/tab_unselected_holo"
|
||||||
android:fillViewport="true"
|
android:fillViewport="true"
|
||||||
android:scrollbars="none">
|
android:scrollbars="none">
|
||||||
<FrameLayout android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content">
|
<FrameLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<TabWidget
|
<TabWidget
|
||||||
android:id="@android:id/tabs"
|
android:id="@android:id/tabs"
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center" />
|
android:layout_gravity="center"
|
||||||
|
android:orientation="horizontal" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</HorizontalScrollView>
|
</HorizontalScrollView>
|
||||||
|
|
||||||
@ -85,64 +87,68 @@
|
|||||||
android:id="@android:id/tabcontent"
|
android:id="@android:id/tabcontent"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="0"/>
|
android:layout_weight="0" />
|
||||||
|
|
||||||
<android.support.v4.view.ViewPager
|
<android.support.v4.view.ViewPager
|
||||||
android:id="@+id/pager"
|
android:id="@+id/pager"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"/>
|
android:layout_weight="1" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</TabHost>
|
</TabHost>
|
||||||
|
|
||||||
<!-- OK confirm and cancel buttons. -->
|
<!-- OK confirm and cancel buttons. -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:divider="?android:attr/dividerHorizontal"
|
||||||
android:divider="?android:attr/dividerHorizontal"
|
android:orientation="vertical"
|
||||||
android:showDividers="beginning">
|
android:showDividers="beginning">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
style="?android:attr/buttonBarStyle"
|
style="?android:attr/buttonBarStyle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:measureWithLargestChild="true"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/leftSpacer"
|
||||||
|
android:layout_width="0dip"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.25"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:measureWithLargestChild="true">
|
android:visibility="gone" />
|
||||||
|
|
||||||
<LinearLayout android:id="@+id/leftSpacer"
|
<Button
|
||||||
android:layout_weight="0.25"
|
android:id="@+id/cancel_button"
|
||||||
android:layout_width="0dip"
|
style="?android:attr/buttonBarButtonStyle"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="0dip"
|
||||||
android:orientation="horizontal"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="gone" />
|
android:layout_gravity="start"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:text="@string/cancel" />
|
||||||
|
|
||||||
<Button android:id="@+id/cancel_button"
|
<Button
|
||||||
android:layout_width="0dip"
|
android:id="@+id/ok_button"
|
||||||
android:layout_height="wrap_content"
|
style="?android:attr/buttonBarButtonStyle"
|
||||||
android:layout_gravity="start"
|
android:layout_width="0dip"
|
||||||
android:layout_weight="1"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/cancel"
|
android:layout_gravity="end"
|
||||||
android:maxLines="2"
|
android:layout_weight="1"
|
||||||
style="?android:attr/buttonBarButtonStyle" />
|
android:filterTouchesWhenObscured="true"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:text="@string/next" />
|
||||||
|
|
||||||
<Button android:id="@+id/ok_button"
|
<LinearLayout
|
||||||
android:layout_width="0dip"
|
android:id="@+id/rightSpacer"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="0dip"
|
||||||
android:layout_gravity="end"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="0.25"
|
||||||
android:text="@string/next"
|
android:orientation="horizontal"
|
||||||
android:maxLines="2"
|
android:visibility="gone" />
|
||||||
android:filterTouchesWhenObscured="true"
|
|
||||||
style="?android:attr/buttonBarButtonStyle" />
|
|
||||||
|
|
||||||
<LinearLayout android:id="@+id/rightSpacer"
|
|
||||||
android:layout_width="0dip"
|
|
||||||
android:layout_weight="0.25"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2008 The Android Open Source Project
|
||||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -17,59 +16,46 @@
|
|||||||
<!--
|
<!--
|
||||||
Defines the layout of the application snippet that appears on top of the
|
Defines the layout of the application snippet that appears on top of the
|
||||||
installation screens
|
installation screens
|
||||||
-->
|
--><!-- The snippet about the application - title, icon, description. -->
|
||||||
<!-- The snippet about the application - title, icon, description. -->
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
<RelativeLayout
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@+id/app_snippet"
|
android:id="@+id/app_snippet"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingLeft="16dip"
|
|
||||||
android:paddingStart="16dip"
|
|
||||||
android:paddingRight="16dip"
|
|
||||||
android:paddingEnd="16dip"
|
android:paddingEnd="16dip"
|
||||||
android:paddingTop="24dip"
|
android:paddingLeft="16dip"
|
||||||
>
|
android:paddingRight="16dip"
|
||||||
<ImageView android:id="@+id/app_icon"
|
android:paddingStart="16dip"
|
||||||
android:layout_width="32dip"
|
android:paddingTop="16dip">
|
||||||
android:layout_height="32dip"
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/app_icon"
|
||||||
|
android:layout_width="48dip"
|
||||||
|
android:layout_height="48dip"
|
||||||
android:layout_marginLeft="8dip"
|
android:layout_marginLeft="8dip"
|
||||||
android:layout_marginStart="8dip"
|
android:layout_marginStart="8dip"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:gravity="start"
|
android:gravity="start"
|
||||||
android:scaleType="centerCrop"/>
|
android:scaleType="centerCrop"
|
||||||
<TextView android:id="@+id/app_name"
|
tools:src="@drawable/ic_launcher" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/app_name"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:ellipsize="end"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
android:paddingEnd="16dip"
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
android:paddingLeft="16dip"
|
||||||
|
android:paddingRight="16dip"
|
||||||
|
android:paddingStart="16dip"
|
||||||
android:shadowColor="@color/shadow"
|
android:shadowColor="@color/shadow"
|
||||||
android:shadowRadius="2"
|
android:shadowRadius="2"
|
||||||
android:layout_toRightOf="@id/app_icon"
|
|
||||||
android:layout_toEndOf="@id/app_icon"
|
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:layout_centerInParent="true"
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
android:paddingRight="16dip"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
android:paddingEnd="16dip"
|
tools:text="App Name" />
|
||||||
android:paddingTop="3dip"
|
|
||||||
android:paddingLeft="16dip"
|
|
||||||
android:paddingStart="16dip"
|
|
||||||
android:ellipsize="end"/>
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/top_divider"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingTop="4dip"
|
|
||||||
android:layout_below="@id/app_name">
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progress_bar"
|
|
||||||
style="?android:attr/progressBarStyleHorizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2008 The Android Open Source Project
|
||||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -21,36 +20,34 @@
|
|||||||
user before it is installed.
|
user before it is installed.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
android:layout_width="match_parent"
|
||||||
android:orientation="vertical"
|
android:layout_height="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:orientation="vertical">
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/install_confirm"
|
android:id="@+id/install_confirm"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/install_confirm"
|
android:paddingLeft="16dp"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:paddingRight="16dp"
|
||||||
android:paddingLeft="16dp"
|
android:paddingTop="4dip"
|
||||||
android:paddingRight="16dp"
|
android:text="@string/install_confirm"
|
||||||
android:paddingTop="4dip" />
|
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/divider"
|
android:id="@+id/divider"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/filler"
|
android:id="@+id/filler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:visibility="gone">
|
android:visibility="gone"></FrameLayout>
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<TabHost
|
<TabHost
|
||||||
android:id="@android:id/tabhost"
|
android:id="@android:id/tabhost"
|
||||||
@ -59,24 +56,28 @@
|
|||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<HorizontalScrollView android:id="@+id/tabscontainer"
|
<HorizontalScrollView
|
||||||
|
android:id="@+id/tabscontainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/tab_unselected_holo"
|
android:background="@drawable/tab_unselected_holo"
|
||||||
android:fillViewport="true"
|
android:fillViewport="true"
|
||||||
android:scrollbars="none">
|
android:scrollbars="none">
|
||||||
<FrameLayout android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content">
|
<FrameLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<TabWidget
|
<TabWidget
|
||||||
android:id="@android:id/tabs"
|
android:id="@android:id/tabs"
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center" />
|
android:layout_gravity="center"
|
||||||
|
android:orientation="horizontal" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</HorizontalScrollView>
|
</HorizontalScrollView>
|
||||||
|
|
||||||
@ -84,63 +85,65 @@
|
|||||||
android:id="@android:id/tabcontent"
|
android:id="@android:id/tabcontent"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="0"/>
|
android:layout_weight="0" />
|
||||||
|
|
||||||
<android.support.v4.view.ViewPager
|
<android.support.v4.view.ViewPager
|
||||||
android:id="@+id/pager"
|
android:id="@+id/pager"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"/>
|
android:layout_weight="1" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</TabHost>
|
</TabHost>
|
||||||
|
|
||||||
<!-- OK confirm and cancel buttons. -->
|
<!-- OK confirm and cancel buttons. -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:divider="?android:attr/dividerHorizontal"
|
||||||
android:divider="?android:attr/dividerHorizontal"
|
android:orientation="vertical"
|
||||||
android:showDividers="beginning">
|
android:showDividers="beginning">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:measureWithLargestChild="true"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/leftSpacer"
|
||||||
|
android:layout_width="0dip"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.25"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:measureWithLargestChild="true">
|
android:visibility="gone" />
|
||||||
|
|
||||||
<LinearLayout android:id="@+id/leftSpacer"
|
<Button
|
||||||
android:layout_weight="0.25"
|
android:id="@+id/cancel_button"
|
||||||
android:layout_width="0dip"
|
android:layout_width="0dip"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:layout_gravity="start"
|
||||||
android:visibility="gone" />
|
android:layout_weight="1"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:text="@string/cancel" />
|
||||||
|
|
||||||
<Button android:id="@+id/cancel_button"
|
<Button
|
||||||
android:layout_width="0dip"
|
android:id="@+id/ok_button"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="0dip"
|
||||||
android:layout_gravity="start"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_gravity="end"
|
||||||
android:text="@string/cancel"
|
android:layout_weight="1"
|
||||||
android:maxLines="2"
|
android:filterTouchesWhenObscured="true"
|
||||||
/>
|
android:maxLines="2"
|
||||||
|
android:text="@string/next" />
|
||||||
|
|
||||||
<Button android:id="@+id/ok_button"
|
<LinearLayout
|
||||||
android:layout_width="0dip"
|
android:id="@+id/rightSpacer"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="0dip"
|
||||||
android:layout_gravity="end"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="0.25"
|
||||||
android:text="@string/next"
|
android:orientation="horizontal"
|
||||||
android:maxLines="2"
|
android:visibility="gone" />
|
||||||
android:filterTouchesWhenObscured="true"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<LinearLayout android:id="@+id/rightSpacer"
|
|
||||||
android:layout_width="0dip"
|
|
||||||
android:layout_weight="0.25"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2008 The Android Open Source Project
|
||||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -14,24 +13,22 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<include
|
<include
|
||||||
|
android:id="@+id/app_snippet"
|
||||||
layout="@layout/install_app_details"
|
layout="@layout/install_app_details"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content" />
|
||||||
android:id="@+id/app_snippet"/>
|
|
||||||
|
|
||||||
<include
|
<include
|
||||||
layout="@layout/install_confirm"
|
|
||||||
android:id="@+id/install_confirm_panel"
|
android:id="@+id/install_confirm_panel"
|
||||||
|
layout="@layout/install_confirm"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/app_snippet"
|
android:layout_below="@id/app_snippet" />
|
||||||
android:layout_alignParentBottom="true"/>
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
||||||
|
@ -268,7 +268,6 @@
|
|||||||
<string name="root_access_denied_title">Root access denied</string>
|
<string name="root_access_denied_title">Root access denied</string>
|
||||||
<string name="root_access_denied_body">Either your Android device is not rooted or you have denied root access for F-Droid.</string>
|
<string name="root_access_denied_body">Either your Android device is not rooted or you have denied root access for F-Droid.</string>
|
||||||
<string name="install_error_unknown">Failed to install due to an unknown error</string>
|
<string name="install_error_unknown">Failed to install due to an unknown error</string>
|
||||||
<string name="install_error_cannot_parse">An error occurred while parsing the package</string>
|
|
||||||
<string name="uninstall_error_unknown">Failed to uninstall due to an unknown error</string>
|
<string name="uninstall_error_unknown">Failed to uninstall due to an unknown error</string>
|
||||||
<string name="system_install_denied_title">F-Droid Privileged Extension is not available</string>
|
<string name="system_install_denied_title">F-Droid Privileged Extension is not available</string>
|
||||||
<string name="system_install_denied_body">This option is only available when F-Droid Privileged Extension is installed.</string>
|
<string name="system_install_denied_body">This option is only available when F-Droid Privileged Extension is installed.</string>
|
||||||
@ -339,10 +338,7 @@
|
|||||||
|
|
||||||
<string name="tap_to_install_format">Tap to install %s</string>
|
<string name="tap_to_install_format">Tap to install %s</string>
|
||||||
<string name="tap_to_update_format">Tap to update %s</string>
|
<string name="tap_to_update_format">Tap to update %s</string>
|
||||||
<string name="install_confirm">Do you want to install this application?
|
<string name="install_confirm">needs access to</string>
|
||||||
It will get access to:</string>
|
|
||||||
<string name="install_confirm_no_perms">Do you want to install this application?
|
|
||||||
It does not require any special access.</string>
|
|
||||||
<string name="install_confirm_update">Do you want to install an update
|
<string name="install_confirm_update">Do you want to install an update
|
||||||
to this existing application? Your existing data will not
|
to this existing application? Your existing data will not
|
||||||
be lost. The updated application will get access to:</string>
|
be lost. The updated application will get access to:</string>
|
||||||
|
@ -48,6 +48,20 @@
|
|||||||
<item name="colorAccent">@color/fdroid_green</item>
|
<item name="colorAccent">@color/fdroid_green</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="MinWithDialogBaseThemeDark" parent="Theme.AppCompat.Dialog.MinWidth">
|
||||||
|
<item name="colorAccent">@color/fdroid_green</item>
|
||||||
|
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="MinWithDialogBaseThemeLight" parent="Theme.AppCompat.Light.Dialog.MinWidth">
|
||||||
|
<item name="colorAccent">@color/fdroid_green</item>
|
||||||
|
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="TextViewStyle" parent="android:Widget.TextView">
|
<style name="TextViewStyle" parent="android:Widget.TextView">
|
||||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user