diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a9d100ba0..c36cac993 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -126,16 +126,6 @@ android:resource="@xml/install_history_file_provider" /> - - - - - - - - - - - - * Copyright (C) 2013 Stefan Völkel, bd@bc-bd.org - * Copyright (C) 2015 Nico Alt, nicoalt@posteo.org - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 3 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package org.fdroid.fdroid; - -import android.app.Activity; -import android.app.PendingIntent; -import android.bluetooth.BluetoothAdapter; -import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.database.ContentObserver; -import android.graphics.Bitmap; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; -import android.support.v4.app.ListFragment; -import android.support.v4.app.NavUtils; -import android.support.v4.content.ContextCompat; -import android.support.v4.content.LocalBroadcastManager; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; -import android.text.Html; -import android.text.Layout; -import android.text.Selection; -import android.text.Spannable; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.format.DateFormat; -import android.text.method.LinkMovementMethod; -import android.text.style.ClickableSpan; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import com.nostra13.universalimageloader.core.DisplayImageOptions; -import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.assist.ImageScaleType; - -import org.fdroid.fdroid.data.Apk; -import org.fdroid.fdroid.data.ApkProvider; -import org.fdroid.fdroid.data.App; -import org.fdroid.fdroid.data.AppPrefs; -import org.fdroid.fdroid.data.AppPrefsProvider; -import org.fdroid.fdroid.data.AppProvider; -import org.fdroid.fdroid.data.InstalledApp; -import org.fdroid.fdroid.data.InstalledAppProvider; -import org.fdroid.fdroid.data.RepoProvider; -import org.fdroid.fdroid.data.Schema; -import org.fdroid.fdroid.installer.InstallManagerService; -import org.fdroid.fdroid.installer.Installer; -import org.fdroid.fdroid.installer.InstallerFactory; -import org.fdroid.fdroid.installer.InstallerService; -import org.fdroid.fdroid.net.Downloader; -import org.fdroid.fdroid.net.DownloaderService; -import org.fdroid.fdroid.privileged.views.AppDiff; -import org.fdroid.fdroid.privileged.views.AppSecurityPermissions; - -import java.util.List; - -public class AppDetails extends AppCompatActivity { - - private static final String TAG = "AppDetails"; - - private static final int REQUEST_ENABLE_BLUETOOTH = 2; - private static final int REQUEST_PERMISSION_DIALOG = 3; - private static final int REQUEST_UNINSTALL_DIALOG = 4; - - public static final String EXTRA_FROM = "from"; - public static final String EXTRA_HINT_SEARCHING = "searching"; - - private FDroidApp fdroidApp; - private ApkListAdapter adapter; - - /** - * Check if {@code packageName} is currently visible to the user. - */ - public static boolean isAppVisible(String packageName) { - return packageName != null && packageName.equals(visiblePackageName); - } - - private static String visiblePackageName; - - private static class ViewHolder { - TextView version; - TextView status; - TextView repository; - TextView size; - TextView api; - TextView incompatibleReasons; - TextView buildtype; - TextView added; - TextView nativecode; - } - - // observer to update view when package has been installed/deleted - private AppObserver myAppObserver; - - class AppObserver extends ContentObserver { - - AppObserver(Handler handler) { - super(handler); - } - - @Override - public void onChange(boolean selfChange) { - onChange(selfChange, null); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - onAppChanged(); - } - - } - - class ApkListAdapter extends ArrayAdapter { - - private final LayoutInflater inflater = (LayoutInflater) context.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - - ApkListAdapter(Context context, App app) { - super(context, 0); - final List apks = ApkProvider.Helper.findByPackageName(context, app.packageName); - for (final Apk apk : apks) { - if (apk.compatible || Preferences.get().showIncompatibleVersions()) { - add(apk); - } - } - } - - private String getInstalledStatus(final Apk apk) { - // Definitely not installed. - if (apk.versionCode != app.installedVersionCode) { - return getString(R.string.app_not_installed); - } - // Definitely installed this version. - if (apk.sig != null && apk.sig.equals(app.installedSig)) { - return getString(R.string.app_installed); - } - // Installed the same version, but from someplace else. - final String installerPkgName; - try { - installerPkgName = packageManager.getInstallerPackageName(app.packageName); - } catch (IllegalArgumentException e) { - Log.w(TAG, "Application " + app.packageName + " is not installed anymore"); - return getString(R.string.app_not_installed); - } - if (TextUtils.isEmpty(installerPkgName)) { - return getString(R.string.app_inst_unknown_source); - } - final String installerLabel = InstalledAppProvider - .getApplicationLabel(context, installerPkgName); - return getString(R.string.app_inst_known_source, installerLabel); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - - java.text.DateFormat df = DateFormat.getDateFormat(context); - final Apk apk = getItem(position); - ViewHolder holder; - - if (convertView == null) { - convertView = inflater.inflate(R.layout.apklistitem, parent, false); - - holder = new ViewHolder(); - holder.version = (TextView) convertView.findViewById(R.id.version); - holder.status = (TextView) convertView.findViewById(R.id.status); - holder.repository = (TextView) convertView.findViewById(R.id.repository); - holder.size = (TextView) convertView.findViewById(R.id.size); - holder.api = (TextView) convertView.findViewById(R.id.api); - holder.incompatibleReasons = (TextView) convertView.findViewById(R.id.incompatible_reasons); - holder.buildtype = (TextView) convertView.findViewById(R.id.buildtype); - holder.added = (TextView) convertView.findViewById(R.id.added); - holder.nativecode = (TextView) convertView.findViewById(R.id.nativecode); - - convertView.setTag(holder); - } else { - holder = (ViewHolder) convertView.getTag(); - } - - holder.version.setText(getString(R.string.version) - + " " + apk.versionName - + (apk.versionCode == app.suggestedVersionCode ? " ☆" : "")); - - holder.status.setText(getInstalledStatus(apk)); - - holder.repository.setText(getString(R.string.repo_provider, - RepoProvider.Helper.findById(getContext(), apk.repoId).getName())); - - if (apk.size > 0) { - holder.size.setText(Utils.getFriendlySize(apk.size)); - holder.size.setVisibility(View.VISIBLE); - } else { - holder.size.setVisibility(View.GONE); - } - - if (!Preferences.get().expertMode()) { - holder.api.setVisibility(View.GONE); - } else if (apk.minSdkVersion > 0 && apk.maxSdkVersion < Apk.SDK_VERSION_MAX_VALUE) { - holder.api.setText(getString(R.string.minsdk_up_to_maxsdk, - Utils.getAndroidVersionName(apk.minSdkVersion), - Utils.getAndroidVersionName(apk.maxSdkVersion))); - holder.api.setVisibility(View.VISIBLE); - } else if (apk.minSdkVersion > 0) { - holder.api.setText(getString(R.string.minsdk_or_later, - Utils.getAndroidVersionName(apk.minSdkVersion))); - holder.api.setVisibility(View.VISIBLE); - } else if (apk.maxSdkVersion > 0) { - holder.api.setText(getString(R.string.up_to_maxsdk, - Utils.getAndroidVersionName(apk.maxSdkVersion))); - holder.api.setVisibility(View.VISIBLE); - } - - if (apk.srcname != null) { - holder.buildtype.setText("source"); - } else { - holder.buildtype.setText("bin"); - } - - if (apk.added != null) { - holder.added.setText(getString(R.string.added_on, - df.format(apk.added))); - holder.added.setVisibility(View.VISIBLE); - } else { - holder.added.setVisibility(View.GONE); - } - - if (Preferences.get().expertMode() && apk.nativecode != null) { - holder.nativecode.setText(TextUtils.join(" ", apk.nativecode)); - holder.nativecode.setVisibility(View.VISIBLE); - } else { - holder.nativecode.setVisibility(View.GONE); - } - - if (apk.incompatibleReasons != null) { - holder.incompatibleReasons.setText( - getResources().getString( - R.string.requires_features, - TextUtils.join(", ", apk.incompatibleReasons))); - holder.incompatibleReasons.setVisibility(View.VISIBLE); - } else { - holder.incompatibleReasons.setVisibility(View.GONE); - } - - // Disable it all if it isn't compatible... - final View[] views = { - convertView, - holder.version, - holder.status, - holder.repository, - holder.size, - holder.api, - holder.buildtype, - holder.added, - holder.nativecode, - }; - - for (final View v : views) { - v.setEnabled(apk.compatible); - } - - return convertView; - } - } - - private static final int INSTALL = Menu.FIRST; - private static final int UNINSTALL = Menu.FIRST + 1; - private static final int IGNOREALL = Menu.FIRST + 2; - private static final int IGNORETHIS = Menu.FIRST + 3; - private static final int LAUNCH = Menu.FIRST + 4; - private static final int SHARE = Menu.FIRST + 5; - private static final int SEND_VIA_BLUETOOTH = Menu.FIRST + 6; - - private App app; - private PackageManager packageManager; - private String activeDownloadUrlString; - private LocalBroadcastManager localBroadcastManager; - - private AppPrefs startingPrefs; - - private final Context context = this; - - private AppDetailsHeaderFragment headerFragment; - - /** - * Stores relevant data that we want to keep track of when destroying the activity - * with the expectation of it being recreated straight away (e.g. after an - * orientation change). One of the major things is that we want the download thread - * to stay active, but for it not to trigger any UI stuff (e.g. progress bar) - * between the activity being destroyed and recreated. - */ - private static class ConfigurationChangeHelper { - - public final String urlString; - public final App app; - - ConfigurationChangeHelper(String urlString, App app) { - this.urlString = urlString; - this.app = app; - } - } - - /** - * Attempt to extract the packageName from the intent which launched this activity. - * @return May return null, if we couldn't find the packageName. This should - * never happen as AppDetails is only to be called by the FDroid activity - * and not externally. - */ - private String getPackageNameFromIntent(Intent intent) { - if (!intent.hasExtra(AppDetails2.EXTRA_APPID)) { - Log.e(TAG, "No package name found in the intent!"); - return null; - } - - return intent.getStringExtra(AppDetails2.EXTRA_APPID); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - - fdroidApp = (FDroidApp) getApplication(); - fdroidApp.applyTheme(this); - - super.onCreate(savedInstanceState); - - // Must be called *after* super.onCreate(), as that is where the action bar - // compat implementation is assigned in the ActionBarActivity base class. - supportRequestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - - Intent intent = getIntent(); - if (intent.hasExtra(EXTRA_FROM)) { - setTitle(intent.getStringExtra(EXTRA_FROM)); - } - - packageManager = getPackageManager(); - - // Get the preferences we're going to use in this Activity... - ConfigurationChangeHelper previousData = (ConfigurationChangeHelper) getLastCustomNonConfigurationInstance(); - if (previousData != null) { - Utils.debugLog(TAG, "Recreating view after configuration change."); - activeDownloadUrlString = previousData.urlString; - if (activeDownloadUrlString != null) { - Utils.debugLog(TAG, "Download was in progress before the configuration change, so we will start to listen to its events again."); - } - app = previousData.app; - setApp(app); - } else { - if (!reset(getPackageNameFromIntent(intent))) { - finish(); - return; - } - } - - // Set up the list... - adapter = new ApkListAdapter(this, app); - - // 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); - - getSupportActionBar().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(); - } - - localBroadcastManager = LocalBroadcastManager.getInstance(this); - } - - @Override - protected void onStart() { - super.onStart(); - // register observer to know when install status changes - myAppObserver = new AppObserver(new Handler()); - getContentResolver().registerContentObserver( - AppProvider.getHighestPriorityMetadataUri(app.packageName), - true, - myAppObserver); - } - - @Override - protected void onResume() { - super.onResume(); - updateNotificationsForApp(); - } - - @Override - protected void onResumeFragments() { - // Must be called before super.onResumeFragments(), as the fragments depend on the active - // url being correctly set in order to know whether or not to show the download progress bar. - calcActiveDownloadUrlString(app.packageName); - - super.onResumeFragments(); - - headerFragment = (AppDetailsHeaderFragment) getSupportFragmentManager().findFragmentById(R.id.header); - refreshApkList(); - supportInvalidateOptionsMenu(); - if (DownloaderService.isQueuedOrActive(activeDownloadUrlString)) { - registerDownloaderReceiver(); - } - visiblePackageName = app.packageName; - } - - /** - * Remove progress listener, suppress progress bar, set downloadHandler to null. - */ - private void cleanUpFinishedDownload() { - activeDownloadUrlString = null; - if (headerFragment != null) { - headerFragment.removeProgress(); - } - unregisterDownloaderReceiver(); - } - - protected void onStop() { - super.onStop(); - visiblePackageName = null; - getContentResolver().unregisterContentObserver(myAppObserver); - - // When leaving the app details, make sure to refresh app status for this app, since - // we might want to show notifications for it now. - updateNotificationsForApp(); - } - - /** - * Some notifications (like "downloading" and "installed") are not shown for this app if it is open in app details. - * When closing, we need to refresh the notifications, so they are displayed again. - */ - private void updateNotificationsForApp() { - if (app != null) { - AppUpdateStatusManager appUpdateStatusManager = AppUpdateStatusManager.getInstance(this); - for (AppUpdateStatusManager.AppUpdateStatus status : appUpdateStatusManager.getByPackageName(app.packageName)) { - if (status.status == AppUpdateStatusManager.Status.Installed) { - appUpdateStatusManager.removeApk(status.getUniqueKey()); - } else { - appUpdateStatusManager.refreshApk(status.getUniqueKey()); - } - } - } - } - - @Override - protected void onPause() { - super.onPause(); - // save the active URL for this app in case we come back - getPreferences(MODE_PRIVATE) - .edit() - .putString(getPackageNameFromIntent(getIntent()), activeDownloadUrlString) - .apply(); - if (app != null && !app.getPrefs(this).equals(startingPrefs)) { - Utils.debugLog(TAG, "Updating 'ignore updates', as it has changed since we started the activity..."); - AppPrefsProvider.Helper.update(this, app, app.getPrefs(this)); - } - unregisterDownloaderReceiver(); - } - - private void unregisterDownloaderReceiver() { - if (localBroadcastManager == null) { - return; - } - localBroadcastManager.unregisterReceiver(downloadReceiver); - } - - private void registerDownloaderReceiver() { - if (activeDownloadUrlString != null) { // if a download is active - String url = activeDownloadUrlString; - localBroadcastManager.registerReceiver(downloadReceiver, - DownloaderService.getIntentFilter(url)); - } - } - - private final BroadcastReceiver downloadReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - switch (intent.getAction()) { - case Downloader.ACTION_STARTED: - if (headerFragment != null) { - headerFragment.startProgress(); - } - break; - case Downloader.ACTION_PROGRESS: - if (headerFragment != null) { - headerFragment.updateProgress(intent.getIntExtra(Downloader.EXTRA_BYTES_READ, -1), - intent.getIntExtra(Downloader.EXTRA_TOTAL_BYTES, -1)); - } - break; - case Downloader.ACTION_COMPLETE: - // Starts the install process one the download is complete. - cleanUpFinishedDownload(); - localBroadcastManager.registerReceiver(installReceiver, - Installer.getInstallIntentFilter(intent.getData())); - break; - case Downloader.ACTION_INTERRUPTED: - if (intent.hasExtra(Downloader.EXTRA_ERROR_MESSAGE)) { - String msg = intent.getStringExtra(Downloader.EXTRA_ERROR_MESSAGE) - + " " + intent.getDataString(); - Toast.makeText(context, R.string.download_error, Toast.LENGTH_SHORT).show(); - Toast.makeText(context, msg, Toast.LENGTH_LONG).show(); - } else { // user canceled - Toast.makeText(context, R.string.details_notinstalled, Toast.LENGTH_LONG).show(); - } - cleanUpFinishedDownload(); - break; - default: - throw new RuntimeException("intent action not handled!"); - } - } - }; - - private final BroadcastReceiver installReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - switch (intent.getAction()) { - case Installer.ACTION_INSTALL_STARTED: - headerFragment.startProgress(false); - headerFragment.showIndeterminateProgress(getString(R.string.installing)); - break; - case Installer.ACTION_INSTALL_COMPLETE: - headerFragment.removeProgress(); - - localBroadcastManager.unregisterReceiver(this); - break; - case Installer.ACTION_INSTALL_INTERRUPTED: - headerFragment.removeProgress(); - onAppChanged(); - - String errorMessage = - intent.getStringExtra(Installer.EXTRA_ERROR_MESSAGE); - - if (!TextUtils.isEmpty(errorMessage) && !isFinishing()) { - Log.e(TAG, "install aborted with errorMessage: " + errorMessage); - - String title = String.format( - getString(R.string.install_error_notify_title), - app.name); - - AlertDialog.Builder alertBuilder = new AlertDialog.Builder(AppDetails.this); - alertBuilder.setTitle(title); - alertBuilder.setMessage(errorMessage); - alertBuilder.setNeutralButton(android.R.string.ok, null); - alertBuilder.create().show(); - } - - localBroadcastManager.unregisterReceiver(this); - break; - case Installer.ACTION_INSTALL_USER_INTERACTION: - PendingIntent installPendingIntent = - intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI); - - try { - installPendingIntent.send(); - } catch (PendingIntent.CanceledException e) { - Log.e(TAG, "PI canceled", e); - } - - break; - default: - throw new RuntimeException("intent action not handled!"); - } - } - }; - - private final BroadcastReceiver uninstallReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - switch (intent.getAction()) { - case Installer.ACTION_UNINSTALL_STARTED: - headerFragment.startProgress(false); - headerFragment.showIndeterminateProgress(getString(R.string.uninstalling)); - break; - case Installer.ACTION_UNINSTALL_COMPLETE: - headerFragment.removeProgress(); - onAppChanged(); - - localBroadcastManager.unregisterReceiver(this); - break; - case Installer.ACTION_UNINSTALL_INTERRUPTED: - headerFragment.removeProgress(); - - String errorMessage = - intent.getStringExtra(Installer.EXTRA_ERROR_MESSAGE); - - if (!TextUtils.isEmpty(errorMessage)) { - Log.e(TAG, "uninstall aborted with errorMessage: " + errorMessage); - - AlertDialog.Builder alertBuilder = new AlertDialog.Builder(AppDetails.this); - alertBuilder.setTitle(R.string.uninstall_error_notify_title); - alertBuilder.setMessage(errorMessage); - alertBuilder.setNeutralButton(android.R.string.ok, null); - alertBuilder.create().show(); - } - - localBroadcastManager.unregisterReceiver(this); - break; - case Installer.ACTION_UNINSTALL_USER_INTERACTION: - PendingIntent uninstallPendingIntent = - intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI); - - try { - uninstallPendingIntent.send(); - } catch (PendingIntent.CanceledException e) { - Log.e(TAG, "PI canceled", e); - } - - break; - default: - throw new RuntimeException("intent action not handled!"); - } - } - }; - - private void onAppChanged() { - if (!reset(app.packageName)) { - this.finish(); - return; - } - - refreshApkList(); - refreshHeader(); - supportInvalidateOptionsMenu(); - } - - @Override - public Object onRetainCustomNonConfigurationInstance() { - return new ConfigurationChangeHelper(activeDownloadUrlString, app); - } - - @Override - protected void onDestroy() { - unregisterDownloaderReceiver(); - super.onDestroy(); - } - - // Reset the display and list contents. Used when entering the activity, and - // also when something has been installed/uninstalled. - // Return true if the app was found, false otherwise. - private boolean reset(String packageName) { - - Utils.debugLog(TAG, "Getting application details for " + packageName); - App newApp = null; - - calcActiveDownloadUrlString(packageName); - - if (!TextUtils.isEmpty(packageName)) { - newApp = AppProvider.Helper.findHighestPriorityMetadata(getContentResolver(), packageName); - } - - setApp(newApp); - - return this.app != null; - } - - private void calcActiveDownloadUrlString(String packageName) { - String urlString = getPreferences(MODE_PRIVATE).getString(packageName, null); - if (DownloaderService.isQueuedOrActive(urlString)) { - activeDownloadUrlString = urlString; - } else { - // this URL is no longer active, remove it - getPreferences(MODE_PRIVATE).edit().remove(packageName).apply(); - } - } - - /** - * If passed null, this will show a message to the user ("Could not find app ..." or something - * like that) and then finish the activity. - */ - private void setApp(App newApp) { - if (newApp == null) { - Toast.makeText(this, R.string.no_such_app, Toast.LENGTH_LONG).show(); - finish(); - return; - } - - app = newApp; - - startingPrefs = app.getPrefs(this).createClone(); - } - - private void refreshApkList() { - adapter.notifyDataSetChanged(); - } - - private void refreshHeader() { - if (headerFragment != null) { - headerFragment.updateViews(); - } - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - menu.clear(); - if (app == null) { - return true; - } - - if (packageManager.getLaunchIntentForPackage(app.packageName) != null && app.canAndWantToUpdate(this)) { - MenuItemCompat.setShowAsAction(menu.add( - Menu.NONE, LAUNCH, 1, R.string.menu_launch) - .setIcon(R.drawable.ic_play_arrow_white), - MenuItemCompat.SHOW_AS_ACTION_IF_ROOM | - MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT); - } - - if (app.isInstalled()) { - MenuItemCompat.setShowAsAction(menu.add( - Menu.NONE, UNINSTALL, 1, R.string.menu_uninstall) - .setIcon(R.drawable.ic_delete_white), - MenuItemCompat.SHOW_AS_ACTION_IF_ROOM | - MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT); - } - - MenuItemCompat.setShowAsAction(menu.add( - Menu.NONE, SHARE, 1, R.string.menu_share) - .setIcon(R.drawable.ic_share_white), - MenuItemCompat.SHOW_AS_ACTION_IF_ROOM | - MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT); - - menu.add(Menu.NONE, IGNOREALL, 2, R.string.menu_ignore_all) - .setIcon(R.drawable.ic_do_not_disturb_white) - .setCheckable(true) - .setChecked(app.getPrefs(context).ignoreAllUpdates); - - if (app.hasUpdates()) { - menu.add(Menu.NONE, IGNORETHIS, 2, R.string.menu_ignore_this) - .setIcon(R.drawable.ic_do_not_disturb_white) - .setCheckable(true) - .setChecked(app.getPrefs(context).ignoreThisUpdate >= app.suggestedVersionCode); - } - - // Ignore on devices without Bluetooth - if (app.isInstalled() && fdroidApp.bluetoothAdapter != null) { - menu.add(Menu.NONE, SEND_VIA_BLUETOOTH, 3, R.string.send_via_bluetooth) - .setIcon(R.drawable.ic_bluetooth_white); - } - return true; - } - - private void tryOpenUri(String s) { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(s)); - if (intent.resolveActivity(packageManager) == null) { - Toast.makeText(this, - getString(R.string.no_handler_app, intent.getDataString()), - Toast.LENGTH_LONG).show(); - return; - } - startActivity(intent); - } - - private static final class SafeLinkMovementMethod extends LinkMovementMethod { - - private static SafeLinkMovementMethod instance; - - private final Context ctx; - - private SafeLinkMovementMethod(Context ctx) { - this.ctx = ctx; - } - - public static SafeLinkMovementMethod getInstance(Context ctx) { - if (instance == null) { - instance = new SafeLinkMovementMethod(ctx); - } - return instance; - } - - private static CharSequence getLink(TextView widget, Spannable buffer, - MotionEvent event) { - int x = (int) event.getX(); - int y = (int) event.getY(); - x -= widget.getTotalPaddingLeft(); - y -= widget.getTotalPaddingTop(); - x += widget.getScrollX(); - y += widget.getScrollY(); - - Layout layout = widget.getLayout(); - final int line = layout.getLineForVertical(y); - final int off = layout.getOffsetForHorizontal(line, x); - final ClickableSpan[] links = buffer.getSpans(off, off, ClickableSpan.class); - - if (links.length > 0) { - final ClickableSpan link = links[0]; - final Spanned s = (Spanned) widget.getText(); - return s.subSequence(s.getSpanStart(link), s.getSpanEnd(link)); - } - return "null"; - } - - @Override - public boolean onTouchEvent(@NonNull TextView widget, @NonNull Spannable buffer, - @NonNull MotionEvent event) { - try { - return super.onTouchEvent(widget, buffer, event); - } catch (ActivityNotFoundException ex) { - Selection.removeSelection(buffer); - final CharSequence link = getLink(widget, buffer, event); - Toast.makeText(ctx, - ctx.getString(R.string.no_handler_app, link), - Toast.LENGTH_LONG).show(); - return true; - } - } - - } - - private void navigateUp() { - NavUtils.navigateUpFromSameTask(this); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - - case android.R.id.home: - if (getIntent().hasExtra(EXTRA_HINT_SEARCHING)) { - finish(); - } else { - navigateUp(); - } - return true; - - case LAUNCH: - launchApk(app.packageName); - return true; - - case SHARE: - shareApp(app); - return true; - - case INSTALL: - // Note that this handles updating as well as installing. - if (app.suggestedVersionCode > 0) { - final Apk apkToInstall = ApkProvider.Helper.findApkFromAnyRepo(this, app.packageName, app.suggestedVersionCode); - install(apkToInstall); - } - return true; - - case UNINSTALL: - uninstallApk(); - return true; - - case IGNOREALL: - app.getPrefs(this).ignoreAllUpdates ^= true; - item.setChecked(app.getPrefs(this).ignoreAllUpdates); - return true; - - case IGNORETHIS: - if (app.getPrefs(this).ignoreThisUpdate >= app.suggestedVersionCode) { - app.getPrefs(this).ignoreThisUpdate = 0; - } else { - app.getPrefs(this).ignoreThisUpdate = app.suggestedVersionCode; - } - item.setChecked(app.getPrefs(this).ignoreThisUpdate > 0); - return true; - - case SEND_VIA_BLUETOOTH: - /* - * If Bluetooth has not been enabled/turned on, then - * enabling device discoverability will automatically enable Bluetooth - */ - Intent discoverBt = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); - discoverBt.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 121); - startActivityForResult(discoverBt, REQUEST_ENABLE_BLUETOOTH); - // if this is successful, the Bluetooth transfer is started - return true; - - } - return super.onOptionsItemSelected(item); - } - - // Install the version of this app denoted by 'app.curApk'. - private void install(final Apk apk) { - if (isFinishing()) { - return; - } - - if (!apk.compatible) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setMessage(R.string.installIncompatible); - builder.setPositiveButton(R.string.yes, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, - int whichButton) { - initiateInstall(apk); - } - }); - builder.setNegativeButton(R.string.no, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, - int whichButton) { - } - }); - AlertDialog alert = builder.create(); - alert.show(); - return; - } - if (app.installedSig != null && apk.sig != null - && !apk.sig.equals(app.installedSig)) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setMessage(R.string.SignatureMismatch).setPositiveButton( - R.string.ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - AlertDialog alert = builder.create(); - alert.show(); - return; - } - initiateInstall(apk); - } - - private void initiateInstall(Apk apk) { - Installer installer = InstallerFactory.create(this, apk); - Intent intent = installer.getPermissionScreen(); - if (intent != null) { - // permission screen required - Utils.debugLog(TAG, "permission screen required"); - startActivityForResult(intent, REQUEST_PERMISSION_DIALOG); - return; - } - - startInstall(apk); - } - - private void startInstall(Apk apk) { - activeDownloadUrlString = apk.getUrl(); - registerDownloaderReceiver(); - InstallManagerService.queue(this, app, apk); - } - - /** - * Attempts to find the installed {@link Apk} from the database. If not found, will lookup the - * {@link InstalledAppProvider} to find the details of the installed app and use that to - * instantiate an {@link Apk} to be returned. - * - * Cases where an {@link Apk} will not be found in the database and for which we fall back to - * the {@link InstalledAppProvider} include: - * + System apps which are provided by a repository, but for which the version code bundled - * with the system is not included in the repository. - * + Regular apps from a repository, where the installed version is old enough that it is no - * longer available in the repository. - * - * @throws IllegalStateException If neither the {@link PackageManager} or the - * {@link InstalledAppProvider} can't find a reference to the installed apk. - */ - @NonNull - private Apk getInstalledApk() { - try { - PackageInfo pi = packageManager.getPackageInfo(app.packageName, 0); - - Apk apk = ApkProvider.Helper.findApkFromAnyRepo(this, pi.packageName, pi.versionCode); - if (apk == null) { - InstalledApp installedApp = InstalledAppProvider.Helper.findByPackageName(context, pi.packageName); - if (installedApp == null) { - throw new IllegalStateException("No installed app found when trying to uninstall"); - } - - apk = new Apk(installedApp); - } - return apk; - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - throw new IllegalStateException("Couldn't find app while installing"); - } - } - - /** - * Queue for uninstall based on the instance variable {@link #app}. - */ - private void uninstallApk() { - if (app.installedApk == null) { - // TODO ideally, app would be refreshed immediately after install, then this - // workaround would be unnecessary - app.installedApk = getInstalledApk(); - } - - Installer installer = InstallerFactory.create(this, app.installedApk); - Intent intent = installer.getUninstallScreen(); - if (intent != null) { - // uninstall screen required - Utils.debugLog(TAG, "screen screen required"); - startActivityForResult(intent, REQUEST_UNINSTALL_DIALOG); - return; - } - - startUninstall(); - } - - private void startUninstall() { - localBroadcastManager.registerReceiver(uninstallReceiver, - Installer.getUninstallIntentFilter(app.packageName)); - InstallerService.uninstall(context, app.installedApk); - } - - private void launchApk(String packageName) { - Intent intent = packageManager.getLaunchIntentForPackage(packageName); - startActivity(intent); - } - - private void shareApp(App app) { - Intent shareIntent = new Intent(Intent.ACTION_SEND); - shareIntent.setType("text/plain"); - - shareIntent.putExtra(Intent.EXTRA_SUBJECT, app.name); - shareIntent.putExtra(Intent.EXTRA_TEXT, app.name + " (" + app.summary + ") - https://f-droid.org/app/" + app.packageName); - - startActivity(Intent.createChooser(shareIntent, getString(R.string.menu_share))); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_ENABLE_BLUETOOTH: - fdroidApp.sendViaBluetooth(this, resultCode, app.packageName); - break; - case REQUEST_PERMISSION_DIALOG: - if (resultCode == Activity.RESULT_OK) { - Uri uri = data.getData(); - Apk apk = ApkProvider.Helper.findByUri(this, uri, Schema.ApkTable.Cols.ALL); - startInstall(apk); - } - break; - case REQUEST_UNINSTALL_DIALOG: - if (resultCode == Activity.RESULT_OK) { - startUninstall(); - } - break; - } - } - - private App getApp() { - return app; - } - - private ApkListAdapter getApks() { - return adapter; - } - - public static class AppDetailsSummaryFragment extends Fragment { - - final Preferences prefs; - private AppDetails appDetails; - private static final int MAX_LINES = 5; - private static boolean viewAllDescription; - private static LinearLayout llViewMoreDescription; - private static LinearLayout llViewMorePermissions; - private final View.OnClickListener expanderPermissions = new View.OnClickListener() { - @Override - public void onClick(View v) { - final View permissionListView = llViewMorePermissions.findViewById(R.id.permission_list); - final TextView permissionHeader = (TextView) llViewMorePermissions.findViewById(R.id.permissions); - - if (permissionListView.getVisibility() == View.GONE) { - permissionListView.setVisibility(View.VISIBLE); - permissionHeader.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(getActivity(), R.drawable.ic_lock_24dp_grey600), null, ContextCompat.getDrawable(getActivity(), R.drawable.ic_expand_less_grey600), null); - } else { - permissionListView.setVisibility(View.GONE); - permissionHeader.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(getActivity(), R.drawable.ic_lock_24dp_grey600), null, ContextCompat.getDrawable(getActivity(), R.drawable.ic_expand_more_grey600), null); - } - } - }; - private ViewGroup layoutLinks; - - public AppDetailsSummaryFragment() { - prefs = Preferences.get(); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - appDetails = (AppDetails) activity; - } - - @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()); - } - - // The HTML formatter adds "\n\n" at the end of every paragraph. This - // is desired between paragraphs, but not at the end of the whole - // string as it adds unwanted spacing at the end of the TextView. - // Remove all trailing newlines. - // Use this function instead of a trim() as that would require - // converting to String and thus losing formatting (e.g. bold). - private static CharSequence trimNewlines(CharSequence s) { - if (s == null || s.length() < 1) { - return s; - } - int i; - for (i = s.length() - 1; i >= 0; i--) { - if (s.charAt(i) != '\n') { - break; - } - } - if (i == s.length() - 1) { - return s; - } - return s.subSequence(0, i + 1); - } - - private ViewGroup layoutLinksContent; - private final View.OnClickListener expanderLinks = new View.OnClickListener() { - @Override - public void onClick(View v) { - - TextView linksHeader = (TextView) layoutLinks.findViewById(R.id.information); - - if (layoutLinksContent.getVisibility() == View.GONE) { - layoutLinksContent.setVisibility(View.VISIBLE); - linksHeader.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(getActivity(), R.drawable.ic_website), null, ContextCompat.getDrawable(getActivity(), R.drawable.ic_expand_less_grey600), null); - } else { - layoutLinksContent.setVisibility(View.GONE); - linksHeader.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(getActivity(), R.drawable.ic_website), null, ContextCompat.getDrawable(getActivity(), R.drawable.ic_expand_more_grey600), null); - } - } - }; - - private final View.OnClickListener onClickListener = new View.OnClickListener() { - public void onClick(View v) { - String url = null; - App app = appDetails.getApp(); - switch (v.getId()) { - case R.id.website: - url = app.webSite; - break; - case R.id.email: - final String subject = Uri.encode(getString(R.string.app_details_subject, app.name)); - url = "mailto:" + app.authorEmail + "?subject=" + subject; - break; - case R.id.source: - url = app.sourceCode; - break; - case R.id.issues: - url = app.issueTracker; - break; - case R.id.changelog: - url = app.changelog; - break; - case R.id.donate: - url = app.donate; - break; - case R.id.bitcoin: - url = app.getBitcoinUri(); - break; - case R.id.litecoin: - url = app.getLitecoinUri(); - break; - case R.id.flattr: - url = app.getFlattrUri(); - break; - } - if (url != null) { - ((AppDetails) getActivity()).tryOpenUri(url); - } - } - }; - - private final View.OnClickListener expanderDescription = new View.OnClickListener() { - public void onClick(View v) { - final TextView description = (TextView) llViewMoreDescription.findViewById(R.id.description); - final TextView viewMorePermissions = (TextView) llViewMoreDescription.findViewById(R.id.view_more_description); - if (viewAllDescription) { - description.setMaxLines(Integer.MAX_VALUE); - viewMorePermissions.setText(getString(R.string.less)); - } else { - description.setMaxLines(MAX_LINES); - if (Build.VERSION.SDK_INT > 10) { - // ellipsizing doesn't work properly here on 2.X - description.setEllipsize(TextUtils.TruncateAt.MARQUEE); - } - viewMorePermissions.setText(R.string.more); - } - viewAllDescription ^= true; - } - }; - - private void setupView(final View view) { - App app = appDetails.getApp(); - // Expandable description - final TextView description = (TextView) view.findViewById(R.id.description); - final Spanned desc = Html.fromHtml(app.description, null, new Utils.HtmlTagHandler()); - description.setMovementMethod(SafeLinkMovementMethod.getInstance(getActivity())); - description.setText(trimNewlines(desc)); - final View viewMoreDescription = view.findViewById(R.id.view_more_description); - description.post(new Runnable() { - @Override - public void run() { - // If description has more than five lines - if (description.getLineCount() > MAX_LINES) { - description.setMaxLines(MAX_LINES); - if (Build.VERSION.SDK_INT > 10) { - // ellipsizing doesn't work properly here on 2.X - description.setEllipsize(TextUtils.TruncateAt.MARQUEE); - } - description.setOnClickListener(expanderDescription); - viewAllDescription = true; - - llViewMoreDescription = (LinearLayout) view.findViewById(R.id.ll_description); - llViewMoreDescription.setOnClickListener(expanderDescription); - - viewMoreDescription.setOnClickListener(expanderDescription); - } else { - viewMoreDescription.setVisibility(View.GONE); - } - } - }); - - // App ID - final TextView packageNameView = (TextView) view.findViewById(R.id.package_name); - if (prefs.expertMode()) { - packageNameView.setText(app.packageName); - } else { - packageNameView.setVisibility(View.GONE); - } - - // Summary - final TextView summaryView = (TextView) view.findViewById(R.id.summary); - summaryView.setText(app.summary); - - layoutLinks = (ViewGroup) view.findViewById(R.id.ll_information); - layoutLinksContent = (ViewGroup) layoutLinks.findViewById(R.id.ll_information_content); - - final TextView linksHeader = (TextView) view.findViewById(R.id.information); - linksHeader.setOnClickListener(expanderLinks); - - // Website button - View tv = view.findViewById(R.id.website); - if (!TextUtils.isEmpty(app.webSite)) { - tv.setOnClickListener(onClickListener); - } else { - tv.setVisibility(View.GONE); - } - - // Email button - tv = view.findViewById(R.id.email); - if (!TextUtils.isEmpty(app.authorEmail)) { - tv.setOnClickListener(onClickListener); - } else { - tv.setVisibility(View.GONE); - } - - // Source button - tv = view.findViewById(R.id.source); - if (!TextUtils.isEmpty(app.sourceCode)) { - tv.setOnClickListener(onClickListener); - } else { - tv.setVisibility(View.GONE); - } - - // Issues button - tv = view.findViewById(R.id.issues); - if (!TextUtils.isEmpty(app.issueTracker)) { - tv.setOnClickListener(onClickListener); - } else { - tv.setVisibility(View.GONE); - } - - // Changelog button - tv = view.findViewById(R.id.changelog); - if (!TextUtils.isEmpty(app.changelog)) { - tv.setOnClickListener(onClickListener); - } else { - tv.setVisibility(View.GONE); - } - - // Donate button - tv = view.findViewById(R.id.donate); - if (!TextUtils.isEmpty(app.donate)) { - tv.setOnClickListener(onClickListener); - } else { - tv.setVisibility(View.GONE); - } - - // Bitcoin - tv = view.findViewById(R.id.bitcoin); - if (!TextUtils.isEmpty(app.bitcoin)) { - tv.setOnClickListener(onClickListener); - } else { - tv.setVisibility(View.GONE); - } - - // Litecoin - tv = view.findViewById(R.id.litecoin); - if (!TextUtils.isEmpty(app.litecoin)) { - tv.setOnClickListener(onClickListener); - } else { - tv.setVisibility(View.GONE); - } - - // Flattr - tv = view.findViewById(R.id.flattr); - if (!TextUtils.isEmpty(app.flattrID)) { - tv.setOnClickListener(onClickListener); - } else { - tv.setVisibility(View.GONE); - } - - Apk curApk = null; - for (int i = 0; i < appDetails.getApks().getCount(); i++) { - final Apk apk = appDetails.getApks().getItem(i); - if (apk.versionCode == app.suggestedVersionCode) { - curApk = apk; - break; - } - } - - // Expandable permissions - llViewMorePermissions = (LinearLayout) view.findViewById(R.id.ll_permissions); - final TextView permissionHeader = (TextView) view.findViewById(R.id.permissions); - - final boolean curApkCompatible = curApk != null && curApk.compatible; - if (!appDetails.getApks().isEmpty() && (curApkCompatible || prefs.showIncompatibleVersions())) { - // build and set the string once - buildPermissionInfo(); - permissionHeader.setOnClickListener(expanderPermissions); - - } else { - permissionHeader.setVisibility(View.GONE); - } - - // Anti features - final TextView antiFeaturesView = (TextView) view.findViewById(R.id.antifeatures); - if (app.antiFeatures != null) { - StringBuilder sb = new StringBuilder(); - for (String af : app.antiFeatures) { - String afdesc = descAntiFeature(af); - 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 void buildPermissionInfo() { - AppDiff appDiff = new AppDiff(appDetails.getPackageManager(), appDetails.getApks().getItem(0)); - AppSecurityPermissions perms = new AppSecurityPermissions(appDetails, appDiff.pkgInfo); - - final ViewGroup permList = (ViewGroup) llViewMorePermissions.findViewById(R.id.permission_list); - permList.addView(perms.getPermissionsView(AppSecurityPermissions.WHICH_ALL)); - } - - private String descAntiFeature(String af) { - switch (af) { - case "Ads": - return getString(R.string.antiadslist); - case "Tracking": - return getString(R.string.antitracklist); - case "NonFreeNet": - return getString(R.string.antinonfreenetlist); - case "NonFreeAdd": - return getString(R.string.antinonfreeadlist); - case "NonFreeDep": - return getString(R.string.antinonfreedeplist); - case "UpstreamNonFree": - return getString(R.string.antiupstreamnonfreelist); - case "NonFreeAssets": - return getString(R.string.antinonfreeassetslist); - default: - return af; - } - } - - public void updateViews(View view) { - if (view == null) { - Log.e(TAG, "AppDetailsSummaryFragment.updateViews(): view == null. Oops."); - return; - } - - App app = appDetails.getApp(); - TextView signatureView = (TextView) view.findViewById(R.id.signature); - if (prefs.expertMode() && !TextUtils.isEmpty(app.installedSig)) { - signatureView.setVisibility(View.VISIBLE); - signatureView.setText("Signed: " + app.installedSig); - } else { - signatureView.setVisibility(View.GONE); - } - } - } - - public static class AppDetailsHeaderFragment extends Fragment implements View.OnClickListener { - - private AppDetails appDetails; - private Button btMain; - private ProgressBar progressBar; - private TextView progressSize; - private TextView progressPercent; - private ImageButton cancelButton; - final DisplayImageOptions displayImageOptions; - public static boolean installed; - public static boolean updateWanted; - - public AppDetailsHeaderFragment() { - 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 View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.app_details_header, container, false); - setupView(view); - return view; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - appDetails = (AppDetails) activity; - } - - private void setupView(View view) { - App app = appDetails.getApp(); - - // Set the icon... - ImageView iv = (ImageView) view.findViewById(R.id.icon); - ImageLoader.getInstance().displayImage(app.iconUrlLarge, iv, - displayImageOptions); - - // Set the title - TextView tv = (TextView) view.findViewById(R.id.title); - tv.setText(app.name); - - btMain = (Button) view.findViewById(R.id.btn_main); - progressBar = (ProgressBar) view.findViewById(R.id.progress_bar); - progressSize = (TextView) view.findViewById(R.id.progress_size); - progressPercent = (TextView) view.findViewById(R.id.progress_percentage); - cancelButton = (ImageButton) view.findViewById(R.id.cancel); - progressBar.setIndeterminate(false); - cancelButton.setOnClickListener(this); - - updateViews(view); - } - - @Override - public void onResume() { - super.onResume(); - updateViews(); - restoreProgressBarOnResume(); - } - - /** - * After resuming the fragment, decide whether or not we need to show the progress bar. - * Also, put an appropriate message depending on whether or not the download is active or - * just queued. - * - * NOTE: this can't be done in the `updateViews` method as it currently stands. The reason - * is because that method gets called all the time, for all sorts of reasons. The progress - * bar is updated with actual progress values in response to async broadcasts. If we always - * tried to force the progress bar in `updateViews`, it would override the values that were - * set by the async progress broadcasts. - */ - private void restoreProgressBarOnResume() { - if (appDetails.activeDownloadUrlString != null) { - // We don't actually know what the current progress is, so this will show an indeterminate - // progress bar until the first progress/complete event we receive. - if (DownloaderService.isQueuedOrActive(appDetails.activeDownloadUrlString)) { - showIndeterminateProgress(getString(R.string.download_pending)); - } else { - showIndeterminateProgress(""); - } - } - } - - /** - * Displays empty, indeterminate progress bar and related views. - */ - public void startProgress() { - startProgress(true); - } - - public void startProgress(boolean allowCancel) { - cancelButton.setVisibility(allowCancel ? View.VISIBLE : View.GONE); - if (isAdded()) { - showIndeterminateProgress(getString(R.string.download_pending)); - updateViews(); - } - } - - private void showIndeterminateProgress(String message) { - setProgressVisible(true); - progressBar.setIndeterminate(true); - progressSize.setText(message); - progressPercent.setText(""); - } - - /** - * Updates progress bar and captions to new values (in bytes). - */ - public void updateProgress(long bytesDownloaded, long totalBytes) { - if (bytesDownloaded < 0 || totalBytes == 0) { - // Avoid division by zero and other weird values - return; - } - - if (totalBytes == -1) { - setProgressVisible(true); - progressBar.setIndeterminate(true); - progressSize.setText(Utils.getFriendlySize(bytesDownloaded)); - progressPercent.setText(""); - } else { - long percent = bytesDownloaded * 100 / totalBytes; - setProgressVisible(true); - progressBar.setIndeterminate(false); - progressBar.setProgress((int) percent); - progressBar.setMax(100); - progressSize.setText(Utils.getFriendlySize(bytesDownloaded) + " / " + Utils.getFriendlySize(totalBytes)); - progressPercent.setText(Long.toString(percent) + " %"); - } - } - - /** - * Shows or hides progress bar and related views. - */ - private void setProgressVisible(boolean visible) { - int state = visible ? View.VISIBLE : View.GONE; - progressBar.setVisibility(state); - progressSize.setVisibility(state); - progressPercent.setVisibility(state); - } - - /** - * Removes progress bar and related views, invokes {@link #updateViews()}. - */ - public void removeProgress() { - setProgressVisible(false); - cancelButton.setVisibility(View.GONE); - updateViews(); - } - - /** - * Cancels download and hides progress bar. - */ - @Override - public void onClick(View view) { - AppDetails appDetails = (AppDetails) getActivity(); - if (appDetails == null || appDetails.activeDownloadUrlString == null) { - return; - } - - InstallManagerService.cancel(getContext(), appDetails.activeDownloadUrlString); - } - - public void updateViews() { - updateViews(getView()); - } - - public void updateViews(View view) { - if (view == null) { - Log.e(TAG, "AppDetailsHeaderFragment.updateViews(): view == null. Oops."); - return; - } - App app = appDetails.getApp(); - TextView statusView = (TextView) view.findViewById(R.id.status); - btMain.setVisibility(View.VISIBLE); - - if (appDetails.activeDownloadUrlString != null) { - btMain.setText(R.string.downloading); - btMain.setEnabled(false); - } else if (!app.isInstalled() && app.suggestedVersionCode > 0 && - appDetails.adapter.getCount() > 0) { - // Check count > 0 due to incompatible apps resulting in an empty list. - // If App isn't installed - installed = false; - statusView.setText(R.string.details_notinstalled); - NfcHelper.disableAndroidBeam(appDetails); - // Set Install button and hide second button - btMain.setText(R.string.menu_install); - btMain.setOnClickListener(onClickListener); - btMain.setEnabled(true); - } else if (app.isInstalled()) { - // If App is installed - installed = true; - statusView.setText(getString(R.string.details_installed, app.installedVersionName)); - NfcHelper.setAndroidBeam(appDetails, app.packageName); - if (app.canAndWantToUpdate(appDetails)) { - updateWanted = true; - btMain.setText(R.string.menu_upgrade); - } else { - updateWanted = false; - if (appDetails.packageManager.getLaunchIntentForPackage(app.packageName) != null) { - btMain.setText(R.string.menu_launch); - } else { - btMain.setText(R.string.menu_uninstall); - } - } - btMain.setOnClickListener(onClickListener); - btMain.setEnabled(true); - } - TextView author = (TextView) view.findViewById(R.id.author); - if (!TextUtils.isEmpty(app.authorName)) { - author.setText(getString(R.string.by_author) + " " + app.authorName); - author.setVisibility(View.VISIBLE); - } - TextView currentVersion = (TextView) view.findViewById(R.id.current_version); - if (!appDetails.getApks().isEmpty()) { - currentVersion.setText(appDetails.getApks().getItem(0).versionName + " (" + app.license + ")"); - } else { - currentVersion.setVisibility(View.GONE); - btMain.setVisibility(View.GONE); - } - - } - - private final View.OnClickListener onClickListener = new View.OnClickListener() { - public void onClick(View v) { - App app = appDetails.getApp(); - AppDetails activity = (AppDetails) getActivity(); - if (updateWanted && app.suggestedVersionCode > 0) { - Apk apkToInstall = ApkProvider.Helper.findApkFromAnyRepo(activity, app.packageName, app.suggestedVersionCode); - activity.install(apkToInstall); - return; - } - if (installed) { - // If installed - if (activity.packageManager.getLaunchIntentForPackage(app.packageName) != null) { - // If "launchable", launch - activity.launchApk(app.packageName); - } else { - activity.uninstallApk(); - } - } else if (app.suggestedVersionCode > 0) { - // If not installed, install - btMain.setEnabled(false); - btMain.setText(R.string.system_install_installing); - final Apk apkToInstall = ApkProvider.Helper.findApkFromAnyRepo(activity, app.packageName, app.suggestedVersionCode); - activity.install(apkToInstall); - } - } - }; - } - - public static class AppDetailsListFragment extends ListFragment { - - private static final String SUMMARY_TAG = "summary"; - - private AppDetails appDetails; - private AppDetailsSummaryFragment summaryFragment; - - private FrameLayout headerView; - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - appDetails = (AppDetails) activity; - } - - @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()); - 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(appDetails.getApks()); - } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) { - App app = appDetails.getApp(); - final Apk apk = appDetails.getApks().getItem(position - l.getHeaderViewsCount()); - if (app.installedVersionCode == apk.versionCode) { - appDetails.uninstallApk(); - } else if (app.installedVersionCode > apk.versionCode) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setMessage(R.string.installDowngrade); - builder.setPositiveButton(R.string.yes, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, - int whichButton) { - appDetails.install(apk); - } - }); - builder.setNegativeButton(R.string.no, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, - int whichButton) { - } - }); - AlertDialog alert = builder.create(); - alert.show(); - } else { - appDetails.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); - } - } - -} diff --git a/app/src/main/java/org/fdroid/fdroid/AppDetails2.java b/app/src/main/java/org/fdroid/fdroid/AppDetails2.java index 4917be1c1..cf242b497 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppDetails2.java +++ b/app/src/main/java/org/fdroid/fdroid/AppDetails2.java @@ -1,3 +1,24 @@ +/* + * Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com + * Copyright (C) 2013-15 Daniel Martí + * Copyright (C) 2013 Stefan Völkel, bd@bc-bd.org + * Copyright (C) 2015 Nico Alt, nicoalt@posteo.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + package org.fdroid.fdroid; import android.app.Activity; diff --git a/app/src/main/java/org/fdroid/fdroid/FDroid.java b/app/src/main/java/org/fdroid/fdroid/FDroid.java deleted file mode 100644 index 5aff91827..000000000 --- a/app/src/main/java/org/fdroid/fdroid/FDroid.java +++ /dev/null @@ -1,456 +0,0 @@ -/* - * Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com - * Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 3 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package org.fdroid.fdroid; - -import android.app.NotificationManager; -import android.app.SearchManager; -import android.bluetooth.BluetoothAdapter; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.Configuration; -import android.database.ContentObserver; -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.view.MenuItemCompat; -import android.support.v4.view.ViewPager; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.SearchView; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; -import android.widget.Toast; - -import org.fdroid.fdroid.compat.TabManager; -import org.fdroid.fdroid.compat.UriCompat; -import org.fdroid.fdroid.data.AppProvider; -import org.fdroid.fdroid.data.NewRepoConfig; -import org.fdroid.fdroid.views.AppListFragmentPagerAdapter; -import org.fdroid.fdroid.views.ManageReposActivity; -import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; - -public class FDroid extends AppCompatActivity implements SearchView.OnQueryTextListener { - - private static final String TAG = "FDroid"; - - private static final int REQUEST_PREFS = 1; - private static final int REQUEST_ENABLE_BLUETOOTH = 2; - private static final int REQUEST_SWAP = 3; - - public static final String EXTRA_TAB_UPDATE = "extraTab"; - - private static final String ACTION_ADD_REPO = "org.fdroid.fdroid.FDroid.ACTION_ADD_REPO"; - - private static final String ADD_REPO_INTENT_HANDLED = "addRepoIntentHandled"; - - private FDroidApp fdroidApp; - - private SearchView searchView; - - private ViewPager viewPager; - - @Nullable - private TabManager tabManager; - - private AppListFragmentPagerAdapter adapter; - - @Nullable - private MenuItem searchMenuItem; - - @Nullable - private String pendingSearchQuery; - - @Override - protected void onCreate(Bundle savedInstanceState) { - - fdroidApp = (FDroidApp) getApplication(); - fdroidApp.applyTheme(this); - - super.onCreate(savedInstanceState); - setContentView(R.layout.fdroid); - createViews(); - - getTabManager().createTabs(); - - // Start a search by just typing - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); - - Intent intent = getIntent(); - handleSearchOrAppViewIntent(intent); - - if (intent.hasExtra(EXTRA_TAB_UPDATE)) { - boolean showUpdateTab = intent.getBooleanExtra(EXTRA_TAB_UPDATE, false); - if (showUpdateTab) { - getTabManager().selectTab(2); - } - } - - Uri uri = AppProvider.getContentUri(); - getContentResolver().registerContentObserver(uri, true, new AppObserver()); - - // Re-enable once it can be disabled via a setting - // See https://gitlab.com/fdroid/fdroidclient/issues/435 - // - // if (UpdateService.isNetworkAvailableForUpdate(this)) { - // UpdateService.updateNow(this); - // } - } - - private void performSearch(String query) { - if (searchMenuItem == null) { - // Store this for later when we do actually have a search menu ready to use. - pendingSearchQuery = query; - return; - } - - SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchMenuItem); - MenuItemCompat.expandActionView(searchMenuItem); - searchView.setQuery(query, true); - } - - @Override - protected void onResume() { - super.onResume(); - FDroidApp.checkStartTor(this); - // AppDetails and RepoDetailsActivity set different NFC actions, so reset here - NfcHelper.setAndroidBeam(this, getApplication().getPackageName()); - checkForAddRepoIntent(getIntent()); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - handleSearchOrAppViewIntent(intent); - - // This is called here as well as onResume(), because onNewIntent() is not called the first - // time the activity is created. An alternative option to make sure that the add repo intent - // is always handled is to call setIntent(intent) here. However, after this good read: - // http://stackoverflow.com/a/7749347 it seems that adding a repo is not really more - // important than the original intent which caused the activity to start (even though it - // could technically have been an add repo intent itself). - // The end result is that this method will be called twice for one add repo intent. Once - // here and once in onResume(). However, the method deals with this by ensuring it only - // handles the same intent once. - checkForAddRepoIntent(intent); - } - - private void handleSearchOrAppViewIntent(Intent intent) { - if (Intent.ACTION_SEARCH.equals(intent.getAction())) { - String query = intent.getStringExtra(SearchManager.QUERY); - performSearch(query); - return; - } - - final Uri data = intent.getData(); - if (data == null) { - return; - } - - final String scheme = data.getScheme(); - final String path = data.getPath(); - String packageName = null; - String query = null; - if (data.isHierarchical()) { - final String host = data.getHost(); - if (host == null) { - return; - } - switch (host) { - case "f-droid.org": - if (path.startsWith("/repository/browse")) { - // http://f-droid.org/repository/browse?fdfilter=search+query - query = UriCompat.getQueryParameter(data, "fdfilter"); - - // http://f-droid.org/repository/browse?fdid=packageName - packageName = UriCompat.getQueryParameter(data, "fdid"); - } else if (path.startsWith("/app")) { - // http://f-droid.org/app/packageName - packageName = data.getLastPathSegment(); - if ("app".equals(packageName)) { - packageName = null; - } - } - break; - case "details": - // market://details?id=app.id - packageName = UriCompat.getQueryParameter(data, "id"); - break; - case "search": - // market://search?q=query - query = UriCompat.getQueryParameter(data, "q"); - break; - case "play.google.com": - if (path.startsWith("/store/apps/details")) { - // http://play.google.com/store/apps/details?id=app.id - packageName = UriCompat.getQueryParameter(data, "id"); - } else if (path.startsWith("/store/search")) { - // http://play.google.com/store/search?q=foo - query = UriCompat.getQueryParameter(data, "q"); - } - break; - case "apps": - case "amazon.com": - case "www.amazon.com": - // amzn://apps/android?p=app.id - // http://amazon.com/gp/mas/dl/android?s=app.id - packageName = UriCompat.getQueryParameter(data, "p"); - query = UriCompat.getQueryParameter(data, "s"); - break; - } - } else if ("fdroid.app".equals(scheme)) { - // fdroid.app:app.id - packageName = data.getSchemeSpecificPart(); - } else if ("fdroid.search".equals(scheme)) { - // fdroid.search:query - query = data.getSchemeSpecificPart(); - } - - if (!TextUtils.isEmpty(query)) { - // an old format for querying via packageName - if (query.startsWith("pname:")) { - packageName = query.split(":")[1]; - } - - // sometimes, search URLs include pub: or other things before the query string - if (query.contains(":")) { - query = query.split(":")[1]; - } - } - - if (!TextUtils.isEmpty(packageName)) { - Utils.debugLog(TAG, "FDroid launched via app link for '" + packageName + "'"); - Intent intentToInvoke = new Intent(this, AppDetails2.class); - intentToInvoke.putExtra(AppDetails2.EXTRA_APPID, packageName); - startActivity(intentToInvoke); - finish(); - } else if (!TextUtils.isEmpty(query)) { - Utils.debugLog(TAG, "FDroid launched via search link for '" + query + "'"); - performSearch(query); - } - } - - private void checkForAddRepoIntent(Intent intent) { - // Don't handle the intent after coming back to this view (e.g. after hitting the back button) - // http://stackoverflow.com/a/14820849 - if (!intent.hasExtra(ADD_REPO_INTENT_HANDLED)) { - intent.putExtra(ADD_REPO_INTENT_HANDLED, true); - NewRepoConfig parser = new NewRepoConfig(this, intent); - if (parser.isValidRepo()) { - if (parser.isFromSwap()) { - Intent confirmIntent = new Intent(this, SwapWorkflowActivity.class); - confirmIntent.putExtra(SwapWorkflowActivity.EXTRA_CONFIRM, true); - confirmIntent.setData(intent.getData()); - startActivityForResult(confirmIntent, REQUEST_SWAP); - } else { - startActivity(new Intent(ACTION_ADD_REPO, intent.getData(), this, ManageReposActivity.class)); - } - } else if (parser.getErrorMessage() != null) { - Toast.makeText(this, parser.getErrorMessage(), Toast.LENGTH_LONG).show(); - } - } - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - getTabManager().onConfigurationChanged(newConfig); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.main, menu); - if (fdroidApp.bluetoothAdapter == null) { - // ignore on devices without Bluetooth - MenuItem btItem = menu.findItem(R.id.action_bluetooth_apk); - btItem.setVisible(false); - } - - SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); - searchMenuItem = menu.findItem(R.id.action_search); - searchView = (SearchView) MenuItemCompat.getActionView(searchMenuItem); - searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); - // LayoutParams.MATCH_PARENT does not work, use a big value instead - searchView.setMaxWidth(1000000); - searchView.setOnQueryTextListener(this); - - // If we were asked to execute a search before getting around to building the options - // menu, then we should deal with that now that the options menu is all sorted out. - if (pendingSearchQuery != null) { - performSearch(pendingSearchQuery); - pendingSearchQuery = null; - } - - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - - case R.id.action_update_repo: - UpdateService.updateNow(this); - return true; - - case R.id.action_manage_repos: - startActivity(new Intent(this, ManageReposActivity.class)); - return true; - - case R.id.action_settings: - Intent prefs = new Intent(getBaseContext(), PreferencesActivity.class); - startActivityForResult(prefs, REQUEST_PREFS); - return true; - - case R.id.action_swap: - startActivity(new Intent(this, SwapWorkflowActivity.class)); - return true; - - case R.id.action_bluetooth_apk: - /* - * If Bluetooth has not been enabled/turned on, then enabling - * device discoverability will automatically enable Bluetooth - */ - Intent discoverBt = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); - discoverBt.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 121); - startActivityForResult(discoverBt, REQUEST_ENABLE_BLUETOOTH); - // if this is successful, the Bluetooth transfer is started - return true; - - case R.id.action_about: - View view = LayoutInflater.from(this).inflate(R.layout.about, null); - - String versionName = Utils.getVersionName(this); - if (versionName != null) { - ((TextView) view.findViewById(R.id.version)).setText(versionName); - } - - AlertDialog alrt = new AlertDialog.Builder(this).setView(view).create(); - alrt.setTitle(R.string.about_title); - alrt.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.ok), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int whichButton) { - } - }); - alrt.show(); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - - switch (requestCode) { - case REQUEST_PREFS: - // The automatic update settings may have changed, so reschedule (or - // unschedule) the service accordingly. It's cheap, so no need to - // check if the particular setting has actually been changed. - UpdateService.schedule(getBaseContext()); - - if ((resultCode & PreferencesActivity.RESULT_RESTART) != 0) { - ((FDroidApp) getApplication()).reloadTheme(); - final Intent intent = getIntent(); - overridePendingTransition(0, 0); - intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - finish(); - overridePendingTransition(0, 0); - startActivity(intent); - } - break; - case REQUEST_ENABLE_BLUETOOTH: - fdroidApp.sendViaBluetooth(this, resultCode, "org.fdroid.fdroid"); - break; - } - } - - private void createViews() { - viewPager = (ViewPager) findViewById(R.id.main_pager); - adapter = new AppListFragmentPagerAdapter(this); - viewPager.setAdapter(adapter); - viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { - @Override - public void onPageSelected(int position) { - getTabManager().selectTab(position); - } - }); - } - - @NonNull - private TabManager getTabManager() { - if (tabManager == null) { - tabManager = new TabManager(this, viewPager); - } - return tabManager; - } - - private void refreshUpdateTabLabel() { - getTabManager().refreshTabLabel(TabManager.INDEX_CAN_UPDATE); - getTabManager().refreshTabLabel(TabManager.INDEX_INSTALLED); - } - - public void removeNotification(int id) { - NotificationManager nMgr = (NotificationManager) getBaseContext() - .getSystemService(Context.NOTIFICATION_SERVICE); - nMgr.cancel(id); - } - - @Override - public boolean onQueryTextSubmit(String query) { - searchView.clearFocus(); - return true; - } - - @Override - public boolean onQueryTextChange(String newText) { - adapter.updateSearchQuery(newText); - return true; - } - - private class AppObserver extends ContentObserver { - - AppObserver() { - super(null); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - FDroid.this.runOnUiThread(new Runnable() { - @Override - public void run() { - refreshUpdateTabLabel(); - } - }); - } - - @Override - public void onChange(boolean selfChange) { - onChange(selfChange, null); - } - - } - -} diff --git a/app/src/main/java/org/fdroid/fdroid/Preferences.java b/app/src/main/java/org/fdroid/fdroid/Preferences.java index 79ac07f55..0da1f339c 100644 --- a/app/src/main/java/org/fdroid/fdroid/Preferences.java +++ b/app/src/main/java/org/fdroid/fdroid/Preferences.java @@ -312,7 +312,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh } /** - * This is cached as it is called several times inside the AppListAdapter. + * This is cached as it is called several times inside app list adapters. * Providing it here means the shared preferences file only needs to be * read once, and we will keep our copy up to date by listening to changes * in PREF_ROOTED. @@ -326,7 +326,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh } /** - * This is cached as it is called several times inside the AppListAdapter. + * This is cached as it is called several times inside app list adapters. * Providing it here means the shared preferences file only needs to be * read once, and we will keep our copy up to date by listening to changes * in PREF_HIDE_ANTI_FEATURE_APPS. diff --git a/app/src/main/java/org/fdroid/fdroid/PreferencesActivity.java b/app/src/main/java/org/fdroid/fdroid/PreferencesActivity.java deleted file mode 100644 index 82a1559c1..000000000 --- a/app/src/main/java/org/fdroid/fdroid/PreferencesActivity.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 3 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package org.fdroid.fdroid; - -import android.os.Build; -import android.os.Bundle; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.NavUtils; -import android.support.v7.app.AppCompatActivity; -import android.view.MenuItem; -import android.widget.LinearLayout; - -import org.fdroid.fdroid.views.fragments.PreferencesFragment; - -public class PreferencesActivity extends AppCompatActivity { - - public static final int RESULT_RESTART = 4; - - @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 - if (Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT <= 13) { - setContentView(new LinearLayout(this)); - } - - PreferencesFragment preferencesFragment = new PreferencesFragment(); - fm.beginTransaction() - .add(android.R.id.content, preferencesFragment) - .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. - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - } - - @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/app/src/main/java/org/fdroid/fdroid/compat/ArrayAdapterCompat.java b/app/src/main/java/org/fdroid/fdroid/compat/ArrayAdapterCompat.java deleted file mode 100644 index befe7974e..000000000 --- a/app/src/main/java/org/fdroid/fdroid/compat/ArrayAdapterCompat.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.fdroid.fdroid.compat; - -import android.annotation.TargetApi; -import android.os.Build; -import android.widget.ArrayAdapter; - -import java.util.List; - -public class ArrayAdapterCompat { - - @TargetApi(11) - public static void addAll(ArrayAdapter adapter, List list) { - if (Build.VERSION.SDK_INT >= 11) { - adapter.addAll(list); - } else { - for (T category : list) { - adapter.add(category); - } - } - } - -} diff --git a/app/src/main/java/org/fdroid/fdroid/compat/TabManager.java b/app/src/main/java/org/fdroid/fdroid/compat/TabManager.java deleted file mode 100644 index dc467c951..000000000 --- a/app/src/main/java/org/fdroid/fdroid/compat/TabManager.java +++ /dev/null @@ -1,154 +0,0 @@ -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; - -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 final int INDEX_COUNT = 3; - - private final ViewPager pager; - private final FDroid parent; - private final ActionBar actionBar; - private Spinner actionBarSpinner; - - // 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; - } - - private CharSequence getLabel(int index) { - return pager.getAdapter().getPageTitle(index); - } - - private void removeNotification(int id) { - parent.removeNotification(id); - } - - public void createTabs() { - actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); - for (int i = 0; i < pager.getAdapter().getCount(); i++) { - CharSequence label = pager.getAdapter().getPageTitle(i); - actionBar.addTab( - actionBar.newTab() - .setText(label) - .setTabListener(new ActionBar.TabListener() { - @Override - public void onTabSelected(ActionBar.Tab tab, - FragmentTransaction ft) { - int pos = tab.getPosition(); - pager.setCurrentItem(pos); - if (pos == INDEX_CAN_UPDATE) { - removeNotification(1); - } - } - - @Override - public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { - } - - @Override - public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { - } - }) - ); - } - } - - public void selectTab(int index) { - actionBar.setSelectedNavigationItem(index); - Spinner actionBarSpinner = getActionBarSpinner(); - if (actionBarSpinner != null) { - actionBarSpinner.setSelection(index); - } - if (index == INDEX_CAN_UPDATE) { - removeNotification(1); - } - } - - public void refreshTabLabel(int index) { - CharSequence text = getLabel(index); - actionBar.getTabAt(index).setText(text); - } - - public void onConfigurationChanged(Configuration newConfig) { - dirtyConfig = true; - } - - /** - * Traversing the view hierarchy is a non-trivial task, and takes between 0 and 3 - * milliseconds on my SGS i9000 (Android 4.2). - * As such, we lazily try to identify the spinner, and only search once per - * orientation change. Once we've found it, we stop looking. - */ - private Spinner getActionBarSpinner() { - if (actionBarSpinner == null && dirtyConfig) { - dirtyConfig = false; - actionBarSpinner = findActionBarSpinner(); - } - return actionBarSpinner; - } - - /** - * Dodgey hack to fix issue 231, based on the solution at - * http://stackoverflow.com/a/13353493 - * Turns out that there is a bug in Android where the Spinner in the action - * bar (which represents the tabs if there is not enough space) is not - * updated when we call setSelectedNavigationItem(), and they don't expose - * the spinner via the API. So we go on a merry hunt for all spinners in - * our view, and find the first one with an id of -1. - * - * This is because the view hierarchy dictates that the action bar comes - * before everything below it when traversing children, and also our spinner - * on the first view (for the app categories) has an id, whereas the - * actionbar one doesn't. If that changes in future releases of android, - * then we will need to update the findListNavigationSpinner() method. - */ - private Spinner findActionBarSpinner() { - View rootView = parent.findViewById(android.R.id.content).getRootView(); - List spinners = traverseViewChildren((ViewGroup) rootView); - return findListNavigationSpinner(spinners); - } - - private Spinner findListNavigationSpinner(List spinners) { - Spinner spinner = null; - if (spinners.size() > 0) { - Spinner first = spinners.get(0); - if (first.getId() == -1) { - spinner = first; - } - } - return spinner; - } - - private List traverseViewChildren(ViewGroup parent) { - List spinners = new ArrayList<>(); - for (int i = 0; i < parent.getChildCount(); i++) { - View child = parent.getChildAt(i); - if (child instanceof Spinner) { - spinners.add((Spinner) child); - } else if (child instanceof ViewGroup) { - spinners.addAll(traverseViewChildren((ViewGroup) child)); - } - } - return spinners; - } -} diff --git a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java index df1474543..d79405c79 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java @@ -53,20 +53,6 @@ public class AppProvider extends FDroidProvider { private Helper() { } - public static int count(Context context, Uri uri) { - final String[] projection = {Cols._COUNT}; - Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null); - int count = 0; - if (cursor != null) { - if (cursor.getCount() == 1) { - cursor.moveToFirst(); - count = cursor.getInt(0); - } - cursor.close(); - } - return count; - } - public static List all(ContentResolver resolver) { return all(resolver, Cols.ALL); } @@ -367,8 +353,6 @@ public class AppProvider extends FDroidProvider { private static final String PATH_INSTALLED = "installed"; private static final String PATH_CAN_UPDATE = "canUpdate"; private static final String PATH_SEARCH = "search"; - private static final String PATH_SEARCH_INSTALLED = "searchInstalled"; - private static final String PATH_SEARCH_CAN_UPDATE = "searchCanUpdate"; private static final String PATH_SEARCH_REPO = "searchRepo"; protected static final String PATH_APPS = "apps"; protected static final String PATH_SPECIFIC_APP = "app"; @@ -389,9 +373,7 @@ public class AppProvider extends FDroidProvider { private static final int CALC_SUGGESTED_APKS = CATEGORY + 1; private static final int REPO = CALC_SUGGESTED_APKS + 1; private static final int SEARCH_REPO = REPO + 1; - private static final int SEARCH_INSTALLED = SEARCH_REPO + 1; - private static final int SEARCH_CAN_UPDATE = SEARCH_INSTALLED + 1; - private static final int HIGHEST_PRIORITY = SEARCH_CAN_UPDATE + 1; + private static final int HIGHEST_PRIORITY = SEARCH_REPO + 1; private static final int CALC_PREFERRED_METADATA = HIGHEST_PRIORITY + 1; private static final int TOP_FROM_CATEGORY = CALC_PREFERRED_METADATA + 1; @@ -402,8 +384,6 @@ public class AppProvider extends FDroidProvider { MATCHER.addURI(getAuthority(), PATH_CATEGORY + "/*", CATEGORY); MATCHER.addURI(getAuthority(), PATH_SEARCH + "/*/*", SEARCH_TEXT_AND_CATEGORIES); MATCHER.addURI(getAuthority(), PATH_SEARCH + "/*", SEARCH_TEXT); - MATCHER.addURI(getAuthority(), PATH_SEARCH_INSTALLED + "/*", SEARCH_INSTALLED); - MATCHER.addURI(getAuthority(), PATH_SEARCH_CAN_UPDATE + "/*", SEARCH_CAN_UPDATE); MATCHER.addURI(getAuthority(), PATH_SEARCH_REPO + "/*/*", SEARCH_REPO); MATCHER.addURI(getAuthority(), PATH_REPO + "/#", REPO); MATCHER.addURI(getAuthority(), PATH_CAN_UPDATE, CAN_UPDATE); @@ -456,10 +436,6 @@ public class AppProvider extends FDroidProvider { .build(); } - public static Uri getContentUri(App app) { - return getContentUri(app.packageName); - } - /** * @see AppProvider.Helper#findSpecificApp(ContentResolver, String, long, String[]) for details * of why you should usually prefer {@link AppProvider#getHighestPriorityMetadataUri(String)} to @@ -481,10 +457,6 @@ public class AppProvider extends FDroidProvider { .build(); } - public static Uri getContentUri(String packageName) { - return Uri.withAppendedPath(getContentUri(), packageName); - } - public static Uri getSearchUri(String query, @Nullable String category) { if (TextUtils.isEmpty(query) && TextUtils.isEmpty(category)) { // Return all the things for an empty search. @@ -504,22 +476,6 @@ public class AppProvider extends FDroidProvider { return builder.build(); } - public static Uri getSearchInstalledUri(String query) { - return getContentUri() - .buildUpon() - .appendPath(PATH_SEARCH_INSTALLED) - .appendPath(query) - .build(); - } - - public static Uri getSearchCanUpdateUri(String query) { - return getContentUri() - .buildUpon() - .appendPath(PATH_SEARCH_CAN_UPDATE) - .appendPath(query) - .build(); - } - public static Uri getSearchUri(Repo repo, String query) { return getContentUri().buildUpon() .appendPath(PATH_SEARCH_REPO) @@ -761,16 +717,6 @@ public class AppProvider extends FDroidProvider { includeSwap = false; break; - case SEARCH_INSTALLED: - selection = querySearch(uri.getLastPathSegment()).add(queryInstalled()); - includeSwap = false; - break; - - case SEARCH_CAN_UPDATE: - selection = querySearch(uri.getLastPathSegment()).add(queryCanUpdate()); - includeSwap = false; - break; - case SEARCH_REPO: selection = selection .add(querySearch(pathSegments.get(2))) diff --git a/app/src/main/java/org/fdroid/fdroid/data/CategoryProvider.java b/app/src/main/java/org/fdroid/fdroid/data/CategoryProvider.java index 59836d8b2..c68440abf 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/CategoryProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/CategoryProvider.java @@ -1,6 +1,5 @@ package org.fdroid.fdroid.data; -import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; @@ -8,17 +7,12 @@ import android.database.Cursor; import android.net.Uri; import android.support.annotation.NonNull; -import org.fdroid.fdroid.R; import org.fdroid.fdroid.data.Schema.CatJoinTable; import org.fdroid.fdroid.data.Schema.CategoryTable; import org.fdroid.fdroid.data.Schema.AppMetadataTable; import org.fdroid.fdroid.data.Schema.PackageTable; import org.fdroid.fdroid.data.Schema.CategoryTable.Cols; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - public class CategoryProvider extends FDroidProvider { public static final class Helper { @@ -53,47 +47,6 @@ public class CategoryProvider extends FDroidProvider { cursor.close(); } } - - public static String getCategoryAll(Context context) { - return context.getString(R.string.category_All); - } - - public static String getCategoryWhatsNew(Context context) { - return context.getString(R.string.category_Whats_New); - } - - public static String getCategoryRecentlyUpdated(Context context) { - return context.getString(R.string.category_Recently_Updated); - } - - public static List categories(Context context) { - final ContentResolver resolver = context.getContentResolver(); - final Uri uri = CategoryProvider.getAllCategories(); - final String[] projection = {Cols.NAME}; - final Cursor cursor = resolver.query(uri, projection, null, null, null); - List categories = new ArrayList<>(30); - if (cursor != null) { - if (cursor.getCount() > 0) { - cursor.moveToFirst(); - while (!cursor.isAfterLast()) { - final String name = cursor.getString(0); - categories.add(name); - cursor.moveToNext(); - } - } - cursor.close(); - } - Collections.sort(categories); - - // Populate the category list with the real categories, and the - // locally generated meta-categories for "What's New", "Recently - // Updated" and "All"... - categories.add(0, getCategoryAll(context)); - categories.add(0, getCategoryRecentlyUpdated(context)); - categories.add(0, getCategoryWhatsNew(context)); - - return categories; - } } private class Query extends QueryBuilder { diff --git a/app/src/main/java/org/fdroid/fdroid/privileged/views/AppSecurityPermissions.java b/app/src/main/java/org/fdroid/fdroid/privileged/views/AppSecurityPermissions.java index 74176e9f4..06d703b63 100644 --- a/app/src/main/java/org/fdroid/fdroid/privileged/views/AppSecurityPermissions.java +++ b/app/src/main/java/org/fdroid/fdroid/privileged/views/AppSecurityPermissions.java @@ -415,7 +415,7 @@ public class AppSecurityPermissions { || ((pInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_PRE23) != 0); // Dangerous and normal permissions are always shown to the user - // this is matches the permission list in AppDetails + // this is matches the permission list in AppDetails2 if (isNormal || isDangerous) { return true; } diff --git a/app/src/main/java/org/fdroid/fdroid/views/AppListAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/AppListAdapter.java deleted file mode 100644 index 98cfac28c..000000000 --- a/app/src/main/java/org/fdroid/fdroid/views/AppListAdapter.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.fdroid.fdroid.views; - -import android.content.Context; -import android.database.Cursor; -import android.support.v4.widget.CursorAdapter; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import com.nostra13.universalimageloader.core.DisplayImageOptions; -import com.nostra13.universalimageloader.core.ImageLoader; - -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.data.App; - -public abstract class AppListAdapter extends CursorAdapter { - - private LayoutInflater inflater; - private DisplayImageOptions displayImageOptions; - private String upgradeFromTo; - - @SuppressWarnings("deprecation") - public AppListAdapter(Context context, Cursor c) { - super(context, c); - init(context); - } - - @Override - public boolean isEmpty() { - return mDataValid && super.isEmpty(); - } - - public AppListAdapter(Context context, Cursor c, boolean autoRequery) { - super(context, c, autoRequery); - init(context); - } - - public AppListAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - init(context); - } - - private void init(Context context) { - inflater = (LayoutInflater) context.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - displayImageOptions = Utils.getImageLoadingOptions().build(); - upgradeFromTo = context.getResources().getString(R.string.upgrade_from_to); - } - - protected abstract boolean showStatusUpdate(); - - protected abstract boolean showStatusInstalled(); - - private static class ViewHolder { - TextView name; - TextView summary; - TextView status; - TextView license; - ImageView icon; - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - View view = inflater.inflate(R.layout.applistitem, parent, false); - - ViewHolder holder = new ViewHolder(); - holder.name = (TextView) view.findViewById(R.id.name); - holder.summary = (TextView) view.findViewById(R.id.summary); - holder.status = (TextView) view.findViewById(R.id.status); - holder.license = (TextView) view.findViewById(R.id.license); - holder.icon = (ImageView) view.findViewById(R.id.icon); - view.setTag(holder); - - setupView(view, cursor, holder); - - return view; - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - ViewHolder holder = (ViewHolder) view.getTag(); - setupView(view, cursor, holder); - } - - private void setupView(View view, Cursor cursor, ViewHolder holder) { - final App app = new App(cursor); - - holder.name.setText(app.name); - holder.summary.setText(app.summary); - - ImageLoader.getInstance().displayImage(app.iconUrl, holder.icon, - displayImageOptions); - - holder.status.setText(getVersionInfo(app)); - holder.license.setText(app.license); - - // Disable it all if it isn't compatible... - final View[] views = { - view, - holder.status, - holder.summary, - holder.license, - holder.name, - }; - - for (View v : views) { - v.setEnabled(app.compatible && !app.isFiltered()); - } - } - - private String getVersionInfo(App app) { - - if (app.suggestedVersionCode <= 0) { - return null; - } - - if (!app.isInstalled()) { - return app.getSuggestedVersionName(); - } - - final String installedVersionString = app.installedVersionName; - - if (app.canAndWantToUpdate(mContext) && showStatusUpdate()) { - return String.format(upgradeFromTo, - installedVersionString, app.getSuggestedVersionName()); - } - - if (app.installedVersionCode > 0 && showStatusInstalled()) { - return installedVersionString + " ✔"; - } - - return installedVersionString; - } -} diff --git a/app/src/main/java/org/fdroid/fdroid/views/AppListFragmentPagerAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/AppListFragmentPagerAdapter.java deleted file mode 100644 index 59d1d4ec4..000000000 --- a/app/src/main/java/org/fdroid/fdroid/views/AppListFragmentPagerAdapter.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.fdroid.fdroid.views; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentPagerAdapter; -import android.view.ViewGroup; - -import org.fdroid.fdroid.FDroid; -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.compat.TabManager; -import org.fdroid.fdroid.data.AppProvider; -import org.fdroid.fdroid.views.fragments.AppListFragment; -import org.fdroid.fdroid.views.fragments.AvailableAppsFragment; -import org.fdroid.fdroid.views.fragments.CanUpdateAppsFragment; -import org.fdroid.fdroid.views.fragments.InstalledAppsFragment; - -/** - * Used by the FDroid activity in conjunction with its ViewPager to support - * swiping of tabs for both old devices (< 3.0) and new devices. - * - * See http://stackoverflow.com/a/15261142 for how to obtain references - * to fragments in order to update them in response to search queries. - */ -public class AppListFragmentPagerAdapter extends FragmentPagerAdapter { - - @NonNull private final FDroid parent; - @Nullable private String searchQuery; - - private final AppListFragment[] registeredFragments = new AppListFragment[TabManager.INDEX_COUNT]; - - public AppListFragmentPagerAdapter(@NonNull FDroid parent) { - super(parent.getSupportFragmentManager()); - this.parent = parent; - } - - @Override - public Object instantiateItem(ViewGroup container, int position) { - AppListFragment fragment = (AppListFragment) super.instantiateItem(container, position); - fragment.updateSearchQuery(searchQuery); - registeredFragments[position] = fragment; - return fragment; - } - - @Override - public void destroyItem(ViewGroup container, int position, Object object) { - registeredFragments[position] = null; - super.destroyItem(container, position, object); - } - - private String getInstalledTabTitle() { - int installedCount = AppProvider.Helper.count(parent, AppProvider.getInstalledUri()); - return parent.getString(R.string.tab_installed_apps_count, installedCount); - } - - private String getUpdateTabTitle() { - int updateCount = AppProvider.Helper.count(parent, AppProvider.getCanUpdateUri()); - return parent.getString(R.string.tab_updates_count, updateCount); - } - - /** - * Changing the search query is quite an expensive operation, so this does some rudimentary - * checking to see if the two queries are meaningfully different. At present, it trims the - * strings and does a case insensitive comparison. - */ - private boolean isSearchQuerySame(String newQuery) { - String oldValueTrimmed = searchQuery == null ? "" : searchQuery.trim(); - String newValueTrimmed = newQuery == null ? "" : newQuery.trim(); - return oldValueTrimmed.equalsIgnoreCase(newValueTrimmed); - } - - public void updateSearchQuery(@Nullable String query) { - if (isSearchQuerySame(query)) { - return; - } - - searchQuery = query; - for (AppListFragment fragment : registeredFragments) { - if (fragment != null) { - fragment.updateSearchQuery(query); - } - } - } - - @Override - public Fragment getItem(int i) { - switch (i) { - case TabManager.INDEX_AVAILABLE: - return new AvailableAppsFragment(); - case TabManager.INDEX_INSTALLED: - return new InstalledAppsFragment(); - default: - return new CanUpdateAppsFragment(); - } - } - - @Override - public int getCount() { - return TabManager.INDEX_COUNT; - } - - @Override - public String getPageTitle(int i) { - switch (i) { - case TabManager.INDEX_AVAILABLE: - return parent.getString(R.string.tab_available_apps); - case TabManager.INDEX_INSTALLED: - return getInstalledTabTitle(); - case TabManager.INDEX_CAN_UPDATE: - return getUpdateTabTitle(); - default: - return ""; - } - } - -} diff --git a/app/src/main/java/org/fdroid/fdroid/views/AvailableAppListAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/AvailableAppListAdapter.java deleted file mode 100644 index e39f67ee1..000000000 --- a/app/src/main/java/org/fdroid/fdroid/views/AvailableAppListAdapter.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.fdroid.fdroid.views; - -import android.content.Context; -import android.database.Cursor; -import android.os.Build; - -public class AvailableAppListAdapter extends AppListAdapter { - - public static AvailableAppListAdapter create(Context context, Cursor cursor, int flags) { - if (Build.VERSION.SDK_INT >= 11) { - return new AvailableAppListAdapter(context, cursor, flags); - } - return new AvailableAppListAdapter(context, cursor); - } - - private AvailableAppListAdapter(Context context, Cursor c) { - super(context, c); - } - - public AvailableAppListAdapter(Context context, Cursor c, boolean autoRequery) { - super(context, c, autoRequery); - } - - private AvailableAppListAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - } - - @Override - protected boolean showStatusUpdate() { - return true; - } - - @Override - protected boolean showStatusInstalled() { - return true; - } -} diff --git a/app/src/main/java/org/fdroid/fdroid/views/CanUpdateAppListAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/CanUpdateAppListAdapter.java deleted file mode 100644 index 9578370aa..000000000 --- a/app/src/main/java/org/fdroid/fdroid/views/CanUpdateAppListAdapter.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.fdroid.fdroid.views; - -import android.content.Context; -import android.database.Cursor; -import android.os.Build; - -public class CanUpdateAppListAdapter extends AppListAdapter { - - public static CanUpdateAppListAdapter create(Context context, Cursor cursor, int flags) { - if (Build.VERSION.SDK_INT >= 11) { - return new CanUpdateAppListAdapter(context, cursor, flags); - } - return new CanUpdateAppListAdapter(context, cursor); - } - - private CanUpdateAppListAdapter(Context context, Cursor c) { - super(context, c); - } - - public CanUpdateAppListAdapter(Context context, Cursor c, boolean autoRequery) { - super(context, c, autoRequery); - } - - private CanUpdateAppListAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - } - - @Override - protected boolean showStatusUpdate() { - return true; - } - - @Override - protected boolean showStatusInstalled() { - return false; - } -} diff --git a/app/src/main/java/org/fdroid/fdroid/views/InstalledAppListAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/InstalledAppListAdapter.java deleted file mode 100644 index 4c97ecc9e..000000000 --- a/app/src/main/java/org/fdroid/fdroid/views/InstalledAppListAdapter.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.fdroid.fdroid.views; - -import android.content.Context; -import android.database.Cursor; -import android.os.Build; - -public class InstalledAppListAdapter extends AppListAdapter { - - public static InstalledAppListAdapter create(Context context, Cursor cursor, int flags) { - if (Build.VERSION.SDK_INT >= 11) { - return new InstalledAppListAdapter(context, cursor, flags); - } - return new InstalledAppListAdapter(context, cursor); - } - - private InstalledAppListAdapter(Context context, Cursor c) { - super(context, c); - } - - public InstalledAppListAdapter(Context context, Cursor c, boolean autoRequery) { - super(context, c, autoRequery); - } - - private InstalledAppListAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - } - - @Override - protected boolean showStatusUpdate() { - return true; - } - - @Override - protected boolean showStatusInstalled() { - return false; - } -} diff --git a/app/src/main/java/org/fdroid/fdroid/views/RepoDetailsActivity.java b/app/src/main/java/org/fdroid/fdroid/views/RepoDetailsActivity.java index dcc435adb..4a58aeac2 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/RepoDetailsActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/RepoDetailsActivity.java @@ -147,7 +147,7 @@ public class RepoDetailsActivity extends ActionBarActivity { LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, new IntentFilter(UpdateService.LOCAL_ACTION_STATUS)); - // FDroid.java and AppDetails set different NFC actions, so reset here + // FDroid.java and AppDetails2 set different NFC actions, so reset here setNfc(); processIntent(getIntent()); } diff --git a/app/src/main/java/org/fdroid/fdroid/views/fragments/AppListFragment.java b/app/src/main/java/org/fdroid/fdroid/views/fragments/AppListFragment.java deleted file mode 100644 index d156933a3..000000000 --- a/app/src/main/java/org/fdroid/fdroid/views/fragments/AppListFragment.java +++ /dev/null @@ -1,240 +0,0 @@ -package org.fdroid.fdroid.views.fragments; - -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.ActivityOptionsCompat; -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.support.v4.util.Pair; -import android.text.TextUtils; -import android.view.View; -import android.widget.AdapterView; -import android.widget.TextView; - -import org.fdroid.fdroid.AppDetails; -import org.fdroid.fdroid.AppDetails2; -import org.fdroid.fdroid.Preferences; -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.UpdateService; -import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.data.App; -import org.fdroid.fdroid.data.Schema.AppMetadataTable; -import org.fdroid.fdroid.views.AppListAdapter; - -public abstract class AppListFragment extends ListFragment implements - AdapterView.OnItemClickListener, - AdapterView.OnItemLongClickListener, - Preferences.ChangeListener, - LoaderManager.LoaderCallbacks { - - private static final String TAG = "AppListFragment"; - - private static final int REQUEST_APPDETAILS = 0; - - private static final String[] APP_PROJECTION = { - AppMetadataTable.Cols._ID, // Required for cursor loader to work. - AppMetadataTable.Cols.Package.PACKAGE_NAME, - AppMetadataTable.Cols.NAME, - AppMetadataTable.Cols.SUMMARY, - AppMetadataTable.Cols.IS_COMPATIBLE, - AppMetadataTable.Cols.LICENSE, - AppMetadataTable.Cols.ICON, - AppMetadataTable.Cols.ICON_URL, - AppMetadataTable.Cols.InstalledApp.VERSION_CODE, - AppMetadataTable.Cols.InstalledApp.VERSION_NAME, - AppMetadataTable.Cols.SuggestedApk.VERSION_NAME, - AppMetadataTable.Cols.SUGGESTED_VERSION_CODE, - AppMetadataTable.Cols.REQUIREMENTS, // Needed for filtering apps that require root. - AppMetadataTable.Cols.ANTI_FEATURES, // Needed for filtering apps that require anti-features. - }; - - private static final String APP_SORT = AppMetadataTable.Cols.NAME; - - private AppListAdapter appAdapter; - - @Nullable private String searchQuery; - - protected abstract AppListAdapter getAppListAdapter(); - - protected abstract String getFromTitle(); - - protected abstract Uri getDataUri(); - - protected abstract Uri getDataUri(String query); - - protected abstract int getEmptyMessage(); - - protected abstract int getNoSearchResultsMessage(); - - /** - * Subclasses can choose to do different things based on when a user begins searching. - * For example, the "Available" tab chooses to hide its category spinner to make it clear - * that it is searching all apps, not the current category. - * NOTE: This will get called multiple times, every time the user changes the - * search query. - */ - void onSearch() { - // Do nothing by default. - } - - /** - * Alerts the child class that the user is no longer performing a search. - * This is triggered every time the search query is blank. - * - * @see AppListFragment#onSearch() - */ - protected void onSearchStopped() { - // Do nothing by default. - } - - /** - * Utility function to set empty view text which should be different - * depending on whether search is active or not. - */ - private void setEmptyText(int resId) { - ((TextView) getListView().getEmptyView()).setText(resId); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - // Can't do this in the onCreate view, because "onCreateView" which - // returns the list view is "called between onCreate and - // onActivityCreated" according to the docs. - getListView().setOnItemClickListener(this); - getListView().setOnItemLongClickListener(this); - } - - @Override - public void onResume() { - super.onResume(); - - //Starts a new or restarts an existing Loader in this manager - getLoaderManager().initLoader(0, null, this); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - appAdapter = getAppListAdapter(); - - if (appAdapter.getCount() == 0) { - updateEmptyRepos(); - } - - setListAdapter(appAdapter); - } - - /** - * The first time the app is run, we will have an empty app list. - * If this is the case, we will attempt to update with the default repo. - * However, if we have tried this at least once, then don't try to do - * it automatically again, because the repos or internet connection may - * be bad. - */ - private boolean updateEmptyRepos() { - Preferences prefs = Preferences.get(); - if (!prefs.hasTriedEmptyUpdate()) { - Utils.debugLog(TAG, "Empty app list, and we haven't done an update yet. Forcing repo update."); - prefs.setTriedEmptyUpdate(true); - UpdateService.updateNow(getActivity()); - return true; - } - Utils.debugLog(TAG, "Empty app list, but it looks like we've had an update previously. Will not force repo update."); - return false; - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - showItemDetails(view, position, false); - } - - @Override - public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { - showItemDetails(view, position, true); - return true; - } - - private void showItemDetails(View view, int position, boolean useNewDetailsActivity) { - // Cursor is null in the swap list when touching the first item. - Cursor cursor = (Cursor) getListView().getItemAtPosition(position); - if (cursor != null) { - final App app = new App(cursor); - Intent intent = getAppDetailsIntent(useNewDetailsActivity); - intent.putExtra(AppDetails2.EXTRA_APPID, app.packageName); - intent.putExtra(AppDetails.EXTRA_FROM, getFromTitle()); - if (Build.VERSION.SDK_INT >= 21) { - Pair iconTransitionPair = Pair.create(view.findViewById(R.id.icon), - getString(R.string.transition_app_item_icon)); - Bundle bundle = ActivityOptionsCompat - .makeSceneTransitionAnimation(getActivity(), - iconTransitionPair) - .toBundle(); - startActivityForResult(intent, REQUEST_APPDETAILS, bundle); - } else { - startActivityForResult(intent, REQUEST_APPDETAILS); - } - } - } - - private Intent getAppDetailsIntent(boolean useNewDetailsActivity) { - return new Intent(getActivity(), useNewDetailsActivity ? AppDetails2.class : AppDetails.class); - } - - @Override - public void onPreferenceChange() { - getAppListAdapter().notifyDataSetChanged(); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - appAdapter.swapCursor(data); - } - - @Override - public void onLoaderReset(Loader loader) { - appAdapter.swapCursor(null); - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - Uri uri = updateSearchStatus() ? getDataUri(searchQuery) : getDataUri(); - return new CursorLoader( - getActivity(), uri, APP_PROJECTION, null, null, APP_SORT); - } - - /** - * Notifies the subclass via {@link AppListFragment#onSearch()} and {@link AppListFragment#onSearchStopped()} - * about whether or not a search is taking place and changes empty message - * appropriately. - * - * @return True if a user is searching. - */ - private boolean updateSearchStatus() { - if (TextUtils.isEmpty(searchQuery)) { - onSearchStopped(); - setEmptyText(getEmptyMessage()); - return false; - } - onSearch(); - setEmptyText(getNoSearchResultsMessage()); - return true; - } - - public void updateSearchQuery(@Nullable String query) { - if (!TextUtils.equals(query, searchQuery)) { - searchQuery = query; - if (isAdded()) { - getLoaderManager().restartLoader(0, null, this); - } - } - } -} diff --git a/app/src/main/java/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java b/app/src/main/java/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java deleted file mode 100644 index ce495b2e8..000000000 --- a/app/src/main/java/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java +++ /dev/null @@ -1,253 +0,0 @@ -package org.fdroid.fdroid.views.fragments; - -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.database.Cursor; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.support.annotation.Nullable; -import android.support.v4.app.LoaderManager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Spinner; - -import org.fdroid.fdroid.Preferences; -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.compat.ArrayAdapterCompat; -import org.fdroid.fdroid.compat.CursorAdapterCompat; -import org.fdroid.fdroid.data.AppProvider; -import org.fdroid.fdroid.data.CategoryProvider; -import org.fdroid.fdroid.views.AppListAdapter; -import org.fdroid.fdroid.views.AvailableAppListAdapter; - -import java.util.ArrayList; -import java.util.List; - -public class AvailableAppsFragment extends AppListFragment implements - LoaderManager.LoaderCallbacks { - - private static final String TAG = "AvailableAppsFragment"; - - private static final String PREFERENCES_FILE = "CategorySpinnerPosition"; - private static final String CATEGORY_KEY = "Selection"; - private static String defaultCategory; - - private List categories; - - @Nullable - private View categoryWrapper; - - @Nullable - private Spinner categorySpinner; - private String currentCategory; - private AppListAdapter adapter; - - @Override - protected String getFromTitle() { - return getString(R.string.tab_available_apps); - } - - @Override - protected AppListAdapter getAppListAdapter() { - if (adapter == null) { - final AppListAdapter a = AvailableAppListAdapter.create(getActivity(), null, CursorAdapterCompat.FLAG_AUTO_REQUERY); - Preferences.get().registerUpdateHistoryListener(new Preferences.ChangeListener() { - @Override - public void onPreferenceChange() { - a.notifyDataSetChanged(); - } - }); - adapter = a; - } - return adapter; - } - - private class CategoryObserver extends ContentObserver { - - private final ArrayAdapter adapter; - - CategoryObserver(ArrayAdapter adapter) { - // Using Looper.getMainLooper() ensures that the onChange method is run on the main thread. - super(new Handler(Looper.getMainLooper())); - this.adapter = adapter; - } - - @Override - public void onChange(boolean selfChange) { - final Activity activity = getActivity(); - if (!isAdded() || adapter == null || activity == null) { - return; - } - - // Because onChange is always invoked on the main thread (see constructor), we want to - // run the database query on a background thread. Afterwards, the UI is updated - // on a foreground thread. - new AsyncTask>() { - @Override - protected List doInBackground(Void... params) { - return CategoryProvider.Helper.categories(activity); - } - - @Override - protected void onPostExecute(List loadedCategories) { - adapter.clear(); - categories = loadedCategories; - ArrayAdapterCompat.addAll(adapter, translateCategories(activity, loadedCategories)); - } - }.execute(); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - onChange(selfChange); - } - } - - /** - * Attempt to translate category names with fallback to default name if no translation available - */ - private static List translateCategories(Context context, List categories) { - List translatedCategories = new ArrayList<>(categories.size()); - Resources res = context.getResources(); - String pkgName = context.getPackageName(); - for (String category : categories) { - String resId = category.replace(" & ", "_").replace(" ", "_").replace("'", ""); - int id = res.getIdentifier("category_" + resId, "string", pkgName); - translatedCategories.add(id == 0 ? category : context.getString(id)); - } - return translatedCategories; - } - - private Spinner setupCategorySpinner(Spinner spinner) { - - categorySpinner = spinner; - categorySpinner.setId(R.id.category_spinner); - - categories = CategoryProvider.Helper.categories(getActivity()); - - ArrayAdapter adapter = new ArrayAdapter<>( - getActivity(), android.R.layout.simple_spinner_item, translateCategories(getActivity(), categories)); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - categorySpinner.setAdapter(adapter); - - getActivity().getContentResolver().registerContentObserver( - AppProvider.getContentUri(), false, new CategoryObserver(adapter)); - - categorySpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int pos, long id) { - getListView().setSelection(0); - setCurrentCategory(categories.get(pos)); - } - - @Override - public void onNothingSelected(AdapterView parent) { - setCurrentCategory(null); - } - }); - return categorySpinner; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.available_app_list, container, false); - - categoryWrapper = view.findViewById(R.id.category_wrapper); - setupCategorySpinner((Spinner) view.findViewById(R.id.category_spinner)); - defaultCategory = CategoryProvider.Helper.getCategoryWhatsNew(getActivity()); - - return view; - } - - @Override - protected Uri getDataUri() { - if (currentCategory == null || currentCategory.equals(CategoryProvider.Helper.getCategoryAll(getActivity()))) { - return AppProvider.getContentUri(); - } - if (currentCategory.equals(CategoryProvider.Helper.getCategoryRecentlyUpdated(getActivity()))) { - return AppProvider.getRecentlyUpdatedUri(); - } - if (currentCategory.equals(CategoryProvider.Helper.getCategoryWhatsNew(getActivity()))) { - // Removed this feature in the new UI. this fragment will be gone soon so not implementing it again. - // return AppProvider.getNewlyAddedUri(); - return AppProvider.getRecentlyUpdatedUri(); - } - return AppProvider.getCategoryUri(currentCategory); - } - - @Override - protected Uri getDataUri(String query) { - return AppProvider.getSearchUri(query, null); - } - - @Override - protected int getEmptyMessage() { - return R.string.empty_available_app_list; - } - - @Override - protected int getNoSearchResultsMessage() { - return R.string.empty_search_available_app_list; - } - - private void setCurrentCategory(String category) { - currentCategory = category; - Utils.debugLog(TAG, "Category '" + currentCategory + "' selected."); - getLoaderManager().restartLoader(0, null, this); - } - - @Override - public void onResume() { - /* restore the saved Category Spinner position */ - Activity activity = getActivity(); - SharedPreferences p = activity.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE); - currentCategory = p.getString(CATEGORY_KEY, defaultCategory); - - if (categorySpinner != null) { - for (int i = 0; i < categorySpinner.getCount(); i++) { - if (currentCategory.equals(categorySpinner.getItemAtPosition(i).toString())) { - categorySpinner.setSelection(i); - break; - } - } - } - - setCurrentCategory(currentCategory); - super.onResume(); - } - - @Override - public void onPause() { - super.onPause(); - /* store the Category Spinner position for when we come back */ - SharedPreferences p = getActivity().getSharedPreferences(PREFERENCES_FILE, - Context.MODE_PRIVATE); - SharedPreferences.Editor e = p.edit(); - e.putString(CATEGORY_KEY, currentCategory); - e.apply(); - } - - @Override - protected void onSearch() { - if (categoryWrapper != null) { - categoryWrapper.setVisibility(View.GONE); - } - } - - @Override - protected void onSearchStopped() { - if (categoryWrapper != null) { - categoryWrapper.setVisibility(View.VISIBLE); - } - } -} diff --git a/app/src/main/java/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java b/app/src/main/java/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java deleted file mode 100644 index 80d5ff11d..000000000 --- a/app/src/main/java/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.fdroid.fdroid.views.fragments; - -import android.net.Uri; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.compat.CursorAdapterCompat; -import org.fdroid.fdroid.data.AppProvider; -import org.fdroid.fdroid.views.AppListAdapter; -import org.fdroid.fdroid.views.CanUpdateAppListAdapter; - -public class CanUpdateAppsFragment extends AppListFragment { - - @Override - protected AppListAdapter getAppListAdapter() { - return CanUpdateAppListAdapter.create(getActivity(), null, CursorAdapterCompat.FLAG_AUTO_REQUERY); - } - - @Override - protected String getFromTitle() { - return getString(R.string.tab_updates); - } - - @Override - protected Uri getDataUri() { - return AppProvider.getCanUpdateUri(); - } - - @Override - protected Uri getDataUri(String query) { - return AppProvider.getSearchCanUpdateUri(query); - } - - @Override - protected int getEmptyMessage() { - return R.string.empty_can_update_app_list; - } - - @Override - protected int getNoSearchResultsMessage() { - return R.string.empty_search_can_update_app_list; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.can_update_app_list, container, false); - } - -} diff --git a/app/src/main/java/org/fdroid/fdroid/views/fragments/InstalledAppsFragment.java b/app/src/main/java/org/fdroid/fdroid/views/fragments/InstalledAppsFragment.java deleted file mode 100644 index 928629e43..000000000 --- a/app/src/main/java/org/fdroid/fdroid/views/fragments/InstalledAppsFragment.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.fdroid.fdroid.views.fragments; - -import android.net.Uri; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.compat.CursorAdapterCompat; -import org.fdroid.fdroid.data.AppProvider; -import org.fdroid.fdroid.views.AppListAdapter; -import org.fdroid.fdroid.views.InstalledAppListAdapter; - -public class InstalledAppsFragment extends AppListFragment { - - @Override - protected AppListAdapter getAppListAdapter() { - return InstalledAppListAdapter.create(getActivity(), null, CursorAdapterCompat.FLAG_AUTO_REQUERY); - } - - @Override - protected String getFromTitle() { - return getString(R.string.tab_installed_apps); - } - - @Override - protected Uri getDataUri() { - return AppProvider.getInstalledUri(); - } - - @Override - protected Uri getDataUri(String query) { - return AppProvider.getSearchInstalledUri(query); - } - - @Override - protected int getEmptyMessage() { - return R.string.empty_installed_app_list; - } - - @Override - protected int getNoSearchResultsMessage() { - return R.string.empty_search_installed_app_list; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.installed_app_list, container, false); - } - -} diff --git a/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java b/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java index c190ac645..14b4b7d51 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java +++ b/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java @@ -17,7 +17,6 @@ import org.fdroid.fdroid.AppDetails2; import org.fdroid.fdroid.CleanCacheService; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.Preferences; -import org.fdroid.fdroid.PreferencesActivity; import org.fdroid.fdroid.R; import org.fdroid.fdroid.UpdateService; import org.fdroid.fdroid.installer.InstallHistoryService; @@ -83,8 +82,6 @@ public class PreferencesFragment extends PreferenceFragment private void updateSummary(String key, boolean changing) { - int result = 0; - switch (key) { case Preferences.PREF_UPD_INTERVAL: ListPreference listPref = (ListPreference) findPreference( @@ -114,10 +111,7 @@ public class PreferencesFragment extends PreferenceFragment case Preferences.PREF_THEME: entrySummary(key); - if (changing) { - result |= PreferencesActivity.RESULT_RESTART; - getActivity().setResult(result); - } + // TODO: Ask MainActivity to restart itself. break; case Preferences.PREF_INCOMP_VER: @@ -147,10 +141,8 @@ public class PreferencesFragment extends PreferenceFragment case Preferences.PREF_LANGUAGE: entrySummary(key); if (changing) { - result |= PreferencesActivity.RESULT_RESTART; - Activity activity = getActivity(); - activity.setResult(result); - ((FDroidApp) activity.getApplication()).updateLanguage(); + // TODO: Ask MainActivity to restart itself. + ((FDroidApp) getActivity().getApplication()).updateLanguage(); } break; diff --git a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java index 571125aee..1461fc582 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java @@ -147,7 +147,7 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB setSelectedMenuInNav(); } - // AppDetails 2 and RepoDetailsActivity set different NFC actions, so reset here + // AppDetails2 and RepoDetailsActivity set different NFC actions, so reset here NfcHelper.setAndroidBeam(this, getApplication().getPackageName()); checkForAddRepoIntent(getIntent()); } diff --git a/app/src/main/res/drawable-hdpi/ic_bitcoin.png b/app/src/main/res/drawable-hdpi/ic_bitcoin.png deleted file mode 100644 index a4869252d..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_bitcoin.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_clear.png b/app/src/main/res/drawable-hdpi/ic_clear.png deleted file mode 100644 index 1a9cd75a0..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_clear.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_flattr.png b/app/src/main/res/drawable-hdpi/ic_flattr.png deleted file mode 100644 index e114f192c..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_flattr.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_help_white.png b/app/src/main/res/drawable-hdpi/ic_help_white.png deleted file mode 100644 index 5664f9532..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_help_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_litecoin.png b/app/src/main/res/drawable-hdpi/ic_litecoin.png deleted file mode 100644 index 7996d4e41..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_litecoin.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_play_arrow_white.png b/app/src/main/res/drawable-hdpi/ic_play_arrow_white.png deleted file mode 100644 index 57c9fa546..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_play_arrow_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_settings_white.png b/app/src/main/res/drawable-hdpi/ic_settings_white.png deleted file mode 100644 index 97ded33b5..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_settings_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_stat_notify_updates.png b/app/src/main/res/drawable-hdpi/ic_stat_notify_updates.png deleted file mode 100644 index f39e443f6..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_stat_notify_updates.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_toc_white.png b/app/src/main/res/drawable-hdpi/ic_toc_white.png deleted file mode 100644 index bb3adfa89..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_toc_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-ldpi/ic_stat_notify_updates.png b/app/src/main/res/drawable-ldpi/ic_stat_notify_updates.png deleted file mode 100644 index c0f2718bd..000000000 Binary files a/app/src/main/res/drawable-ldpi/ic_stat_notify_updates.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/feature_placeholder.png b/app/src/main/res/drawable-mdpi/feature_placeholder.png deleted file mode 100644 index 861559285..000000000 Binary files a/app/src/main/res/drawable-mdpi/feature_placeholder.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_bitcoin.png b/app/src/main/res/drawable-mdpi/ic_bitcoin.png deleted file mode 100644 index 132166d9d..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_bitcoin.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_clear.png b/app/src/main/res/drawable-mdpi/ic_clear.png deleted file mode 100644 index 40a1a84e3..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_clear.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_flattr.png b/app/src/main/res/drawable-mdpi/ic_flattr.png deleted file mode 100644 index 850d5b949..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_flattr.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_help_white.png b/app/src/main/res/drawable-mdpi/ic_help_white.png deleted file mode 100644 index db699622b..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_help_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_litecoin.png b/app/src/main/res/drawable-mdpi/ic_litecoin.png deleted file mode 100644 index 4af36b5ce..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_litecoin.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_play_arrow_white.png b/app/src/main/res/drawable-mdpi/ic_play_arrow_white.png deleted file mode 100644 index c61e948bb..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_play_arrow_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_settings_white.png b/app/src/main/res/drawable-mdpi/ic_settings_white.png deleted file mode 100644 index 8909c3553..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_settings_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_stat_notify_updates.png b/app/src/main/res/drawable-mdpi/ic_stat_notify_updates.png deleted file mode 100644 index def295299..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_stat_notify_updates.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_toc_white.png b/app/src/main/res/drawable-mdpi/ic_toc_white.png deleted file mode 100644 index 2a386add0..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_toc_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_bitcoin.png b/app/src/main/res/drawable-xhdpi/ic_bitcoin.png deleted file mode 100644 index 85144b97c..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_bitcoin.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_clear.png b/app/src/main/res/drawable-xhdpi/ic_clear.png deleted file mode 100644 index 6bc437298..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_clear.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_flattr.png b/app/src/main/res/drawable-xhdpi/ic_flattr.png deleted file mode 100644 index 7bca27f5a..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_flattr.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_help_white.png b/app/src/main/res/drawable-xhdpi/ic_help_white.png deleted file mode 100644 index 2d11cf47a..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_help_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_litecoin.png b/app/src/main/res/drawable-xhdpi/ic_litecoin.png deleted file mode 100644 index d4ac66e35..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_litecoin.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_play_arrow_white.png b/app/src/main/res/drawable-xhdpi/ic_play_arrow_white.png deleted file mode 100644 index a3c80e73d..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_play_arrow_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_settings_white.png b/app/src/main/res/drawable-xhdpi/ic_settings_white.png deleted file mode 100644 index 5caedc8e5..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_settings_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_notify_updates.png b/app/src/main/res/drawable-xhdpi/ic_stat_notify_updates.png deleted file mode 100644 index 1dcf07911..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_stat_notify_updates.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_toc_white.png b/app/src/main/res/drawable-xhdpi/ic_toc_white.png deleted file mode 100644 index 7563e739c..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_toc_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_bitcoin.png b/app/src/main/res/drawable-xxhdpi/ic_bitcoin.png deleted file mode 100644 index 688fe1e10..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_bitcoin.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_clear.png b/app/src/main/res/drawable-xxhdpi/ic_clear.png deleted file mode 100644 index 51b4401ca..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_clear.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_flattr.png b/app/src/main/res/drawable-xxhdpi/ic_flattr.png deleted file mode 100644 index 0005048c1..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_flattr.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_help_white.png b/app/src/main/res/drawable-xxhdpi/ic_help_white.png deleted file mode 100644 index d49181785..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_help_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_litecoin.png b/app/src/main/res/drawable-xxhdpi/ic_litecoin.png deleted file mode 100644 index 41351fd1a..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_litecoin.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white.png b/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white.png deleted file mode 100644 index 547ef30aa..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_settings_white.png b/app/src/main/res/drawable-xxhdpi/ic_settings_white.png deleted file mode 100644 index eabb0a2ba..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_settings_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_notify_updates.png b/app/src/main/res/drawable-xxhdpi/ic_stat_notify_updates.png deleted file mode 100644 index 11c7c3e72..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_stat_notify_updates.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_toc_white.png b/app/src/main/res/drawable-xxhdpi/ic_toc_white.png deleted file mode 100644 index 108a026a9..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_toc_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_bitcoin.png b/app/src/main/res/drawable-xxxhdpi/ic_bitcoin.png deleted file mode 100644 index b443f9e5f..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_bitcoin.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_clear.png b/app/src/main/res/drawable-xxxhdpi/ic_clear.png deleted file mode 100644 index df42feecb..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_clear.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_flattr.png b/app/src/main/res/drawable-xxxhdpi/ic_flattr.png deleted file mode 100644 index 41fc3c830..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_flattr.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_help_white.png b/app/src/main/res/drawable-xxxhdpi/ic_help_white.png deleted file mode 100644 index 8eb7241da..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_help_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_litecoin.png b/app/src/main/res/drawable-xxxhdpi/ic_litecoin.png deleted file mode 100644 index f28268c89..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_litecoin.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white.png b/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white.png deleted file mode 100644 index be5c062b5..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_settings_white.png b/app/src/main/res/drawable-xxxhdpi/ic_settings_white.png deleted file mode 100644 index 507c5edd4..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_settings_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_stat_notify_updates.png b/app/src/main/res/drawable-xxxhdpi/ic_stat_notify_updates.png deleted file mode 100644 index a8457c9d0..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_stat_notify_updates.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_toc_white.png b/app/src/main/res/drawable-xxxhdpi/ic_toc_white.png deleted file mode 100644 index 0ec6e3895..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_toc_white.png and /dev/null differ diff --git a/app/src/main/res/drawable/badge_background.xml b/app/src/main/res/drawable/badge_background.xml deleted file mode 100644 index b72685b11..000000000 --- a/app/src/main/res/drawable/badge_background.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/category_chip_background.xml b/app/src/main/res/drawable/category_chip_background.xml deleted file mode 100644 index fd4ed2ac8..000000000 --- a/app/src/main/res/drawable/category_chip_background.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/download_button.xml b/app/src/main/res/drawable/download_button.xml deleted file mode 100644 index 4faebbd9f..000000000 --- a/app/src/main/res/drawable/download_button.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_cancel_black_24dp.xml b/app/src/main/res/drawable/ic_cancel_black_24dp.xml deleted file mode 100644 index 7d2b57eb2..000000000 --- a/app/src/main/res/drawable/ic_cancel_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_download_button.xml b/app/src/main/res/drawable/ic_download_button.xml deleted file mode 100644 index 562b5f20e..000000000 --- a/app/src/main/res/drawable/ic_download_button.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/app/src/main/res/layout-land/app_details.xml b/app/src/main/res/layout-land/app_details.xml deleted file mode 100644 index 56eed3dd8..000000000 --- a/app/src/main/res/layout-land/app_details.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout-v21/app_details_header.xml b/app/src/main/res/layout-v21/app_details_header.xml deleted file mode 100644 index 447d9dcf1..000000000 --- a/app/src/main/res/layout-v21/app_details_header.xml +++ /dev/null @@ -1,179 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - -