Redesign PrivilegedInstaller

* use new local broadcasts
* show permission screen before download
* display permission screen as dialog
This commit is contained in:
Dominik Schürmann 2016-05-29 23:34:00 +03:00
parent 592cd0424a
commit 2776b86050
19 changed files with 517 additions and 272 deletions

View File

@ -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"

View File

@ -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;
} }
} }

View File

@ -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);
} }

View File

@ -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)) {

View File

@ -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) {

View File

@ -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;
}
} }

View File

@ -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;
}
} }

View File

@ -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();
} }

View File

@ -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;
// }
// }
} }

View File

@ -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;

View File

@ -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);
} }

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>