From 23ed692436e8b27d533c6b611ebec3c9f5801a13 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Wed, 31 Dec 2014 00:27:16 +1100 Subject: [PATCH] "Select apps for swap" screen work on API <= 10. * Provide CheckBox for selected items Newer API's highlight the background using the "activated" state. Older APIs need this to be implemented differently, so there are now checkboxes on the left of the list view items to provide this functionality. * Clean up IDE warnings Diamond operator for generics, remove unused imports and unused method. * Adapter class created for installed apps Cleaned up the code to do with binding views to the adapter in this view. Previously it made quite a few assumptions about the structure of the layout, e.g. "layout.getParent().getParent() is a LinearLayout", which would cause crashes if the layout changed slightly. --- .../select_local_apps_list_item.xml | 1 + .../layout/select_local_apps_list_item.xml | 6 + .../fdroid/views/swap/SelectAppsFragment.java | 149 +++++++++++------- .../fdroid/views/swap/WifiQrFragment.java | 23 ++- 4 files changed, 116 insertions(+), 63 deletions(-) diff --git a/F-Droid/res/layout-v11/select_local_apps_list_item.xml b/F-Droid/res/layout-v11/select_local_apps_list_item.xml index a95e22db0..5ac585492 100644 --- a/F-Droid/res/layout-v11/select_local_apps_list_item.xml +++ b/F-Droid/res/layout-v11/select_local_apps_list_item.xml @@ -22,6 +22,7 @@ android:paddingTop="2dip" > + + , SearchView.OnQueryTextListener { - private PackageManager packageManager; - private Drawable defaultAppIcon; private String mCurrentFilterString; - private Set previouslySelectedApps = new HashSet(); + private Set previouslySelectedApps = new HashSet<>(); public Set getSelectedApps() { return FDroidApp.selectedApps; @@ -103,46 +97,10 @@ public class SelectAppsFragment extends ThemeableListFragment setEmptyText(getString(R.string.no_applications_found)); - packageManager = getActivity().getPackageManager(); - defaultAppIcon = getResources().getDrawable(android.R.drawable.sym_def_app_icon); - ListView listView = getListView(); listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - SimpleCursorAdapter adapter = new SimpleCursorAdapter( - new ContextThemeWrapper(getActivity(), R.style.SwapTheme_AppList_ListItem), - R.layout.select_local_apps_list_item, - null, - new String[] { - InstalledAppProvider.DataColumns.APPLICATION_LABEL, - InstalledAppProvider.DataColumns.APP_ID, - }, - new int[] { - R.id.application_label, - R.id.package_name, - }); - adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() { - @Override - public boolean setViewValue(View view, Cursor cursor, int columnIndex) { - if (columnIndex == cursor.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID)) { - String packageName = cursor.getString(columnIndex); - TextView textView = (TextView) view.findViewById(R.id.package_name); - textView.setText(packageName); - LinearLayout ll = (LinearLayout) view.getParent().getParent(); - ImageView iconView = (ImageView) ll.getChildAt(0); - Drawable icon; - try { - icon = packageManager.getApplicationIcon(packageName); - } catch (PackageManager.NameNotFoundException e) { - icon = defaultAppIcon; - } - iconView.setImageDrawable(icon); - return true; - } - return false; - } - }); - setListAdapter(adapter); + setListAdapter(new AppListAdapter(listView, getActivity(), null)); setListShown(false); // start out with a progress indicator // either reconnect with an existing loader or start a new one @@ -150,7 +108,7 @@ public class SelectAppsFragment extends ThemeableListFragment // build list of existing apps from what is on the file system if (FDroidApp.selectedApps == null) { - FDroidApp.selectedApps = new HashSet(); + FDroidApp.selectedApps = new HashSet<>(); for (String filename : LocalRepoManager.get(getActivity()).repoDir.list()) { if (filename.matches(".*\\.apk")) { String packageName = filename.substring(0, filename.indexOf("_")); @@ -190,7 +148,7 @@ public class SelectAppsFragment extends ThemeableListFragment @Override public void onLoadFinished(Loader loader, Cursor cursor) { - ((SimpleCursorAdapter) this.getListAdapter()).swapCursor(cursor); + ((AppListAdapter)getListAdapter()).swapCursor(cursor); ListView listView = getListView(); String fdroid = loader.getContext().getPackageName(); @@ -217,7 +175,7 @@ public class SelectAppsFragment extends ThemeableListFragment @Override public void onLoaderReset(Loader loader) { - ((SimpleCursorAdapter) this.getListAdapter()).swapCursor(null); + ((AppListAdapter)getListAdapter()).swapCursor(null); } @Override @@ -240,10 +198,6 @@ public class SelectAppsFragment extends ThemeableListFragment return true; } - public String getCurrentFilterString() { - return mCurrentFilterString; - } - @Override protected int getThemeStyle() { return R.style.SwapTheme_StartSwap; @@ -253,4 +207,85 @@ public class SelectAppsFragment extends ThemeableListFragment protected int getHeaderLayout() { return R.layout.swap_create_header; } + + private static class AppListAdapter extends CursorAdapter { + + @Nullable + private LayoutInflater inflater; + + @Nullable + private Drawable defaultAppIcon; + + @NonNull + private final ListView listView; + + public AppListAdapter(@NonNull ListView listView, @NonNull Context context, @Nullable Cursor c) { + super(context, c, FLAG_REGISTER_CONTENT_OBSERVER); + this.listView = listView; + } + + @NonNull + private LayoutInflater getInflater(Context context) { + if (inflater == null) { + Context themedContext = new ContextThemeWrapper(context, R.style.SwapTheme_AppList_ListItem); + inflater = (LayoutInflater)themedContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + return inflater; + } + + @NonNull + private Drawable getDefaultAppIcon(Context context) { + if (defaultAppIcon == null) { + defaultAppIcon = context.getResources().getDrawable(android.R.drawable.sym_def_app_icon); + } + return defaultAppIcon; + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + View view = getInflater(context).inflate(R.layout.select_local_apps_list_item, null); + bindView(view, context, cursor); + return view; + } + + @Override + public void bindView(final View view, final Context context, final Cursor cursor) { + + TextView packageView = (TextView)view.findViewById(R.id.package_name); + TextView labelView = (TextView)view.findViewById(R.id.application_label); + ImageView iconView = (ImageView)view.findViewById(android.R.id.icon); + + String packageName = cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID)); + String appLabel = cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.APPLICATION_LABEL)); + + Drawable icon; + try { + icon = context.getPackageManager().getApplicationIcon(packageName); + } catch (PackageManager.NameNotFoundException e) { + icon = getDefaultAppIcon(context); + } + + packageView.setText(packageName); + labelView.setText(appLabel); + iconView.setImageDrawable(icon); + + // Since v11, the Android SDK provided the ability to show selected list items + // by highlighting their background. Prior to this, we need to handle this ourselves + // by adding a checkbox which can toggle selected items. + View checkBoxView = view.findViewById(R.id.checkbox); + if (checkBoxView != null) { + CheckBox checkBox = (CheckBox)checkBoxView; + checkBox.setOnCheckedChangeListener(null); + checkBox.setChecked(listView.isItemChecked(cursor.getPosition())); + final int position = cursor.getPosition(); + checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + listView.setItemChecked(position, isChecked); + } + }); + } + } + } + } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrFragment.java b/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrFragment.java index 9453af6ad..a71db32eb 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrFragment.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrFragment.java @@ -1,6 +1,5 @@ package org.fdroid.fdroid.views.swap; -import android.annotation.TargetApi; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; @@ -23,6 +22,8 @@ import android.widget.TextView; import android.widget.Toast; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; import org.fdroid.fdroid.FDroid; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.Preferences; @@ -32,12 +33,16 @@ import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.net.WifiStateChangeService; +import java.net.URI; +import java.util.List; import java.util.Locale; public class WifiQrFragment extends Fragment { private static final int CONNECT_TO_SWAP = 1; + private static final String TAG = "org.fdroid.fdroid.views.swap.WifiQrFragment"; + private BroadcastReceiver onWifiChange = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent i) { @@ -105,7 +110,6 @@ public class WifiQrFragment extends Fragment { new IntentFilter(WifiStateChangeService.BROADCAST)); } - @TargetApi(14) private void setUIFromWifi() { if (TextUtils.isEmpty(FDroidApp.repo.address)) @@ -132,19 +136,26 @@ public class WifiQrFragment extends Fragment { } qrUriString += sharingUri.getPath().toUpperCase(Locale.ENGLISH); boolean first = true; - for (String parameterName : sharingUri.getQueryParameterNames()) { - if (!parameterName.equals("ssid")) { + + // Andorid provides an API for getting the query parameters and iterating over them: + // Uri.getQueryParameterNames() + // But it is only available on later Android versions. As such we URLEncodedUtils instead. + List parameters = URLEncodedUtils.parse(URI.create(sharingUri.toString()), "UTF-8"); + for (NameValuePair parameter : parameters) { + if (!parameter.getName().equals("ssid")) { if (first) { qrUriString += "?"; first = false; } else { qrUriString += "&"; } - qrUriString += parameterName.toUpperCase(Locale.ENGLISH) + "=" + - sharingUri.getQueryParameter(parameterName).toUpperCase(Locale.ENGLISH); + qrUriString += parameter.getName().toUpperCase(Locale.ENGLISH) + "=" + + parameter.getValue().toUpperCase(Locale.ENGLISH); } } + Log.i(TAG, "Encoded swap URI in QR Code: " + qrUriString); + // zxing requires >= 8 // TODO: What about 7? I don't feel comfortable bumping the min version for this... // I would suggest show some alternate info, with directions for how to add a new repository manually.