diff --git a/.gitmodules b/.gitmodules
index c6d0215ad..0228b8e49 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -34,6 +34,10 @@
path = extern/nanohttpd
url = https://github.com/eighthave/nanohttpd
ignore = dirty
+[submodule "extern/android-support-v4-preferencefragment"]
+ path = extern/android-support-v4-preferencefragment
+ url = https://github.com/CyberEagle/android-support-v4-preferencefragment.git
+ ignore = dirty
[submodule "extern/zxing-core"]
path = extern/zxing-core
url = https://gitlab.com/fdroid/zxing-core.git
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 280ec4d33..7185fa2d9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -7,7 +7,7 @@
android:versionName="0.69-test" >
+
+ android:windowSoftInputMode="stateHidden">
+
+
/dev/null
diff --git a/build.gradle b/build.gradle
index 05dc5ab02..d9699d94e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,16 +7,18 @@ buildscript {
}
}
-def toolVersion = "19.1"
apply plugin: 'android'
-sdkLoc = System.getenv("ANDROID_HOME")
+def toolVersion = "19.1"
+sdkLoc = getSdkPath()
+
FileCollection getAndroidPrebuilt(String apiLevel) {
files("$sdkLoc/platforms/android-$apiLevel/android.jar")
}
dependencies {
compile project(':support-v4')
+ compile project(':support-appcompat-v7')
compile project(':extern:AndroidPinning')
compile project(':extern:UniversalImageLoader:library')
compile project(':extern:MemorizingTrustManager')
@@ -25,6 +27,9 @@ dependencies {
compile project(':extern:jmdns')
compile project(':extern:zipsigner')
compile project(':extern:zxing-core')
+ compile( project(':extern:android-support-v4-preferencefragment') ) {
+ exclude module: 'support-v4'
+ }
}
project(':extern:UniversalImageLoader:library') {
@@ -120,6 +125,15 @@ android.applicationVariants.all { variant ->
// This is the hacky way which we force the subprojects to use the same build tools:
// http://stackoverflow.com/a/21032272
subprojects {
+
+ // The support-v4 library assumes certain things are defined in the
+ // root project (which is usually the android-support project, but
+ // this time it is the F-Droid project.
+ if (project.name.equals("support-v4")) {
+ apply plugin: 'maven'
+ rootProject.ext.supportRepoOut = ""
+ }
+
afterEvaluate {
android {
@@ -138,3 +152,17 @@ subprojects {
}
}
+/**
+ * Currently a bit hacky, because android.plugin is protected.
+ * The end goal is to find something in the android BaseExtension class found here:
+ * https://android.googlesource.com/platform/tools/build/+/master/gradle/src/main/groovy/com/android/build/gradle/BaseExtension.groovy
+ * which ends up asking their Sdk class for it's location. That class knows about
+ * both ANDROID_HOME env variables, and also local.properties sdk.loc properties.
+ *
+ * If in the future, the android.adbExe is found to be inappropriate, deprecated,
+ * or a better way of finding the sdk path exists, we can change the implementation
+ * of this method to reflect that.
+ */
+def getSdkPath() {
+ new File( "$android.adbExe/../../" ).canonicalPath
+}
diff --git a/custom_rules.xml b/custom_rules.xml
index 7747834aa..5cb033506 100644
--- a/custom_rules.xml
+++ b/custom_rules.xml
@@ -1,6 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-land/appdetails.xml b/res/layout-land/appdetails.xml
deleted file mode 100644
index 9f91b96ff..000000000
--- a/res/layout-land/appdetails.xml
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/res/layout/app_details.xml b/res/layout/app_details.xml
new file mode 100644
index 000000000..3acfc0973
--- /dev/null
+++ b/res/layout/app_details.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/res/layout/app_details_summary.xml b/res/layout/app_details_summary.xml
new file mode 100644
index 000000000..44c9afe11
--- /dev/null
+++ b/res/layout/app_details_summary.xml
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/appdetails.xml b/res/layout/appdetails.xml
deleted file mode 100644
index 686eb91d5..000000000
--- a/res/layout/appdetails.xml
+++ /dev/null
@@ -1,104 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/res/values-v11/styles.xml b/res/values-v11/styles.xml
index 2c6cc4705..f78bab7d6 100644
--- a/res/values-v11/styles.xml
+++ b/res/values-v11/styles.xml
@@ -1,11 +1,15 @@
-
+
-
+
+
+
diff --git a/res/values/array.xml b/res/values/array.xml
index bea7d606e..7aed61a82 100644
--- a/res/values/array.xml
+++ b/res/values/array.xml
@@ -11,5 +11,6 @@
- Dark
- Light
+ - Light (with dark action bar)
diff --git a/res/values/ids.xml b/res/values/ids.xml
index 23d937f3d..2d82ad59c 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -1,4 +1,5 @@
+
\ No newline at end of file
diff --git a/res/values/no_trans.xml b/res/values/no_trans.xml
index 102cdab50..0f8e66e48 100644
--- a/res/values/no_trans.xml
+++ b/res/values/no_trans.xml
@@ -25,6 +25,7 @@
- dark
- light
+ - lightWithDarkActionBar
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 003b8f2ff..dfd911baa 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -1,10 +1,14 @@
-
-
+
+
@@ -25,4 +29,8 @@
+
+
diff --git a/settings.gradle b/settings.gradle
index f948dc4d2..f7d0cded9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -10,6 +10,7 @@ include ':extern:spongycastle:pg'
include ':extern:spongycastle:pkix'
include ':extern:spongycastle:prov'
include ':extern:zxing-core'
+include ':extern:android-support-v4-preferencefragment'
include ':support-v4'
project(':support-v4').projectDir = new File('extern/Support/v4')
diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java
index fca4c0b10..60a3851b6 100644
--- a/src/org/fdroid/fdroid/AppDetails.java
+++ b/src/org/fdroid/fdroid/AppDetails.java
@@ -21,10 +21,12 @@ package org.fdroid.fdroid;
import android.app.Activity;
import android.app.AlertDialog;
-import android.app.ListActivity;
import android.app.ProgressDialog;
import android.bluetooth.BluetoothAdapter;
-import android.content.*;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -34,12 +36,12 @@ import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
-import android.preference.PreferenceManager;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.ListFragment;
import android.support.v4.app.NavUtils;
import android.support.v4.view.MenuItemCompat;
-import android.text.Editable;
+import android.support.v7.app.ActionBarActivity;
import android.text.Html;
-import android.text.Html.TagHandler;
import android.text.Spanned;
import android.text.format.DateFormat;
import android.text.method.LinkMovementMethod;
@@ -52,8 +54,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ArrayAdapter;
+import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
@@ -64,20 +66,45 @@ import org.fdroid.fdroid.Utils.CommaSeparatedList;
import org.fdroid.fdroid.compat.ActionBarCompat;
import org.fdroid.fdroid.compat.MenuManager;
import org.fdroid.fdroid.compat.PackageManagerCompat;
-import org.fdroid.fdroid.data.*;
+import org.fdroid.fdroid.data.Apk;
+import org.fdroid.fdroid.data.ApkProvider;
+import org.fdroid.fdroid.data.App;
+import org.fdroid.fdroid.data.AppProvider;
+import org.fdroid.fdroid.data.Repo;
+import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.installer.Installer;
import org.fdroid.fdroid.installer.Installer.AndroidNotCompatibleException;
import org.fdroid.fdroid.installer.Installer.InstallerCallback;
import org.fdroid.fdroid.net.ApkDownloader;
import org.fdroid.fdroid.net.Downloader;
-import org.xml.sax.XMLReader;
import java.io.File;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.List;
-public class AppDetails extends ListActivity implements ProgressListener {
+interface AppDetailsData {
+ public App getApp();
+ public AppDetails.ApkListAdapter getApks();
+ public Signature getInstalledSignature();
+ public String getInstalledSignatureId();
+}
+
+/**
+ * Interface which allows the apk list fragment to communicate with the activity when
+ * a user requests to install/remove an apk by clicking on an item in the list.
+ *
+ * NOTE: This is not to do with with the sudo/packagemanager/other installer
+ * stuff which allows multiple ways to install apps. It is only here to make fragment-
+ * activity communication possible.
+ */
+interface AppInstallListener {
+ public void install(final Apk apk);
+ public void removeApk(String packageName);
+}
+
+public class AppDetails extends ActionBarActivity implements ProgressListener, AppDetailsData, AppInstallListener {
+
private static final String TAG = "org.fdroid.fdroid.AppDetails";
public static final int REQUEST_ENABLE_BLUETOOTH = 2;
@@ -118,14 +145,13 @@ public class AppDetails extends ListActivity implements ProgressListener {
AppDetails.this.finish();
return;
}
- updateViews();
+ refreshApkList();
MenuManager.create(AppDetails.this).invalidateOptionsMenu();
- }
+ }
}
-
- private class ApkListAdapter extends ArrayAdapter {
+ class ApkListAdapter extends ArrayAdapter {
private LayoutInflater mInflater = (LayoutInflater) mctx.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
@@ -134,7 +160,7 @@ public class AppDetails extends ListActivity implements ProgressListener {
super(context, 0);
List apks = ApkProvider.Helper.findByApp(context, app.id);
for (Apk apk : apks ) {
- if (apk.compatible || pref_incompatibleVersions) {
+ if (apk.compatible || Preferences.get().showIncompatibleVersions()) {
add(apk);
}
}
@@ -149,7 +175,7 @@ public class AppDetails extends ListActivity implements ProgressListener {
ViewHolder holder;
if (convertView == null) {
- convertView = mInflater.inflate(R.layout.apklistitem, null);
+ convertView = mInflater.inflate(R.layout.apklistitem, parent, false);
holder = new ViewHolder();
holder.version = (TextView) convertView.findViewById(R.id.version);
@@ -185,7 +211,7 @@ public class AppDetails extends ListActivity implements ProgressListener {
holder.size.setVisibility(View.GONE);
}
- if (!pref_expert) {
+ if (!Preferences.get().expertMode()) {
holder.api.setVisibility(View.GONE);
} else if (apk.minSdkVersion > 0 && apk.maxSdkVersion > 0) {
holder.api.setText(getString(R.string.minsdk_up_to_maxsdk,
@@ -216,7 +242,7 @@ public class AppDetails extends ListActivity implements ProgressListener {
holder.added.setVisibility(View.GONE);
}
- if (pref_expert && apk.nativecode != null) {
+ if (Preferences.get().expertMode() && apk.nativecode != null) {
holder.nativecode.setText(apk.nativecode.toString().replaceAll(","," "));
holder.nativecode.setVisibility(View.VISIBLE);
} else {
@@ -277,11 +303,7 @@ public class AppDetails extends ListActivity implements ProgressListener {
private boolean startingIgnoreAll;
private int startingIgnoreThis;
- LinearLayout headerView;
- View infoView;
-
private final Context mctx = this;
- private DisplayImageOptions displayImageOptions;
private Installer installer;
/**
@@ -337,9 +359,9 @@ public class AppDetails extends ListActivity implements ProgressListener {
// fdroid.app:app.id
appId = data.getEncodedSchemeSpecificPart();
}
- Log.d("FDroid", "AppDetails launched from link, for '" + appId + "'");
+ Log.d(TAG, "AppDetails launched from link, for '" + appId + "'");
} else if (!i.hasExtra(EXTRA_APPID)) {
- Log.e("FDroid", "No application ID in AppDetails!?");
+ Log.e(TAG, "No application ID in AppDetails!?");
} else {
appId = i.getStringExtra(EXTRA_APPID);
}
@@ -349,39 +371,22 @@ public class AppDetails extends ListActivity implements ProgressListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
-
+
fdroidApp = ((FDroidApp) getApplication());
fdroidApp.applyTheme(this);
super.onCreate(savedInstanceState);
- 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();
-
- setContentView(R.layout.appdetails);
-
- // Actionbar cannot be accessed until after setContentView (on 3.0 and 3.1 devices)
- // see: http://blog.perpetumdesign.com/2011/08/strange-case-of-dr-action-and-mr-bar.html
- // for reason why.
- ActionBarCompat.create(this).setDisplayHomeAsUpEnabled(true);
-
if (getIntent().hasExtra(EXTRA_FROM)) {
setTitle(getIntent().getStringExtra(EXTRA_FROM));
}
mPm = getPackageManager();
- installer = Installer.getActivityInstaller(this, mPm,
- myInstallerCallback);
+ installer = Installer.getActivityInstaller(this, mPm, myInstallerCallback);
// Get the preferences we're going to use in this Activity...
- ConfigurationChangeHelper previousData = (ConfigurationChangeHelper)getLastNonConfigurationInstance();
+ ConfigurationChangeHelper previousData = (ConfigurationChangeHelper)getLastCustomNonConfigurationInstance();
if (previousData != null) {
Log.d(TAG, "Recreating view after configuration change.");
downloadHandler = previousData.downloader;
@@ -397,28 +402,34 @@ public class AppDetails extends ListActivity implements ProgressListener {
}
}
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(getBaseContext());
- pref_expert = prefs.getBoolean(Preferences.PREF_EXPERT, false);
- pref_permissions = prefs.getBoolean(Preferences.PREF_PERMISSIONS, false);
- pref_incompatibleVersions = prefs.getBoolean(
- Preferences.PREF_INCOMP_VER, false);
-
// Set up the list...
- headerView = new LinearLayout(this);
- ListView lv = (ListView) findViewById(android.R.id.list);
- lv.addHeaderView(headerView);
adapter = new ApkListAdapter(this, app);
- setListAdapter(adapter);
- startViews();
+ // Wait until all other intialization before doing this, because it will create the
+ // fragments, which rely on data from the activity that is set earlier in this method.
+ setContentView(R.layout.app_details);
+
+ // Actionbar cannot be accessed until after setContentView (on 3.0 and 3.1 devices)
+ // see: http://blog.perpetumdesign.com/2011/08/strange-case-of-dr-action-and-mr-bar.html
+ // for reason why.
+ ActionBarCompat.create(this).setDisplayHomeAsUpEnabled(true);
+
+ // Check for the presence of a view which only exists in the landscape view.
+ // This seems to be the preferred way to interrogate the view, rather than
+ // to check the orientation. I guess this is because views can be dynamically
+ // chosen based on more than just orientation (e.g. large screen sizes).
+ View onlyInLandscape = findViewById(R.id.app_summary_container);
+
+ AppDetailsListFragment listFragment =
+ (AppDetailsListFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_app_list);
+ if (onlyInLandscape == null) {
+ listFragment.setupSummaryHeader();
+ } else {
+ listFragment.removeSummaryHeader();
+ }
}
- private boolean pref_expert;
- private boolean pref_permissions;
- private boolean pref_incompatibleVersions;
-
// The signature of the installed version.
private Signature mInstalledSignature;
private String mInstalledSigID;
@@ -426,13 +437,13 @@ public class AppDetails extends ListActivity implements ProgressListener {
@Override
protected void onResume() {
super.onResume();
-
+
// register observer to know when install status changes
myAppObserver = new AppObserver(new Handler());
getContentResolver().registerContentObserver(
- AppProvider.getContentUri(app.id),
- true,
- myAppObserver);
+ AppProvider.getContentUri(app.id),
+ true,
+ myAppObserver);
if (downloadHandler != null) {
if (downloadHandler.isComplete()) {
downloadCompleteInstallApk();
@@ -446,9 +457,12 @@ public class AppDetails extends ListActivity implements ProgressListener {
updateProgressDialog();
}
}
+ }
- updateViews();
-
+ @Override
+ protected void onResumeFragments() {
+ super.onResumeFragments();
+ refreshApkList();
MenuManager.create(this).invalidateOptionsMenu();
}
@@ -509,7 +523,7 @@ public class AppDetails extends ListActivity implements ProgressListener {
@Override
- public Object onRetainNonConfigurationInstance() {
+ public Object onRetainCustomNonConfigurationInstance() {
inProcessOfChangingConfiguration = true;
return new ConfigurationChangeHelper(downloadHandler, app);
}
@@ -538,7 +552,7 @@ public class AppDetails extends ListActivity implements ProgressListener {
// Return true if the app was found, false otherwise.
private boolean reset(String appId) {
- Log.d("FDroid", "Getting application details for " + appId);
+ Log.d(TAG, "Getting application details for " + appId);
App newApp = null;
if (appId != null && appId.length() > 0) {
@@ -579,233 +593,22 @@ public class AppDetails extends ListActivity implements ProgressListener {
Hasher hash = new Hasher("MD5", mInstalledSignature.toCharsString().getBytes());
mInstalledSigID = hash.getHash();
} catch (NameNotFoundException e) {
- Log.d("FDroid", "Failed to get installed signature");
+ Log.d(TAG, "Failed to get installed signature");
} catch (NoSuchAlgorithmException e) {
- Log.d("FDroid", "Failed to calculate signature MD5 sum");
+ Log.d(TAG, "Failed to calculate signature MD5 sum");
mInstalledSignature = null;
}
}
}
- private void startViews() {
-
- // Insert the 'infoView' (which contains the summary, various odds and
- // ends, and the description) into the appropriate place, if we're in
- // landscape mode. In portrait mode, we put it in the listview's
- // header..
- infoView = View.inflate(this, R.layout.appinfo, null);
- LinearLayout landparent = (LinearLayout) findViewById(R.id.landleft);
- headerView.removeAllViews();
- if (landparent != null) {
- landparent.addView(infoView);
- Log.d("FDroid", "Setting up landscape view");
- } else {
- headerView.addView(infoView);
- Log.d("FDroid", "Setting up portrait view");
- }
-
- // Set the icon...
- ImageView iv = (ImageView) findViewById(R.id.icon);
- ImageLoader.getInstance().displayImage(app.iconUrl, iv,
- displayImageOptions);
-
- // Set the title and other header details...
- TextView tv = (TextView) findViewById(R.id.title);
- tv.setText(app.name);
- tv = (TextView) findViewById(R.id.license);
- tv.setText(app.license);
-
- if (app.categories != null) {
- tv = (TextView) findViewById(R.id.categories);
- tv.setText(app.categories.toString().replaceAll(",",", "));
- }
-
- tv = (TextView) infoView.findViewById(R.id.description);
-
- tv.setMovementMethod(LinkMovementMethod.getInstance());
-
- // Need this to add the unimplemented support for ordered and unordered
- // lists to Html.fromHtml().
- class HtmlTagHandler implements TagHandler {
- int listNum;
-
- @Override
- public void handleTag(boolean opening, String tag, Editable output,
- XMLReader reader) {
- if (tag.equals("ul")) {
- if (opening)
- listNum = -1;
- else
- output.append('\n');
- } else if (opening && tag.equals("ol")) {
- if (opening)
- listNum = 1;
- else
- output.append('\n');
- } else if (tag.equals("li")) {
- if (opening) {
- if (listNum == -1) {
- output.append("\t• ");
- } else {
- output.append("\t").append(Integer.toString(listNum)).append(". ");
- listNum++;
- }
- } else {
- output.append('\n');
- }
- }
- }
- }
- Spanned desc = Html.fromHtml(
- app.description, null, new HtmlTagHandler());
- tv.setText(desc.subSequence(0, desc.length() - 2));
-
- tv = (TextView) infoView.findViewById(R.id.appid);
- if (pref_expert)
- tv.setText(app.id);
- else
- tv.setVisibility(View.GONE);
-
- tv = (TextView) infoView.findViewById(R.id.summary);
- tv.setText(app.summary);
-
- Apk curApk = null;
- for (int i = 0; i < adapter.getCount(); i ++) {
- Apk apk = adapter.getItem(i);
- if (apk.vercode == app.suggestedVercode) {
- curApk = apk;
- break;
- }
- }
-
- if (pref_permissions && !adapter.isEmpty() &&
- ((curApk != null && curApk.compatible) || pref_incompatibleVersions)) {
- tv = (TextView) infoView.findViewById(R.id.permissions_list);
-
- CommaSeparatedList permsList = adapter.getItem(0).permissions;
- if (permsList == null) {
- tv.setText(getString(R.string.no_permissions));
- } else {
- Iterator permissions = permsList.iterator();
- StringBuilder sb = new StringBuilder();
- while (permissions.hasNext()) {
- String permissionName = permissions.next();
- try {
- Permission permission = new Permission(this, permissionName);
- sb.append("\t• ").append(permission.getName()).append('\n');
- } catch (NameNotFoundException e) {
- if (permissionName.equals("ACCESS_SUPERUSER")) {
- sb.append("\t• Full permissions to all device features and storage\n");
- } else {
- Log.d("FDroid", "Permission not yet available: " + permissionName);
- }
- }
- }
- if (sb.length() > 0) sb.setLength(sb.length() - 1);
- tv.setText(sb.toString());
- }
- tv = (TextView) infoView.findViewById(R.id.permissions);
- tv.setText(getString(
- R.string.permissions_for_long, adapter.getItem(0).version));
- } else {
- infoView.findViewById(R.id.permissions).setVisibility(View.GONE);
- infoView.findViewById(R.id.permissions_list).setVisibility(View.GONE);
- }
-
- tv = (TextView) infoView.findViewById(R.id.antifeatures);
- if (app.antiFeatures != null) {
- StringBuilder sb = new StringBuilder();
- for (String af : app.antiFeatures) {
- String afdesc = descAntiFeature(af);
- if (afdesc != null) {
- sb.append("\t• ").append(afdesc).append("\n");
- }
- }
- if (sb.length() > 0) {
- sb.setLength(sb.length() - 1);
- tv.setText(sb.toString());
- } else {
- tv.setVisibility(View.GONE);
- }
- } else {
- tv.setVisibility(View.GONE);
- }
- }
-
- private String descAntiFeature(String af) {
- if (af.equals("Ads"))
- return getString(R.string.antiadslist);
- if (af.equals("Tracking"))
- return getString(R.string.antitracklist);
- if (af.equals("NonFreeNet"))
- return getString(R.string.antinonfreenetlist);
- if (af.equals("NonFreeAdd"))
- return getString(R.string.antinonfreeadlist);
- if (af.equals("NonFreeDep"))
- return getString(R.string.antinonfreedeplist);
- if (af.equals("UpstreamNonFree"))
- return getString(R.string.antiupstreamnonfreelist);
- return null;
- }
-
- private void updateViews() {
-
- // Refresh the list...
+ private void refreshApkList() {
adapter.notifyDataSetChanged();
-
- TextView tv = (TextView) findViewById(R.id.status);
- if (app.isInstalled()) {
- tv.setText(getString(R.string.details_installed,
- app.installedVersionName));
- NfcBeamManager.setAndroidBeam(this, app.id);
- } else {
- tv.setText(getString(R.string.details_notinstalled));
- NfcBeamManager.disableAndroidBeam(this);
- }
-
- tv = (TextView) infoView.findViewById(R.id.signature);
- if (pref_expert && mInstalledSignature != null) {
- tv.setVisibility(View.VISIBLE);
- tv.setText("Signed: " + mInstalledSigID);
- } else {
- tv.setVisibility(View.GONE);
- }
-
- }
-
- @Override
- protected void onListItemClick(ListView l, View v, int position, long id) {
- final Apk apk = adapter.getItem(position - l.getHeaderViewsCount());
- if (app.installedVersionCode == apk.vercode)
- removeApk(app.id);
- else if (app.installedVersionCode > apk.vercode) {
- AlertDialog.Builder ask_alrt = new AlertDialog.Builder(this);
- ask_alrt.setMessage(getString(R.string.installDowngrade));
- ask_alrt.setPositiveButton(getString(R.string.yes),
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog,
- int whichButton) {
- install(apk);
- }
- });
- ask_alrt.setNegativeButton(getString(R.string.no),
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog,
- int whichButton) {
- }
- });
- AlertDialog alert = ask_alrt.create();
- alert.show();
- } else
- install(apk);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
+ super.onPrepareOptionsMenu(menu);
menu.clear();
if (app == null)
return true;
@@ -997,8 +800,7 @@ public class AppDetails extends ListActivity implements ProgressListener {
}
// Install the version of this app denoted by 'app.curApk'.
- private void install(final Apk apk) {
- final Activity activity = this;
+ public void install(final Apk apk) {
String [] projection = { RepoProvider.DataColumns.ADDRESS };
Repo repo = RepoProvider.Helper.findById(this, apk.repo, projection);
if (repo == null || repo.address == null) {
@@ -1064,7 +866,8 @@ public class AppDetails extends ListActivity implements ProgressListener {
}
}
- private void removeApk(String packageName) {
+ @Override
+ public void removeApk(String packageName) {
setProgressBarIndeterminateVisibility(true);
try {
@@ -1253,4 +1056,327 @@ public class AppDetails extends ListActivity implements ProgressListener {
break;
}
}
-}
+
+ public App getApp() {
+ return app;
+ }
+
+ public ApkListAdapter getApks() {
+ return adapter;
+ }
+
+ public Signature getInstalledSignature() {
+ return mInstalledSignature;
+ }
+
+ public String getInstalledSignatureId() {
+ return mInstalledSigID;
+ }
+
+ public static class AppDetailsSummaryFragment extends Fragment {
+
+ protected final Preferences prefs;
+ protected final DisplayImageOptions displayImageOptions;
+ private AppDetailsData data;
+
+ public AppDetailsSummaryFragment() {
+ prefs = Preferences.get();
+ 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();
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ data = (AppDetailsData)activity;
+ }
+
+ protected App getApp() {
+ return data.getApp();
+ }
+
+ protected ApkListAdapter getApks() {
+ return data.getApks();
+ }
+
+ protected Signature getInstalledSignature() {
+ return data.getInstalledSignature();
+ }
+
+ protected String getInstalledSignatureId() {
+ return data.getInstalledSignatureId();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ super.onCreateView(inflater, container, savedInstanceState);
+ View summaryView = inflater.inflate(R.layout.app_details_summary, container, false);
+ setupView(summaryView);
+ return summaryView;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateViews(getView());
+ }
+
+ private void setupView(View view) {
+
+ // Set the icon...
+ ImageView iv = (ImageView) view.findViewById(R.id.icon);
+ ImageLoader.getInstance().displayImage(getApp().iconUrl, iv, displayImageOptions);
+
+ // Set the title and other header details...
+ TextView tv = (TextView) view.findViewById(R.id.title);
+ tv.setText(getApp().name);
+ tv = (TextView) view.findViewById(R.id.license);
+ tv.setText(getApp().license);
+
+ if (getApp().categories != null) {
+ tv = (TextView) view.findViewById(R.id.categories);
+ tv.setText(getApp().categories.toString().replaceAll(",", ", "));
+ }
+
+ TextView description = (TextView) view.findViewById(R.id.description);
+ Spanned desc = Html.fromHtml(getApp().description, null, new Utils.HtmlTagHandler());
+ description.setMovementMethod(LinkMovementMethod.getInstance());
+ description.setText(desc.subSequence(0, desc.length() - 2));
+
+ TextView appIdView = (TextView) view.findViewById(R.id.appid);
+ if (prefs.expertMode())
+ appIdView.setText(getApp().id);
+ else
+ appIdView.setVisibility(View.GONE);
+
+ TextView summaryView = (TextView) view.findViewById(R.id.summary);
+ summaryView.setText(getApp().summary);
+
+ Apk curApk = null;
+ for (int i = 0; i < getApks().getCount(); i ++) {
+ Apk apk = getApks().getItem(i);
+ if (apk.vercode == getApp().suggestedVercode) {
+ curApk = apk;
+ break;
+ }
+ }
+
+ TextView permissionListView = (TextView) view.findViewById(R.id.permissions_list);
+ TextView permissionHeader = (TextView) view.findViewById(R.id.permissions);
+ boolean curApkCompatible = curApk != null && curApk.compatible;
+ if (prefs.showPermissions() && !getApks().isEmpty() &&
+ ( curApkCompatible || prefs.showIncompatibleVersions() ) ) {
+
+ CommaSeparatedList permsList = getApks().getItem(0).permissions;
+ if (permsList == null) {
+ permissionListView.setText(getString(R.string.no_permissions));
+ } else {
+ Iterator permissions = permsList.iterator();
+ StringBuilder sb = new StringBuilder();
+ while (permissions.hasNext()) {
+ String permissionName = permissions.next();
+ try {
+ Permission permission = new Permission(getActivity(), permissionName);
+ // TODO: Make this list RTL friendly
+ sb.append("\t• ").append(permission.getName()).append('\n');
+ } catch (NameNotFoundException e) {
+ if (permissionName.equals("ACCESS_SUPERUSER")) {
+ // TODO: i18n this string, but surely it is already translated somewhere?
+ sb.append("\t• Full permissions to all device features and storage\n");
+ } else {
+ Log.e(TAG, "Permission not yet available: " + permissionName);
+ }
+ }
+ }
+ if (sb.length() > 0) sb.setLength(sb.length() - 1);
+ permissionListView.setText(sb.toString());
+ }
+ permissionHeader.setText(getString(R.string.permissions_for_long, getApks().getItem(0).version));
+ } else {
+ permissionListView.setVisibility(View.GONE);
+ permissionHeader.setVisibility(View.GONE);
+ }
+
+ TextView antiFeaturesView = (TextView) view.findViewById(R.id.antifeatures);
+ if (getApp().antiFeatures != null) {
+ StringBuilder sb = new StringBuilder();
+ for (String af : getApp().antiFeatures) {
+ String afdesc = descAntiFeature(af);
+ if (afdesc != null) {
+ sb.append("\t• ").append(afdesc).append("\n");
+ }
+ }
+ if (sb.length() > 0) {
+ sb.setLength(sb.length() - 1);
+ antiFeaturesView.setText(sb.toString());
+ } else {
+ antiFeaturesView.setVisibility(View.GONE);
+ }
+ } else {
+ antiFeaturesView.setVisibility(View.GONE);
+ }
+
+ updateViews(view);
+ }
+
+ private String descAntiFeature(String af) {
+ if (af.equals("Ads"))
+ return getString(R.string.antiadslist);
+ if (af.equals("Tracking"))
+ return getString(R.string.antitracklist);
+ if (af.equals("NonFreeNet"))
+ return getString(R.string.antinonfreenetlist);
+ if (af.equals("NonFreeAdd"))
+ return getString(R.string.antinonfreeadlist);
+ if (af.equals("NonFreeDep"))
+ return getString(R.string.antinonfreedeplist);
+ if (af.equals("UpstreamNonFree"))
+ return getString(R.string.antiupstreamnonfreelist);
+ return null;
+ }
+
+ public void updateViews(View view) {
+
+ if (view == null) {
+ Log.e(TAG, "AppDetailsSummaryFragment.refreshApkList - view == null. Oops.");
+ return;
+ }
+
+ TextView statusView = (TextView) view.findViewById(R.id.status);
+ if (getApp().isInstalled()) {
+ statusView.setText(getString(R.string.details_installed, getApp().installedVersionName));
+ NfcBeamManager.setAndroidBeam(getActivity(), getApp().id);
+ } else {
+ statusView.setText(getString(R.string.details_notinstalled));
+ NfcBeamManager.disableAndroidBeam(getActivity());
+ }
+
+ TextView signatureView = (TextView) view.findViewById(R.id.signature);
+ if (prefs.expertMode() && getInstalledSignature() != null) {
+ signatureView.setVisibility(View.VISIBLE);
+ signatureView.setText("Signed: " + getInstalledSignatureId());
+ } else {
+ signatureView.setVisibility(View.GONE);
+ }
+
+ }
+ }
+
+ public static class AppDetailsListFragment extends ListFragment {
+
+ private final String SUMMARY_TAG = "summary";
+
+ private AppDetailsData data;
+ private AppInstallListener installListener;
+ private AppDetailsSummaryFragment summaryFragment = null;
+
+ private FrameLayout headerView;
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ data = (AppDetailsData)activity;
+ installListener = (AppInstallListener)activity;
+ }
+
+ protected void install(final Apk apk) {
+ installListener.install(apk);
+ }
+
+ protected void remove() {
+ installListener.removeApk(getApp().id);
+ }
+
+ protected App getApp() {
+ return data.getApp();
+ }
+
+ protected ApkListAdapter getApks() {
+ return data.getApks();
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ // A bit of a hack, but we can't add the header view in setupSummaryHeader(),
+ // due to the fact it needs to happen before setListAdapter(). Also, seeing
+ // as we may never add a summary header (i.e. in landscape), this is probably
+ // the last opportunity to set the list adapter. As such, we use the headerView
+ // as a mechanism to optionally allow adding a header in the future.
+ if (headerView == null) {
+ headerView = new FrameLayout(getActivity().getApplicationContext());
+ headerView.setId(R.id.appDetailsSummaryHeader);
+ } else {
+ Fragment summaryFragment = getChildFragmentManager().findFragmentByTag(SUMMARY_TAG);
+ if (summaryFragment != null) {
+ getChildFragmentManager().beginTransaction().remove(summaryFragment).commit();
+ }
+ }
+
+ setListAdapter(null);
+ getListView().addHeaderView(headerView);
+ setListAdapter(getApks());
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ final Apk apk = getApks().getItem(position - l.getHeaderViewsCount());
+ if (getApp().installedVersionCode == apk.vercode)
+ remove();
+ else if (getApp().installedVersionCode > apk.vercode) {
+ AlertDialog.Builder ask_alrt = new AlertDialog.Builder(getActivity());
+ ask_alrt.setMessage(getString(R.string.installDowngrade));
+ ask_alrt.setPositiveButton(getString(R.string.yes),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ install(apk);
+ }
+ });
+ ask_alrt.setNegativeButton(getString(R.string.no),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ }
+ });
+ AlertDialog alert = ask_alrt.create();
+ alert.show();
+ } else
+ install(apk);
+ }
+
+ public void removeSummaryHeader() {
+ Fragment summary = getChildFragmentManager().findFragmentByTag(SUMMARY_TAG);
+ if (summary != null) {
+ getChildFragmentManager().beginTransaction().remove(summary).commit();
+ headerView.removeAllViews();
+ headerView.setVisibility(View.GONE);
+ summaryFragment = null;
+ }
+ }
+
+ public void setupSummaryHeader() {
+ Fragment fragment = getChildFragmentManager().findFragmentByTag(SUMMARY_TAG);
+ if (fragment != null) {
+ summaryFragment = (AppDetailsSummaryFragment)fragment;
+ } else {
+ summaryFragment = new AppDetailsSummaryFragment();
+ }
+ getChildFragmentManager().beginTransaction().replace(headerView.getId(), summaryFragment, SUMMARY_TAG).commit();
+ headerView.setVisibility(View.VISIBLE);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/fdroid/fdroid/FDroid.java b/src/org/fdroid/fdroid/FDroid.java
index a0a622c5c..e66fcc7c9 100644
--- a/src/org/fdroid/fdroid/FDroid.java
+++ b/src/org/fdroid/fdroid/FDroid.java
@@ -32,9 +32,9 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.view.ViewPager;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -42,13 +42,12 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
-
import org.fdroid.fdroid.compat.TabManager;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.views.AppListFragmentPageAdapter;
import org.fdroid.fdroid.views.LocalRepoActivity;
-public class FDroid extends FragmentActivity {
+public class FDroid extends ActionBarActivity {
public static final int REQUEST_APPDETAILS = 0;
public static final int REQUEST_MANAGEREPOS = 1;
@@ -80,6 +79,7 @@ public class FDroid extends FragmentActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.fdroid);
createViews();
+
getTabManager().createTabs();
// Start a search by just typing
@@ -333,7 +333,7 @@ public class FDroid extends FragmentActivity {
private TabManager getTabManager() {
if (tabManager == null) {
- tabManager = TabManager.create(this, viewPager);
+ tabManager = new TabManager(this, viewPager);
}
return tabManager;
}
diff --git a/src/org/fdroid/fdroid/FDroidApp.java b/src/org/fdroid/fdroid/FDroidApp.java
index 16d936fce..54111e958 100644
--- a/src/org/fdroid/fdroid/FDroidApp.java
+++ b/src/org/fdroid/fdroid/FDroidApp.java
@@ -32,15 +32,12 @@ import android.os.*;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
-
import com.nostra13.universalimageloader.cache.disc.impl.LimitedAgeDiscCache;
import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.utils.StorageUtils;
-
import de.duenndns.ssl.MemorizingTrustManager;
-
import org.fdroid.fdroid.Preferences.ChangeListener;
import org.fdroid.fdroid.compat.PRNGFixes;
import org.fdroid.fdroid.data.AppProvider;
@@ -52,6 +49,11 @@ import org.fdroid.fdroid.net.WifiStateChangeService;
import org.thoughtcrime.ssl.pinning.PinningTrustManager;
import org.thoughtcrime.ssl.pinning.SystemKeyStore;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.security.*;
import java.util.Set;
@@ -74,7 +76,7 @@ public class FDroidApp extends Application {
BluetoothAdapter bluetoothAdapter = null;
private static enum Theme {
- dark, light
+ dark, light, lightWithDarkActionBar
}
private static Theme curTheme = Theme.dark;
@@ -88,11 +90,14 @@ public class FDroidApp extends Application {
public void applyTheme(Activity activity) {
switch (curTheme) {
case dark:
- //activity.setTheme(R.style.AppThemeDark);
- return;
+ activity.setTheme(R.style.AppThemeDark);
+ break;
case light:
activity.setTheme(R.style.AppThemeLight);
- return;
+ break;
+ case lightWithDarkActionBar:
+ activity.setTheme(R.style.AppThemeLightWithDarkActionBar);
+ break;
}
}
diff --git a/src/org/fdroid/fdroid/ManageRepo.java b/src/org/fdroid/fdroid/ManageRepo.java
index 64b025597..f7a32a3c5 100644
--- a/src/org/fdroid/fdroid/ManageRepo.java
+++ b/src/org/fdroid/fdroid/ManageRepo.java
@@ -26,20 +26,19 @@ import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.NavUtils;
+import android.support.v7.app.ActionBarActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.MenuItem;
import android.widget.LinearLayout;
import android.widget.Toast;
-
-import org.fdroid.fdroid.compat.ActionBarCompat;
import org.fdroid.fdroid.views.fragments.RepoListFragment;
import java.util.Locale;
-public class ManageRepo extends FragmentActivity {
+
+public class ManageRepo extends ActionBarActivity {
/**
* If we have a new repo added, or the address of a repo has changed, then
@@ -53,9 +52,9 @@ public class ManageRepo extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
((FDroidApp) getApplication()).applyTheme(this);
+ super.onCreate(savedInstanceState);
FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentById(android.R.id.content) == null) {
@@ -72,7 +71,7 @@ public class ManageRepo extends FragmentActivity {
.commit();
}
- ActionBarCompat.create(this).setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
diff --git a/src/org/fdroid/fdroid/NfcNotEnabledActivity.java b/src/org/fdroid/fdroid/NfcNotEnabledActivity.java
index bdca0542c..5c708a9d2 100644
--- a/src/org/fdroid/fdroid/NfcNotEnabledActivity.java
+++ b/src/org/fdroid/fdroid/NfcNotEnabledActivity.java
@@ -2,15 +2,16 @@
package org.fdroid.fdroid;
import android.annotation.TargetApi;
-import android.app.Activity;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
+import android.support.v7.app.ActionBarActivity;
// aka Android 4.0 aka Ice Cream Sandwich
-public class NfcNotEnabledActivity extends Activity {
+public class NfcNotEnabledActivity extends ActionBarActivity
+{
/*
* ACTION_NFC_SETTINGS was added in 4.1 aka Jelly Bean MR1 as a
@@ -35,7 +36,10 @@ public class NfcNotEnabledActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
+
+ ((FDroidApp) getApplication()).applyTheme(this);
super.onCreate(savedInstanceState);
+
final Intent intent = new Intent();
if (Build.VERSION.SDK_INT >= 16) {
doOnJellybean(intent);
diff --git a/src/org/fdroid/fdroid/Preferences.java b/src/org/fdroid/fdroid/Preferences.java
index ca6d750fd..4254617a7 100644
--- a/src/org/fdroid/fdroid/Preferences.java
+++ b/src/org/fdroid/fdroid/Preferences.java
@@ -1,13 +1,19 @@
package org.fdroid.fdroid;
-import java.util.*;
-
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Log;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
/**
* Handles shared preferences for FDroid, looking after the names of
* preferences, default values and caching. Needs to be setup in the FDroidApp
@@ -55,6 +61,9 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
private static final boolean DEFAULT_SYSTEM_INSTALLER = false;
private static final boolean DEFAULT_LOCAL_REPO_BONJOUR = true;
private static final boolean DEFAULT_LOCAL_REPO_HTTPS = false;
+ private static final boolean DEFAULT_INCOMP_VER = false;
+ private static final boolean DEFAULT_EXPERT = false;
+ private static final boolean DEFAULT_PERMISSIONS = false;
private boolean compactLayout = DEFAULT_COMPACT_LAYOUT;
private boolean filterAppsRequiringRoot = DEFAULT_ROOTED;
@@ -92,6 +101,18 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
return preferences.getBoolean(PREF_LOCAL_REPO_BONJOUR, DEFAULT_LOCAL_REPO_BONJOUR);
}
+ public boolean showIncompatibleVersions() {
+ return preferences.getBoolean(PREF_INCOMP_VER, DEFAULT_INCOMP_VER);
+ }
+
+ public boolean showPermissions() {
+ return preferences.getBoolean(PREF_PERMISSIONS, DEFAULT_PERMISSIONS);
+ }
+
+ public boolean expertMode() {
+ return preferences.getBoolean(PREF_EXPERT, DEFAULT_EXPERT);
+ }
+
public boolean isLocalRepoHttpsEnabled() {
return preferences.getBoolean(PREF_LOCAL_REPO_HTTPS, DEFAULT_LOCAL_REPO_HTTPS);
}
diff --git a/src/org/fdroid/fdroid/PreferencesActivity.java b/src/org/fdroid/fdroid/PreferencesActivity.java
index 9bf66d4b0..cc7c4940b 100644
--- a/src/org/fdroid/fdroid/PreferencesActivity.java
+++ b/src/org/fdroid/fdroid/PreferencesActivity.java
@@ -19,288 +19,42 @@
package org.fdroid.fdroid;
import android.os.Bundle;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceClickListener;
-import android.preference.PreferenceActivity;
-import android.preference.CheckBoxPreference;
-import android.preference.EditTextPreference;
-import android.preference.ListPreference;
-import android.app.AlertDialog;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.view.MenuItem;
+import android.support.v4.app.FragmentManager;
import android.support.v4.app.NavUtils;
+import android.support.v7.app.ActionBarActivity;
+import android.view.MenuItem;
+import android.widget.LinearLayout;
+import org.fdroid.fdroid.views.fragments.PreferenceFragment;
-import org.fdroid.fdroid.Preferences;
-import org.fdroid.fdroid.compat.ActionBarCompat;
-import org.fdroid.fdroid.installer.CheckRootAsyncTask;
-import org.fdroid.fdroid.installer.CheckRootAsyncTask.CheckRootCallback;
-import org.fdroid.fdroid.installer.Installer;
-
-public class PreferencesActivity extends PreferenceActivity implements
- OnSharedPreferenceChangeListener {
+public class PreferencesActivity extends ActionBarActivity {
public static final int RESULT_RESTART = 4;
- private int result = 0;
-
- private static String[] summariesToUpdate = {
- Preferences.PREF_UPD_INTERVAL,
- Preferences.PREF_UPD_WIFI_ONLY,
- Preferences.PREF_UPD_NOTIFY,
- Preferences.PREF_UPD_HISTORY,
- Preferences.PREF_ROOTED,
- Preferences.PREF_INCOMP_VER,
- Preferences.PREF_THEME,
- Preferences.PREF_PERMISSIONS,
- Preferences.PREF_COMPACT_LAYOUT,
- Preferences.PREF_IGN_TOUCH,
- Preferences.PREF_LOCAL_REPO_BONJOUR,
- Preferences.PREF_LOCAL_REPO_NAME,
- Preferences.PREF_LOCAL_REPO_HTTPS,
- Preferences.PREF_CACHE_APK,
- Preferences.PREF_EXPERT,
- Preferences.PREF_ROOT_INSTALLER,
- Preferences.PREF_SYSTEM_INSTALLER
- };
@Override
protected void onCreate(Bundle savedInstanceState) {
+
((FDroidApp) getApplication()).applyTheme(this);
super.onCreate(savedInstanceState);
+ FragmentManager fm = getSupportFragmentManager();
+ if (fm.findFragmentById(android.R.id.content) == null) {
+ // Need to set a dummy view (which will get overridden by the fragment manager
+ // below) so that we can call setContentView(). This is a work around for
+ // a (bug?) thing in 3.0, 3.1 which requires setContentView to be invoked before
+ // the actionbar is played with:
+ // http://blog.perpetumdesign.com/2011/08/strange-case-of-dr-action-and-mr-bar.html
+ setContentView( new LinearLayout(this) );
+
+ PreferenceFragment preferenceFragment = new PreferenceFragment();
+ fm.beginTransaction()
+ .add(android.R.id.content, preferenceFragment)
+ .commit();
+ }
+
// Actionbar cannot be accessed until after setContentView (on 3.0 and 3.1 devices)
// see: http://blog.perpetumdesign.com/2011/08/strange-case-of-dr-action-and-mr-bar.html
// for reason why.
- ActionBarCompat.create(this).setDisplayHomeAsUpEnabled(true);
-
- addPreferencesFromResource(R.xml.preferences);
- }
-
- protected void onoffSummary(String key, int on, int off) {
- CheckBoxPreference pref = (CheckBoxPreference)findPreference(key);
- if (pref.isChecked()) {
- pref.setSummary(on);
- } else {
- pref.setSummary(off);
- }
- }
-
- protected void entrySummary(String key) {
- ListPreference pref = (ListPreference)findPreference(key);
- pref.setSummary(pref.getEntry());
- }
-
- protected void textSummary(String key, int resId) {
- EditTextPreference pref = (EditTextPreference)findPreference(key);
- pref.setSummary(getString(resId, pref.getText()));
- }
-
- protected void updateSummary(String key, boolean changing) {
-
- if (key.equals(Preferences.PREF_UPD_INTERVAL)) {
- ListPreference pref = (ListPreference)findPreference(
- Preferences.PREF_UPD_INTERVAL);
- int interval = Integer.parseInt(pref.getValue().toString());
- Preference onlyOnWifi = findPreference(
- Preferences.PREF_UPD_WIFI_ONLY);
- onlyOnWifi.setEnabled(interval > 0);
- if (interval == 0) {
- pref.setSummary(R.string.update_interval_zero);
- } else {
- pref.setSummary(pref.getEntry());
- }
-
- } else if (key.equals(Preferences.PREF_UPD_WIFI_ONLY)) {
- onoffSummary(key, R.string.automatic_scan_wifi_on,
- R.string.automatic_scan_wifi_off);
-
- } else if (key.equals(Preferences.PREF_UPD_NOTIFY)) {
- onoffSummary(key, R.string.notify_on,
- R.string.notify_off);
-
- } else if (key.equals(Preferences.PREF_UPD_HISTORY)) {
- textSummary(key, R.string.update_history_summ);
-
- } else if (key.equals(Preferences.PREF_PERMISSIONS)) {
- onoffSummary(key, R.string.showPermissions_on,
- R.string.showPermissions_off);
-
- } else if (key.equals(Preferences.PREF_COMPACT_LAYOUT)) {
- onoffSummary(key, R.string.compactlayout_on,
- R.string.compactlayout_off);
-
- } else if (key.equals(Preferences.PREF_THEME)) {
- entrySummary(key);
- if (changing) {
- result |= RESULT_RESTART;
- setResult(result);
- }
-
- } else if (key.equals(Preferences.PREF_INCOMP_VER)) {
- onoffSummary(key, R.string.show_incompat_versions_on,
- R.string.show_incompat_versions_off);
-
- } else if (key.equals(Preferences.PREF_ROOTED)) {
- onoffSummary(key, R.string.rooted_on,
- R.string.rooted_off);
-
- } else if (key.equals(Preferences.PREF_IGN_TOUCH)) {
- onoffSummary(key, R.string.ignoreTouch_on,
- R.string.ignoreTouch_off);
-
- } else if (key.equals(Preferences.PREF_LOCAL_REPO_BONJOUR)) {
- onoffSummary(key, R.string.local_repo_bonjour_on,
- R.string.local_repo_bonjour_off);
-
- } else if (key.equals(Preferences.PREF_LOCAL_REPO_NAME)) {
- textSummary(key, R.string.local_repo_name_summary);
-
- } else if (key.equals(Preferences.PREF_LOCAL_REPO_HTTPS)) {
- onoffSummary(key, R.string.local_repo_https_on,
- R.string.local_repo_https_off);
-
- } else if (key.equals(Preferences.PREF_CACHE_APK)) {
- onoffSummary(key, R.string.cache_downloaded_on,
- R.string.cache_downloaded_off);
-
- } else if (key.equals(Preferences.PREF_EXPERT)) {
- onoffSummary(key, R.string.expert_on,
- R.string.expert_off);
-
- } else if (key.equals(Preferences.PREF_ROOT_INSTALLER)) {
- onoffSummary(key, R.string.root_installer_on,
- R.string.root_installer_off);
-
- } else if (key.equals(Preferences.PREF_SYSTEM_INSTALLER)) {
- onoffSummary(key, R.string.system_installer_on,
- R.string.system_installer_off);
-
- }
- }
-
- /**
- * Initializes RootInstaller preference. This method ensures that the preference can only be checked and persisted
- * when the user grants root access for F-Droid.
- */
- protected void initRootInstallerPreference() {
- CheckBoxPreference pref = (CheckBoxPreference) findPreference(Preferences.PREF_ROOT_INSTALLER);
-
- // we are handling persistence ourself!
- pref.setPersistent(false);
-
- pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- final CheckBoxPreference pref = (CheckBoxPreference) preference;
-
- if (pref.isChecked()) {
- CheckRootAsyncTask checkTask = new CheckRootAsyncTask(PreferencesActivity.this, new CheckRootCallback() {
-
- @Override
- public void onRootCheck(boolean rootGranted) {
- if (rootGranted) {
- // root access granted
- SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
- editor.putBoolean(Preferences.PREF_ROOT_INSTALLER, true);
- editor.commit();
- pref.setChecked(true);
- } else {
- // root access denied
- SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
- editor.putBoolean(Preferences.PREF_ROOT_INSTALLER, false);
- editor.commit();
- pref.setChecked(false);
-
- AlertDialog.Builder alertBuilder = new AlertDialog.Builder(PreferencesActivity.this);
- alertBuilder.setTitle(R.string.root_access_denied_title);
- alertBuilder.setMessage(PreferencesActivity.this.getString(R.string.root_access_denied_body));
- alertBuilder.setNeutralButton(android.R.string.ok, null);
- alertBuilder.create().show();
- }
- }
- });
- checkTask.execute();
- } else {
- SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
- editor.putBoolean(Preferences.PREF_ROOT_INSTALLER, false);
- editor.commit();
- pref.setChecked(false);
- }
-
- return true;
- }
- });
- }
-
- /**
- * Initializes SystemInstaller preference, which can only be enabled when F-Droid is installed as a system-app
- */
- protected void initSystemInstallerPreference() {
- CheckBoxPreference pref = (CheckBoxPreference) findPreference(Preferences.PREF_SYSTEM_INSTALLER);
-
- // we are handling persistence ourself!
- pref.setPersistent(false);
-
- pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- final CheckBoxPreference pref = (CheckBoxPreference) preference;
-
- if (pref.isChecked()) {
- if (Installer.hasSystemPermissions(PreferencesActivity.this, PreferencesActivity.this.getPackageManager())) {
- // system-permission are granted, i.e. F-Droid is a system-app
- SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
- editor.putBoolean(Preferences.PREF_SYSTEM_INSTALLER, true);
- editor.commit();
- pref.setChecked(true);
- } else {
- // system-permission not available
- SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
- editor.putBoolean(Preferences.PREF_SYSTEM_INSTALLER, false);
- editor.commit();
- pref.setChecked(false);
-
- AlertDialog.Builder alertBuilder = new AlertDialog.Builder(PreferencesActivity.this);
- alertBuilder.setTitle(R.string.system_permission_denied_title);
- alertBuilder.setMessage(PreferencesActivity.this.getString(R.string.system_permission_denied_body));
- alertBuilder.setNeutralButton(android.R.string.ok, null);
- alertBuilder.create().show();
- }
- } else {
- SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
- editor.putBoolean(Preferences.PREF_SYSTEM_INSTALLER, false);
- editor.commit();
- pref.setChecked(false);
- }
-
- return true;
- }
- });
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- getPreferenceScreen().getSharedPreferences()
- .registerOnSharedPreferenceChangeListener(this);
-
- for (String key : summariesToUpdate) {
- updateSummary(key, false);
- }
-
- initRootInstallerPreference();
- initSystemInstallerPreference();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
-
- getPreferenceScreen().getSharedPreferences()
- .unregisterOnSharedPreferenceChangeListener(this);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
@@ -313,10 +67,4 @@ public class PreferencesActivity extends PreferenceActivity implements
return super.onOptionsItemSelected(item);
}
- @Override
- public void onSharedPreferenceChanged(
- SharedPreferences sharedPreferences, String key) {
- updateSummary(key, true);
- }
-
}
diff --git a/src/org/fdroid/fdroid/SearchResults.java b/src/org/fdroid/fdroid/SearchResults.java
index 53f475692..08bab2480 100644
--- a/src/org/fdroid/fdroid/SearchResults.java
+++ b/src/org/fdroid/fdroid/SearchResults.java
@@ -20,17 +20,16 @@ package org.fdroid.fdroid;
import android.content.Intent;
import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.NavUtils;
import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.LinearLayout;
-import org.fdroid.fdroid.compat.ActionBarCompat;
import org.fdroid.fdroid.views.fragments.SearchResultsFragment;
-public class SearchResults extends FragmentActivity {
+public class SearchResults extends ActionBarActivity {
private static final int SEARCH = Menu.FIRST;
@@ -38,7 +37,6 @@ public class SearchResults extends FragmentActivity {
public void onCreate(Bundle savedInstanceState) {
((FDroidApp) getApplication()).applyTheme(this);
-
super.onCreate(savedInstanceState);
// Start a search by just typing
@@ -61,7 +59,7 @@ public class SearchResults extends FragmentActivity {
// Actionbar cannot be accessed until after setContentView (on 3.0 and 3.1 devices)
// see: http://blog.perpetumdesign.com/2011/08/strange-case-of-dr-action-and-mr-bar.html
// for reason why.
- ActionBarCompat.create(this).setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
diff --git a/src/org/fdroid/fdroid/Utils.java b/src/org/fdroid/fdroid/Utils.java
index cd1322f10..ea97f786d 100644
--- a/src/org/fdroid/fdroid/Utils.java
+++ b/src/org/fdroid/fdroid/Utils.java
@@ -23,24 +23,41 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.content.res.XmlResourceParser;
import android.net.Uri;
+import android.text.Editable;
+import android.text.Html;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
-
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListAdapter;
+import android.widget.ListView;
import com.nostra13.universalimageloader.utils.StorageUtils;
-
import org.fdroid.fdroid.data.Repo;
+import org.xml.sax.XMLReader;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import java.io.*;
+import java.io.BufferedInputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.text.SimpleDateFormat;
-import java.util.*;
+import java.util.Formatter;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
public final class Utils {
@@ -452,4 +469,38 @@ public final class Utils {
return String.format("%0" + (bytes.length << 1) + "X", bi);
}
+
+ // Need this to add the unimplemented support for ordered and unordered
+ // lists to Html.fromHtml().
+ public static class HtmlTagHandler implements Html.TagHandler {
+ int listNum;
+
+ @Override
+ public void handleTag(boolean opening, String tag, Editable output,
+ XMLReader reader) {
+ if (tag.equals("ul")) {
+ if (opening)
+ listNum = -1;
+ else
+ output.append('\n');
+ } else if (opening && tag.equals("ol")) {
+ if (opening)
+ listNum = 1;
+ else
+ output.append('\n');
+ } else if (tag.equals("li")) {
+ if (opening) {
+ if (listNum == -1) {
+ output.append("\t• ");
+ } else {
+ output.append("\t").append(Integer.toString(listNum)).append(". ");
+ listNum++;
+ }
+ } else {
+ output.append('\n');
+ }
+ }
+ }
+ }
+
}
diff --git a/src/org/fdroid/fdroid/compat/TabManager.java b/src/org/fdroid/fdroid/compat/TabManager.java
index d93e9fdf3..3ddcf3ead 100644
--- a/src/org/fdroid/fdroid/compat/TabManager.java
+++ b/src/org/fdroid/fdroid/compat/TabManager.java
@@ -1,48 +1,38 @@
package org.fdroid.fdroid.compat;
+import android.content.res.Configuration;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.ViewPager;
+import android.support.v7.app.ActionBar;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Spinner;
+import org.fdroid.fdroid.FDroid;
+
import java.util.ArrayList;
import java.util.List;
-import android.annotation.TargetApi;
-import android.app.ActionBar;
-import android.app.FragmentTransaction;
-import android.content.res.Configuration;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.*;
-
-import android.support.v4.view.ViewPager;
-
-import org.fdroid.fdroid.FDroid;
-import org.fdroid.fdroid.R;
-
-public abstract class TabManager extends Compatibility {
+public class TabManager {
public static final int INDEX_AVAILABLE = 0;
public static final int INDEX_INSTALLED = 1;
public static final int INDEX_CAN_UPDATE = 2;
- public static TabManager create(FDroid parent, ViewPager pager) {
- if (hasApi(11)) {
- return new HoneycombTabManagerImpl(parent, pager);
- } else {
- return new OldTabManagerImpl(parent, pager);
- }
+ private ViewPager pager;
+ private FDroid parent;
+ private final ActionBar actionBar;
+ private Spinner actionBarSpinner = null;
+
+ // Used to make sure we only search for the action bar spinner once
+ // in each orientation.
+ private boolean dirtyConfig = true;
+
+ public TabManager(FDroid parent, ViewPager pager) {
+ actionBar = parent.getSupportActionBar();
+ this.parent = parent;
+ this.pager = pager;
}
- protected final ViewPager pager;
- protected final FDroid parent;
-
- protected TabManager(FDroid parent, ViewPager pager) {
- this.parent = parent;
- this.pager = pager;
- }
-
- abstract public void createTabs();
- abstract public void selectTab(int index);
- abstract public void refreshTabLabel(int index);
- abstract public void onConfigurationChanged(Configuration newConfig);
-
protected CharSequence getLabel(int index) {
return pager.getAdapter().getPageTitle(index);
}
@@ -50,126 +40,7 @@ public abstract class TabManager extends Compatibility {
public void removeNotification(int id) {
parent.removeNotification(id);
}
-}
-class OldTabManagerImpl extends TabManager {
-
- private TabHost tabHost;
-
- public OldTabManagerImpl(FDroid parent, ViewPager pager) {
- super(parent, pager);
- }
-
- /**
- * There is a bit of boiler-plate code required to get a TabWidget showing,
- * which includes creating a TabHost, populating it with the TabWidget,
- * and giving it a FrameLayout as a child. This will make the tabs have
- * dummy empty contents and then hook them up to our ViewPager.
- */
- @Override
- public void createTabs() {
- tabHost = new TabHost(parent, null);
- tabHost.setLayoutParams(new TabHost.LayoutParams(
- TabHost.LayoutParams.MATCH_PARENT, TabHost.LayoutParams.WRAP_CONTENT));
-
- TabWidget tabWidget = new TabWidget(parent);
- tabWidget.setId(android.R.id.tabs);
- tabHost.setLayoutParams(new TabHost.LayoutParams(
- TabWidget.LayoutParams.MATCH_PARENT, TabWidget.LayoutParams.WRAP_CONTENT));
-
- FrameLayout layout = new FrameLayout(parent);
- layout.setId(android.R.id.tabcontent);
- layout.setLayoutParams(new TabWidget.LayoutParams(0, 0));
-
- tabHost.addView(tabWidget);
- tabHost.addView(layout);
- tabHost.setup();
-
- TabHost.TabContentFactory factory = new TabHost.TabContentFactory() {
- @Override
- public View createTabContent(String tag) {
- return new View(parent);
- }
- };
-
- TabHost.TabSpec availableTabSpec = tabHost.newTabSpec("available")
- .setIndicator(
- parent.getString(R.string.tab_noninstalled),
- parent.getResources().getDrawable(android.R.drawable.ic_input_add))
- .setContent(factory);
-
- TabHost.TabSpec installedTabSpec = tabHost.newTabSpec("installed")
- .setIndicator(
- parent.getString(R.string.inst),
- parent.getResources().getDrawable(android.R.drawable.star_off))
- .setContent(factory);
-
- TabHost.TabSpec canUpdateTabSpec = tabHost.newTabSpec("canUpdate")
- .setIndicator(
- parent.getString(R.string.tab_updates),
- parent.getResources().getDrawable(android.R.drawable.star_on))
- .setContent(factory);
-
- tabHost.addTab(availableTabSpec);
- tabHost.addTab(installedTabSpec);
- tabHost.addTab(canUpdateTabSpec);
-
- LinearLayout contentView = (LinearLayout)parent.findViewById(R.id.fdroid_layout);
- contentView.addView(tabHost, 0);
-
- tabHost.setOnTabChangedListener( new TabHost.OnTabChangeListener() {
- @Override
- public void onTabChanged(String tabId) {
- int pos = tabHost.getCurrentTab();
- pager.setCurrentItem(pos);
- if (pos == INDEX_CAN_UPDATE)
- removeNotification(1);
- }
- });
- }
-
-
- @Override
- public void selectTab(int index) {
- tabHost.setCurrentTab(index);
- if (index == INDEX_CAN_UPDATE)
- removeNotification(1);
- }
-
- @Override
- public void refreshTabLabel(int index) {
- CharSequence text = getLabel(index);
-
- // Update the count on the 'Updates' tab to show the number available.
- // This is quite unpleasant, but seems to be the only way to do it.
- TextView textView = (TextView) tabHost.getTabWidget().getChildAt(index)
- .findViewById(android.R.id.title);
- textView.setText(text);
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- // Do nothing
- }
-
-}
-
-@TargetApi(11)
-class HoneycombTabManagerImpl extends TabManager {
-
- protected final ActionBar actionBar;
- private Spinner actionBarSpinner = null;
-
- // Used to make sure we only search for the action bar spinner once
- // in each orientation.
- private boolean dirtyConfig = true;
-
- public HoneycombTabManagerImpl(FDroid parent, ViewPager pager) {
- super(parent, pager);
- actionBar = parent.getActionBar();
- }
-
- @Override
public void createTabs() {
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
for (int i = 0; i < pager.getAdapter().getCount(); i ++) {
@@ -198,7 +69,6 @@ class HoneycombTabManagerImpl extends TabManager {
}
}
- @Override
public void selectTab(int index) {
actionBar.setSelectedNavigationItem(index);
Spinner actionBarSpinner = getActionBarSpinner();
@@ -209,13 +79,11 @@ class HoneycombTabManagerImpl extends TabManager {
removeNotification(1);
}
- @Override
public void refreshTabLabel(int index) {
CharSequence text = getLabel(index);
actionBar.getTabAt(index).setText(text);
}
- @Override
public void onConfigurationChanged(Configuration newConfig) {
dirtyConfig = true;
}
diff --git a/src/org/fdroid/fdroid/views/LocalRepoActivity.java b/src/org/fdroid/fdroid/views/LocalRepoActivity.java
index 8bb5582aa..0741eb182 100644
--- a/src/org/fdroid/fdroid/views/LocalRepoActivity.java
+++ b/src/org/fdroid/fdroid/views/LocalRepoActivity.java
@@ -5,7 +5,10 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
-import android.content.*;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
import android.net.Uri;
import android.net.wifi.WifiManager;
@@ -16,12 +19,22 @@ import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
+import android.support.v7.app.ActionBarActivity;
import android.text.TextUtils;
import android.util.Log;
-import android.view.*;
-import android.widget.*;
-
-import org.fdroid.fdroid.*;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.TextView;
+import android.widget.Toast;
+import org.fdroid.fdroid.FDroidApp;
+import org.fdroid.fdroid.PreferencesActivity;
+import org.fdroid.fdroid.QrGenAsyncTask;
+import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.localrepo.LocalRepoManager;
import org.fdroid.fdroid.localrepo.LocalRepoService;
import org.fdroid.fdroid.net.WifiStateChangeService;
@@ -30,8 +43,9 @@ import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
-public class LocalRepoActivity extends Activity {
- private static final String TAG = "LocalRepoActivity";
+public class LocalRepoActivity extends ActionBarActivity {
+
+ private static final String TAG = "org.fdroid.fdroid.LocalRepoActivity";
private ProgressDialog repoProgress;
private WifiManager wifiManager;
@@ -46,8 +60,8 @@ public class LocalRepoActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
((FDroidApp) getApplication()).applyTheme(this);
+ super.onCreate(savedInstanceState);
setContentView(R.layout.local_repo_activity);
enableWifiButton = (Button) findViewById(R.id.enable_wifi);
diff --git a/src/org/fdroid/fdroid/views/QrWizardDownloadActivity.java b/src/org/fdroid/fdroid/views/QrWizardDownloadActivity.java
index 2952be63a..dbdd9b4f9 100644
--- a/src/org/fdroid/fdroid/views/QrWizardDownloadActivity.java
+++ b/src/org/fdroid/fdroid/views/QrWizardDownloadActivity.java
@@ -1,27 +1,31 @@
package org.fdroid.fdroid.views;
-import android.app.Activity;
-import android.content.*;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
-
import org.fdroid.fdroid.*;
import org.fdroid.fdroid.net.WifiStateChangeService;
-public class QrWizardDownloadActivity extends Activity {
- private static final String TAG = "QrWizardDownloadActivity";
+public class QrWizardDownloadActivity extends ActionBarActivity {
+
+ private static final String TAG = "org.fdroid.fdroid.QrWizardDownloadActivity";
@Override
public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
((FDroidApp) getApplication()).applyTheme(this);
+ super.onCreate(savedInstanceState);
setContentView(R.layout.qr_wizard_activity);
TextView instructions = (TextView) findViewById(R.id.qrWizardInstructions);
instructions.setText(R.string.qr_wizard_download_instructions);
diff --git a/src/org/fdroid/fdroid/views/QrWizardWifiNetworkActivity.java b/src/org/fdroid/fdroid/views/QrWizardWifiNetworkActivity.java
index 6e933de92..5d7419e82 100644
--- a/src/org/fdroid/fdroid/views/QrWizardWifiNetworkActivity.java
+++ b/src/org/fdroid/fdroid/views/QrWizardWifiNetworkActivity.java
@@ -1,37 +1,40 @@
package org.fdroid.fdroid.views;
-import android.app.Activity;
-import android.content.*;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
-
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.QrGenAsyncTask;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.net.WifiStateChangeService;
-public class QrWizardWifiNetworkActivity extends Activity {
- private static final String TAG = "QrWizardWifiNetworkActivity";
+public class QrWizardWifiNetworkActivity extends ActionBarActivity {
+ private static final String TAG = "org.fdroid.fdroid.QrWizardWifiNetworkActivity";
private WifiManager wifiManager;
@Override
public void onCreate(Bundle savedInstanceState) {
+ ((FDroidApp) getApplication()).applyTheme(this);
super.onCreate(savedInstanceState);
+
wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
wifiManager.setWifiEnabled(true);
FDroidApp.startLocalRepoService(this);
- ((FDroidApp) getApplication()).applyTheme(this);
setContentView(R.layout.qr_wizard_activity);
TextView instructions = (TextView) findViewById(R.id.qrWizardInstructions);
instructions.setText(R.string.qr_wizard_wifi_network_instructions);
diff --git a/src/org/fdroid/fdroid/views/RepoDetailsActivity.java b/src/org/fdroid/fdroid/views/RepoDetailsActivity.java
index f0daf679d..495603692 100644
--- a/src/org/fdroid/fdroid/views/RepoDetailsActivity.java
+++ b/src/org/fdroid/fdroid/views/RepoDetailsActivity.java
@@ -10,19 +10,19 @@ import android.nfc.NfcAdapter;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
-import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.NavUtils;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
+import android.view.MenuItem;
import android.widget.LinearLayout;
import android.widget.Toast;
-
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Utils;
-import org.fdroid.fdroid.compat.ActionBarCompat;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.views.fragments.RepoDetailsFragment;
-public class RepoDetailsActivity extends FragmentActivity {
+public class RepoDetailsActivity extends ActionBarActivity {
public static final String TAG = "RepoDetailsActivity";
private Repo repo;
@@ -33,7 +33,6 @@ public class RepoDetailsActivity extends FragmentActivity {
protected void onCreate(Bundle savedInstanceState) {
((FDroidApp) getApplication()).applyTheme(this);
-
super.onCreate(savedInstanceState);
long repoId = getIntent().getLongExtra(RepoDetailsFragment.ARG_REPO_ID, 0);
@@ -62,7 +61,7 @@ public class RepoDetailsActivity extends FragmentActivity {
};
repo = RepoProvider.Helper.findById(this, repoId, projection);
- ActionBarCompat.create(this).setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setTitle(repo.getName());
}
@@ -123,4 +122,15 @@ public class RepoDetailsActivity extends FragmentActivity {
finish();
}
}
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
}
diff --git a/src/org/fdroid/fdroid/views/SelectLocalAppsActivity.java b/src/org/fdroid/fdroid/views/SelectLocalAppsActivity.java
index 71fc8ee9c..b438915ec 100644
--- a/src/org/fdroid/fdroid/views/SelectLocalAppsActivity.java
+++ b/src/org/fdroid/fdroid/views/SelectLocalAppsActivity.java
@@ -1,29 +1,30 @@
package org.fdroid.fdroid.views;
-import android.annotation.TargetApi;
-import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
-import android.view.*;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.widget.SearchView;
-
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.PreferencesActivity;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.views.fragments.SelectLocalAppsFragment;
-@TargetApi(11)
-// TODO replace with appcompat-v7
-public class SelectLocalAppsActivity extends Activity {
+public class SelectLocalAppsActivity extends ActionBarActivity {
+
private static final String TAG = "SelectLocalAppsActivity";
private SelectLocalAppsFragment selectLocalAppsFragment = null;
private SearchView searchView;
@Override
protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
((FDroidApp) getApplication()).applyTheme(this);
+ super.onCreate(savedInstanceState);
setContentView(R.layout.select_local_apps_activity);
}
@@ -31,14 +32,14 @@ public class SelectLocalAppsActivity extends Activity {
protected void onResume() {
super.onResume();
if (selectLocalAppsFragment == null)
- selectLocalAppsFragment = (SelectLocalAppsFragment) getFragmentManager()
+ selectLocalAppsFragment = (SelectLocalAppsFragment) getSupportFragmentManager()
.findFragmentById(R.id.fragment_app_list);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.select_local_apps_activity, menu);
- searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
+ searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.action_search));
searchView.setOnQueryTextListener(selectLocalAppsFragment);
return true;
}
diff --git a/src/org/fdroid/fdroid/views/fragments/PreferenceFragment.java b/src/org/fdroid/fdroid/views/fragments/PreferenceFragment.java
new file mode 100644
index 000000000..5ef5ca048
--- /dev/null
+++ b/src/org/fdroid/fdroid/views/fragments/PreferenceFragment.java
@@ -0,0 +1,279 @@
+package org.fdroid.fdroid.views.fragments;
+
+import android.app.AlertDialog;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.EditTextPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import org.fdroid.fdroid.Preferences;
+import org.fdroid.fdroid.PreferencesActivity;
+import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.installer.CheckRootAsyncTask;
+import org.fdroid.fdroid.installer.Installer;
+
+public class PreferenceFragment
+ extends android.support.v4.preference.PreferenceFragment
+ implements SharedPreferences.OnSharedPreferenceChangeListener {
+
+ private static String[] summariesToUpdate = {
+ Preferences.PREF_UPD_INTERVAL,
+ Preferences.PREF_UPD_WIFI_ONLY,
+ Preferences.PREF_UPD_NOTIFY,
+ Preferences.PREF_UPD_HISTORY,
+ Preferences.PREF_ROOTED,
+ Preferences.PREF_INCOMP_VER,
+ Preferences.PREF_THEME,
+ Preferences.PREF_PERMISSIONS,
+ Preferences.PREF_COMPACT_LAYOUT,
+ Preferences.PREF_IGN_TOUCH,
+ Preferences.PREF_LOCAL_REPO_BONJOUR,
+ Preferences.PREF_LOCAL_REPO_NAME,
+ Preferences.PREF_LOCAL_REPO_HTTPS,
+ Preferences.PREF_CACHE_APK,
+ Preferences.PREF_EXPERT,
+ Preferences.PREF_ROOT_INSTALLER,
+ Preferences.PREF_SYSTEM_INSTALLER
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.preferences);
+ }
+
+ protected void onoffSummary(String key, int on, int off) {
+ CheckBoxPreference pref = (CheckBoxPreference)findPreference(key);
+ if (pref.isChecked()) {
+ pref.setSummary(on);
+ } else {
+ pref.setSummary(off);
+ }
+ }
+
+ protected void entrySummary(String key) {
+ ListPreference pref = (ListPreference)findPreference(key);
+ pref.setSummary(pref.getEntry());
+ }
+
+ protected void textSummary(String key, int resId) {
+ EditTextPreference pref = (EditTextPreference)findPreference(key);
+ pref.setSummary(getString(resId, pref.getText()));
+ }
+
+ protected void updateSummary(String key, boolean changing) {
+
+ int result = 0;
+
+ if (key.equals(Preferences.PREF_UPD_INTERVAL)) {
+ ListPreference pref = (ListPreference)findPreference(
+ Preferences.PREF_UPD_INTERVAL);
+ int interval = Integer.parseInt(pref.getValue());
+ Preference onlyOnWifi = findPreference(
+ Preferences.PREF_UPD_WIFI_ONLY);
+ onlyOnWifi.setEnabled(interval > 0);
+ if (interval == 0) {
+ pref.setSummary(R.string.update_interval_zero);
+ } else {
+ pref.setSummary(pref.getEntry());
+ }
+
+ } else if (key.equals(Preferences.PREF_UPD_WIFI_ONLY)) {
+ onoffSummary(key, R.string.automatic_scan_wifi_on,
+ R.string.automatic_scan_wifi_off);
+
+ } else if (key.equals(Preferences.PREF_UPD_NOTIFY)) {
+ onoffSummary(key, R.string.notify_on,
+ R.string.notify_off);
+
+ } else if (key.equals(Preferences.PREF_UPD_HISTORY)) {
+ textSummary(key, R.string.update_history_summ);
+
+ } else if (key.equals(Preferences.PREF_PERMISSIONS)) {
+ onoffSummary(key, R.string.showPermissions_on,
+ R.string.showPermissions_off);
+
+ } else if (key.equals(Preferences.PREF_COMPACT_LAYOUT)) {
+ onoffSummary(key, R.string.compactlayout_on,
+ R.string.compactlayout_off);
+
+ } else if (key.equals(Preferences.PREF_THEME)) {
+ entrySummary(key);
+ if (changing) {
+ result |= PreferencesActivity.RESULT_RESTART;
+ getActivity().setResult(result);
+ }
+
+ } else if (key.equals(Preferences.PREF_INCOMP_VER)) {
+ onoffSummary(key, R.string.show_incompat_versions_on,
+ R.string.show_incompat_versions_off);
+
+ } else if (key.equals(Preferences.PREF_ROOTED)) {
+ onoffSummary(key, R.string.rooted_on,
+ R.string.rooted_off);
+
+ } else if (key.equals(Preferences.PREF_IGN_TOUCH)) {
+ onoffSummary(key, R.string.ignoreTouch_on,
+ R.string.ignoreTouch_off);
+
+ } else if (key.equals(Preferences.PREF_LOCAL_REPO_BONJOUR)) {
+ onoffSummary(key, R.string.local_repo_bonjour_on,
+ R.string.local_repo_bonjour_off);
+
+ } else if (key.equals(Preferences.PREF_LOCAL_REPO_NAME)) {
+ textSummary(key, R.string.local_repo_name_summary);
+
+ } else if (key.equals(Preferences.PREF_LOCAL_REPO_HTTPS)) {
+ onoffSummary(key, R.string.local_repo_https_on,
+ R.string.local_repo_https_off);
+
+ } else if (key.equals(Preferences.PREF_CACHE_APK)) {
+ onoffSummary(key, R.string.cache_downloaded_on,
+ R.string.cache_downloaded_off);
+
+ } else if (key.equals(Preferences.PREF_EXPERT)) {
+ onoffSummary(key, R.string.expert_on,
+ R.string.expert_off);
+
+ } else if (key.equals(Preferences.PREF_ROOT_INSTALLER)) {
+ onoffSummary(key, R.string.root_installer_on,
+ R.string.root_installer_off);
+
+ } else if (key.equals(Preferences.PREF_SYSTEM_INSTALLER)) {
+ onoffSummary(key, R.string.system_installer_on,
+ R.string.system_installer_off);
+
+ }
+ }
+
+ /**
+ * Initializes RootInstaller preference. This method ensures that the preference can only be checked and persisted
+ * when the user grants root access for F-Droid.
+ */
+ protected void initRootInstallerPreference() {
+ CheckBoxPreference pref = (CheckBoxPreference) findPreference(Preferences.PREF_ROOT_INSTALLER);
+
+ // we are handling persistence ourself!
+ pref.setPersistent(false);
+
+ pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ final CheckBoxPreference pref = (CheckBoxPreference) preference;
+
+ if (pref.isChecked()) {
+ CheckRootAsyncTask checkTask = new CheckRootAsyncTask(getActivity(), new CheckRootAsyncTask.CheckRootCallback() {
+
+ @Override
+ public void onRootCheck(boolean rootGranted) {
+ if (rootGranted) {
+ // root access granted
+ SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
+ editor.putBoolean(Preferences.PREF_ROOT_INSTALLER, true);
+ editor.commit();
+ pref.setChecked(true);
+ } else {
+ // root access denied
+ SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
+ editor.putBoolean(Preferences.PREF_ROOT_INSTALLER, false);
+ editor.commit();
+ pref.setChecked(false);
+
+ AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity());
+ alertBuilder.setTitle(R.string.root_access_denied_title);
+ alertBuilder.setMessage(getActivity().getString(R.string.root_access_denied_body));
+ alertBuilder.setNeutralButton(android.R.string.ok, null);
+ alertBuilder.create().show();
+ }
+ }
+ });
+ checkTask.execute();
+ } else {
+ SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
+ editor.putBoolean(Preferences.PREF_ROOT_INSTALLER, false);
+ editor.commit();
+ pref.setChecked(false);
+ }
+
+ return true;
+ }
+ });
+ }
+
+ /**
+ * Initializes SystemInstaller preference, which can only be enabled when F-Droid is installed as a system-app
+ */
+ protected void initSystemInstallerPreference() {
+ CheckBoxPreference pref = (CheckBoxPreference) findPreference(Preferences.PREF_SYSTEM_INSTALLER);
+
+ // we are handling persistence ourself!
+ pref.setPersistent(false);
+
+ pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ final CheckBoxPreference pref = (CheckBoxPreference) preference;
+
+ if (pref.isChecked()) {
+ if (Installer.hasSystemPermissions(getActivity(), getActivity().getPackageManager())) {
+ // system-permission are granted, i.e. F-Droid is a system-app
+ SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
+ editor.putBoolean(Preferences.PREF_SYSTEM_INSTALLER, true);
+ editor.commit();
+ pref.setChecked(true);
+ } else {
+ // system-permission not available
+ SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
+ editor.putBoolean(Preferences.PREF_SYSTEM_INSTALLER, false);
+ editor.commit();
+ pref.setChecked(false);
+
+ AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity());
+ alertBuilder.setTitle(R.string.system_permission_denied_title);
+ alertBuilder.setMessage(getActivity().getString(R.string.system_permission_denied_body));
+ alertBuilder.setNeutralButton(android.R.string.ok, null);
+ alertBuilder.create().show();
+ }
+ } else {
+ SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
+ editor.putBoolean(Preferences.PREF_SYSTEM_INSTALLER, false);
+ editor.commit();
+ pref.setChecked(false);
+ }
+
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+
+ for (String key : summariesToUpdate) {
+ updateSummary(key, false);
+ }
+
+ initRootInstallerPreference();
+ initSystemInstallerPreference();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(
+ SharedPreferences sharedPreferences, String key) {
+ updateSummary(key, true);
+ }
+
+
+}
diff --git a/src/org/fdroid/fdroid/views/fragments/SelectLocalAppsFragment.java b/src/org/fdroid/fdroid/views/fragments/SelectLocalAppsFragment.java
index 785b9ac25..0429f09be 100644
--- a/src/org/fdroid/fdroid/views/fragments/SelectLocalAppsFragment.java
+++ b/src/org/fdroid/fdroid/views/fragments/SelectLocalAppsFragment.java
@@ -14,24 +14,25 @@ limitations under the License.
package org.fdroid.fdroid.views.fragments;
-import android.annotation.TargetApi;
-import android.app.ListFragment;
-import android.app.LoaderManager.LoaderCallbacks;
-import android.content.CursorLoader;
-import android.content.Loader;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.view.ActionMode;
import android.view.View;
-import android.widget.*;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
import android.widget.SearchView.OnQueryTextListener;
+import android.widget.SimpleCursorAdapter;
import android.widget.SimpleCursorAdapter.ViewBinder;
-
+import android.widget.TextView;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.data.InstalledAppProvider;
@@ -41,10 +42,8 @@ import org.fdroid.fdroid.views.SelectLocalAppsActivity;
import java.util.HashSet;
-//TODO replace with appcompat-v7
-@TargetApi(11)
public class SelectLocalAppsFragment extends ListFragment
- implements LoaderCallbacks, OnQueryTextListener {
+ implements LoaderManager.LoaderCallbacks, OnQueryTextListener {
private PackageManager packageManager;
private Drawable defaultAppIcon;
@@ -95,7 +94,7 @@ public class SelectLocalAppsFragment extends ListFragment
Drawable icon;
try {
icon = packageManager.getApplicationIcon(packageName);
- } catch (NameNotFoundException e) {
+ } catch (PackageManager.NameNotFoundException e) {
icon = defaultAppIcon;
}
iconView.setImageDrawable(icon);