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