From 5ebe3b2420ac349408589eb3f3bff4d1c32bbf2a Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 28 Mar 2017 10:35:24 +1100 Subject: [PATCH 01/14] Remove references to unused classes in AndroidManifest.xml --- app/src/main/AndroidManifest.xml | 29 ----------------------------- 1 file changed, 29 deletions(-) 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" /> - - - - - - - - - - - Date: Tue, 28 Mar 2017 10:38:15 +1100 Subject: [PATCH 02/14] Removed AppListFragmentPageAdapter Only used in old FDroid activity which is about to be removed. --- .../main/java/org/fdroid/fdroid/FDroid.java | 6 - .../views/AppListFragmentPagerAdapter.java | 116 ------------------ 2 files changed, 122 deletions(-) delete mode 100644 app/src/main/java/org/fdroid/fdroid/views/AppListFragmentPagerAdapter.java diff --git a/app/src/main/java/org/fdroid/fdroid/FDroid.java b/app/src/main/java/org/fdroid/fdroid/FDroid.java index 5aff91827..422de3d9b 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroid.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroid.java @@ -48,7 +48,6 @@ 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; @@ -75,8 +74,6 @@ public class FDroid extends AppCompatActivity implements SearchView.OnQueryTextL @Nullable private TabManager tabManager; - private AppListFragmentPagerAdapter adapter; - @Nullable private MenuItem searchMenuItem; @@ -389,8 +386,6 @@ public class FDroid extends AppCompatActivity implements SearchView.OnQueryTextL 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) { @@ -426,7 +421,6 @@ public class FDroid extends AppCompatActivity implements SearchView.OnQueryTextL @Override public boolean onQueryTextChange(String newText) { - adapter.updateSearchQuery(newText); return true; } 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 ""; - } - } - -} From 7b9219e70180641a521c45d1eb786d5a456514af Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 28 Mar 2017 10:40:36 +1100 Subject: [PATCH 03/14] Removed TabManager Only used in old FDroid activity which is about to be removed. --- .../main/java/org/fdroid/fdroid/FDroid.java | 42 +---- .../org/fdroid/fdroid/compat/TabManager.java | 154 ------------------ 2 files changed, 1 insertion(+), 195 deletions(-) delete mode 100644 app/src/main/java/org/fdroid/fdroid/compat/TabManager.java diff --git a/app/src/main/java/org/fdroid/fdroid/FDroid.java b/app/src/main/java/org/fdroid/fdroid/FDroid.java index 422de3d9b..50b57bc32 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroid.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroid.java @@ -25,11 +25,9 @@ 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; @@ -44,7 +42,6 @@ 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; @@ -71,9 +68,6 @@ public class FDroid extends AppCompatActivity implements SearchView.OnQueryTextL private ViewPager viewPager; - @Nullable - private TabManager tabManager; - @Nullable private MenuItem searchMenuItem; @@ -90,21 +84,12 @@ public class FDroid extends AppCompatActivity implements SearchView.OnQueryTextL 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()); @@ -270,12 +255,6 @@ public class FDroid extends AppCompatActivity implements SearchView.OnQueryTextL } } - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - getTabManager().onConfigurationChanged(newConfig); - } - @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); @@ -386,25 +365,6 @@ public class FDroid extends AppCompatActivity implements SearchView.OnQueryTextL private void createViews() { viewPager = (ViewPager) findViewById(R.id.main_pager); - 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) { @@ -435,7 +395,7 @@ public class FDroid extends AppCompatActivity implements SearchView.OnQueryTextL FDroid.this.runOnUiThread(new Runnable() { @Override public void run() { - refreshUpdateTabLabel(); + } }); } 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; - } -} From 4db31ef97590fac94e743e092e0eea13ebc7f963 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 28 Mar 2017 10:47:34 +1100 Subject: [PATCH 04/14] Removed FDroid.java, no longer used. --- .../main/java/org/fdroid/fdroid/FDroid.java | 410 ------------------ app/src/main/res/layout/fdroid.xml | 17 - 2 files changed, 427 deletions(-) delete mode 100644 app/src/main/java/org/fdroid/fdroid/FDroid.java delete mode 100644 app/src/main/res/layout/fdroid.xml 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 50b57bc32..000000000 --- a/app/src/main/java/org/fdroid/fdroid/FDroid.java +++ /dev/null @@ -1,410 +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.database.ContentObserver; -import android.net.Uri; -import android.os.Bundle; -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.UriCompat; -import org.fdroid.fdroid.data.AppProvider; -import org.fdroid.fdroid.data.NewRepoConfig; -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 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(); - - // Start a search by just typing - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); - - Intent intent = getIntent(); - handleSearchOrAppViewIntent(intent); - - 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 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); - } - - 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) { - 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() { - - } - }); - } - - @Override - public void onChange(boolean selfChange) { - onChange(selfChange, null); - } - - } - -} diff --git a/app/src/main/res/layout/fdroid.xml b/app/src/main/res/layout/fdroid.xml deleted file mode 100644 index d7d866c02..000000000 --- a/app/src/main/res/layout/fdroid.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - From 6f80fbd9451a62b8a3672bd06db6b88967835e6a Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 28 Mar 2017 10:52:57 +1100 Subject: [PATCH 05/14] Removed PreferencesActivity. This is now a top level view inside MainActivity. --- .../fdroid/fdroid/PreferencesActivity.java | 74 ------------------- .../views/fragments/PreferencesFragment.java | 14 +--- 2 files changed, 3 insertions(+), 85 deletions(-) delete mode 100644 app/src/main/java/org/fdroid/fdroid/PreferencesActivity.java 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/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; From 65afc83b16b7bb1b57be8f9cdb149fc3b27d88cc Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 28 Mar 2017 10:54:41 +1100 Subject: [PATCH 06/14] Removed app list fragments from old 3 tab layout. --- .../views/fragments/AppListFragment.java | 240 ----------------- .../fragments/AvailableAppsFragment.java | 253 ------------------ .../fragments/CanUpdateAppsFragment.java | 52 ---- .../fragments/InstalledAppsFragment.java | 52 ---- app/src/main/res/layout-v21/applistitem.xml | 104 ------- app/src/main/res/layout/applistitem.xml | 104 ------- .../main/res/layout/available_app_list.xml | 38 --- .../main/res/layout/can_update_app_list.xml | 14 - .../main/res/layout/installed_app_list.xml | 14 - 9 files changed, 871 deletions(-) delete mode 100644 app/src/main/java/org/fdroid/fdroid/views/fragments/AppListFragment.java delete mode 100644 app/src/main/java/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java delete mode 100644 app/src/main/java/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java delete mode 100644 app/src/main/java/org/fdroid/fdroid/views/fragments/InstalledAppsFragment.java delete mode 100644 app/src/main/res/layout-v21/applistitem.xml delete mode 100644 app/src/main/res/layout/applistitem.xml delete mode 100644 app/src/main/res/layout/available_app_list.xml delete mode 100644 app/src/main/res/layout/can_update_app_list.xml delete mode 100644 app/src/main/res/layout/installed_app_list.xml 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/res/layout-v21/applistitem.xml b/app/src/main/res/layout-v21/applistitem.xml deleted file mode 100644 index 9f45beab6..000000000 --- a/app/src/main/res/layout-v21/applistitem.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/applistitem.xml b/app/src/main/res/layout/applistitem.xml deleted file mode 100644 index aed8e85e0..000000000 --- a/app/src/main/res/layout/applistitem.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/available_app_list.xml b/app/src/main/res/layout/available_app_list.xml deleted file mode 100644 index e1b35d157..000000000 --- a/app/src/main/res/layout/available_app_list.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/can_update_app_list.xml b/app/src/main/res/layout/can_update_app_list.xml deleted file mode 100644 index fa7557058..000000000 --- a/app/src/main/res/layout/can_update_app_list.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/layout/installed_app_list.xml b/app/src/main/res/layout/installed_app_list.xml deleted file mode 100644 index 869380928..000000000 --- a/app/src/main/res/layout/installed_app_list.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - From d7fbd868b93e1d6ebbe836823f80496cda797eea Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 28 Mar 2017 10:56:11 +1100 Subject: [PATCH 07/14] Remove app list adapters from old 3 tab layout. --- .../java/org/fdroid/fdroid/Preferences.java | 4 +- .../fdroid/fdroid/views/AppListAdapter.java | 137 ------------------ .../fdroid/views/AvailableAppListAdapter.java | 37 ----- .../fdroid/views/CanUpdateAppListAdapter.java | 37 ----- .../fdroid/views/InstalledAppListAdapter.java | 37 ----- 5 files changed, 2 insertions(+), 250 deletions(-) delete mode 100644 app/src/main/java/org/fdroid/fdroid/views/AppListAdapter.java delete mode 100644 app/src/main/java/org/fdroid/fdroid/views/AvailableAppListAdapter.java delete mode 100644 app/src/main/java/org/fdroid/fdroid/views/CanUpdateAppListAdapter.java delete mode 100644 app/src/main/java/org/fdroid/fdroid/views/InstalledAppListAdapter.java 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/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/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; - } -} From a8e6daadc3da97dff8dd85b5eb97beaffe9b9ba8 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 28 Mar 2017 10:57:50 +1100 Subject: [PATCH 08/14] Removed now-unused ArrayAdapterCompat class. --- .../fdroid/compat/ArrayAdapterCompat.java | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 app/src/main/java/org/fdroid/fdroid/compat/ArrayAdapterCompat.java 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); - } - } - } - -} From c2d5f57fbd854328225f2d70a01c93f9f2bcd817 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 28 Mar 2017 11:06:54 +1100 Subject: [PATCH 09/14] Remove now-unused queries from AppProvider. --- .../org/fdroid/fdroid/data/AppProvider.java | 56 +------------------ 1 file changed, 1 insertion(+), 55 deletions(-) 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))) From 3efb3a3043931e5be96e9c91627f3100799c58d0 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 28 Mar 2017 11:19:07 +1100 Subject: [PATCH 10/14] Remove category provider stuff specific to old 3 tab layout. We no longer prefix the list of categories with "Whats New", "Recently Updated", and "All". The new UI doesn't require this. The only place they were being used now were in the tests. --- .../fdroid/fdroid/data/CategoryProvider.java | 47 -------------- .../fdroid/data/CategoryProviderTest.java | 62 +++++++++---------- 2 files changed, 31 insertions(+), 78 deletions(-) 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/test/java/org/fdroid/fdroid/data/CategoryProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/CategoryProviderTest.java index 986491b42..55ee6d8b3 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/CategoryProviderTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/CategoryProviderTest.java @@ -1,12 +1,12 @@ package org.fdroid.fdroid.data; import android.app.Application; +import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import org.fdroid.fdroid.BuildConfig; -import org.fdroid.fdroid.R; import org.fdroid.fdroid.TestUtils; import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols; import org.fdroid.fdroid.mock.MockRepo; @@ -16,6 +16,8 @@ import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import static org.fdroid.fdroid.Assert.assertContainsOnly; @@ -45,10 +47,6 @@ public class CategoryProviderTest extends FDroidProviderTest { insertAppWithCategory("com.banana.apple", "Banana", "Vegetable,Vegetable", mainRepo); String[] expectedFDroid = new String[] { - context.getResources().getString(R.string.category_Whats_New), - context.getResources().getString(R.string.category_Recently_Updated), - context.getResources().getString(R.string.category_All), - "Animal", "Mineral", "Security", @@ -57,20 +55,12 @@ public class CategoryProviderTest extends FDroidProviderTest { }; String[] expectedGP = new String[] { - context.getResources().getString(R.string.category_Whats_New), - context.getResources().getString(R.string.category_Recently_Updated), - context.getResources().getString(R.string.category_All), - "GuardianProject", "Office", }; // We overwrite "Security" + "Writing" with "GuardianProject" + "Office" String[] expectedBoth = new String[] { - context.getResources().getString(R.string.category_Whats_New), - context.getResources().getString(R.string.category_Recently_Updated), - context.getResources().getString(R.string.category_All), - "Animal", "Mineral", "Vegetable", @@ -79,13 +69,13 @@ public class CategoryProviderTest extends FDroidProviderTest { "Office", }; - assertContainsOnly(CategoryProvider.Helper.categories(context), expectedFDroid); + assertContainsOnly(categories(), expectedFDroid); insertAppWithCategory("info.guardianproject.notepadbot", "NoteCipher", "Office,GuardianProject", gpRepo); - assertContainsOnly(CategoryProvider.Helper.categories(context), expectedBoth); + assertContainsOnly(categories(), expectedBoth); RepoProvider.Helper.purgeApps(context, new MockRepo(mainRepo)); - List categoriesAfterPurge = CategoryProvider.Helper.categories(context); + List categoriesAfterPurge = categories(); assertContainsOnly(categoriesAfterPurge, expectedGP); } @@ -213,11 +203,8 @@ public class CategoryProviderTest extends FDroidProviderTest { insertAppWithCategory("com.rock", "Rock", "Mineral"); insertAppWithCategory("com.banana", "Banana", "Vegetable"); - List categories = CategoryProvider.Helper.categories(context); + List categories = categories(); String[] expected = new String[] { - context.getResources().getString(R.string.category_Whats_New), - context.getResources().getString(R.string.category_Recently_Updated), - context.getResources().getString(R.string.category_All), "Animal", "Mineral", "Vegetable", @@ -233,12 +220,8 @@ public class CategoryProviderTest extends FDroidProviderTest { insertAppWithCategory("com.dog.rock.apple", "Dog-Rock-Apple", "Animal,Mineral,Vegetable", mainRepo); insertAppWithCategory("com.banana.apple", "Banana", "Vegetable,Vegetable", mainRepo); - List categories = CategoryProvider.Helper.categories(context); + List categories = categories(); String[] expected = new String[] { - context.getResources().getString(R.string.category_Whats_New), - context.getResources().getString(R.string.category_Recently_Updated), - context.getResources().getString(R.string.category_All), - "Animal", "Mineral", "Vegetable", @@ -251,12 +234,8 @@ public class CategoryProviderTest extends FDroidProviderTest { "Running,Shooting,Jumping,Bleh,Sneh,Pleh,Blah,Test category," + "The quick brown fox jumps over the lazy dog,With apostrophe's", additionalRepo); - List categoriesLonger = CategoryProvider.Helper.categories(context); + List categoriesLonger = categories(); String[] expectedLonger = new String[] { - context.getResources().getString(R.string.category_Whats_New), - context.getResources().getString(R.string.category_Recently_Updated), - context.getResources().getString(R.string.category_All), - "Animal", "Mineral", "Vegetable", @@ -276,7 +255,7 @@ public class CategoryProviderTest extends FDroidProviderTest { assertContainsOnly(categoriesLonger, expectedLonger); RepoProvider.Helper.purgeApps(context, new MockRepo(additionalRepo)); - List categoriesAfterPurge = CategoryProvider.Helper.categories(context); + List categoriesAfterPurge = categories(); assertContainsOnly(categoriesAfterPurge, expected); } @@ -289,4 +268,25 @@ public class CategoryProviderTest extends FDroidProviderTest { values.put(Cols.ForWriting.Categories.CATEGORIES, categories); AppProviderTest.insertApp(contentResolver, context, id, name, values, repoId); } + + public List categories() { + final ContentResolver resolver = context.getContentResolver(); + final Uri uri = CategoryProvider.getAllCategories(); + final String[] projection = {Schema.CategoryTable.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); + return categories; + } } From fada693ac9b4ddd4eec33ea1cf2cbab8f82507e8 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 28 Mar 2017 11:51:41 +1100 Subject: [PATCH 11/14] 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 @@ - - - - - - - - - - - - - - - - - - - - - - -