From fada693ac9b4ddd4eec33ea1cf2cbab8f82507e8 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 28 Mar 2017 11:51:41 +1100 Subject: [PATCH] Remove AppDetails (in preference to AppDetails2). Also removed associated layouts which are no longer required. --- .../java/org/fdroid/fdroid/AppDetails.java | 1803 ----------------- .../java/org/fdroid/fdroid/AppDetails2.java | 21 + .../views/AppSecurityPermissions.java | 2 +- .../fdroid/views/RepoDetailsActivity.java | 2 +- .../fdroid/views/main/MainActivity.java | 2 +- app/src/main/res/layout-land/app_details.xml | 47 - .../res/layout-v21/app_details_header.xml | 179 -- app/src/main/res/layout/app_details.xml | 31 - .../main/res/layout/app_details_header.xml | 178 -- .../main/res/layout/app_details_summary.xml | 233 --- 10 files changed, 24 insertions(+), 2474 deletions(-) delete mode 100644 app/src/main/java/org/fdroid/fdroid/AppDetails.java delete mode 100644 app/src/main/res/layout-land/app_details.xml delete mode 100644 app/src/main/res/layout-v21/app_details_header.xml delete mode 100644 app/src/main/res/layout/app_details.xml delete mode 100644 app/src/main/res/layout/app_details_header.xml delete mode 100644 app/src/main/res/layout/app_details_summary.xml diff --git a/app/src/main/java/org/fdroid/fdroid/AppDetails.java b/app/src/main/java/org/fdroid/fdroid/AppDetails.java deleted file mode 100644 index ec52e6d56..000000000 --- a/app/src/main/java/org/fdroid/fdroid/AppDetails.java +++ /dev/null @@ -1,1803 +0,0 @@ -/* - * 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; -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/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/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/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/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 @@ - - - - - - - - - - - - - - - - - - - - - - -