package org.fdroid.fdroid.views.swap;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.widget.SearchView;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.data.InstalledAppProvider;
import org.fdroid.fdroid.data.Schema.InstalledAppTable;
import org.fdroid.fdroid.localrepo.SwapService;
public class SelectAppsView extends ListView implements
SwapWorkflowActivity.InnerView,
LoaderManager.LoaderCallbacks<Cursor>,
SearchView.OnQueryTextListener {
public SelectAppsView(Context context) {
super(context);
}
public SelectAppsView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SelectAppsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(21)
public SelectAppsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
private SwapWorkflowActivity getActivity() {
return (SwapWorkflowActivity) getContext();
}
private SwapService getState() {
return getActivity().getState();
}
private static final int LOADER_INSTALLED_APPS = 253341534;
private AppListAdapter adapter;
private String currentFilterString;
@Override
protected void onFinishInflate() {
super.onFinishInflate();
adapter = new AppListAdapter(this, getContext(),
getContext().getContentResolver().query(InstalledAppProvider.getContentUri(), InstalledAppTable.Cols.ALL, null, null, null));
setAdapter(adapter);
setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
// either reconnect with an existing loader or start a new one
getActivity().getSupportLoaderManager().initLoader(LOADER_INSTALLED_APPS, null, this);
setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
toggleAppSelected(position);
}
});
}
@Override
public boolean buildMenu(Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.swap_next_search, menu);
MenuItem nextMenuItem = menu.findItem(R.id.action_next);
int flags = MenuItemCompat.SHOW_AS_ACTION_ALWAYS | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT;
MenuItemCompat.setShowAsAction(nextMenuItem, flags);
nextMenuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
getActivity().onAppsSelected();
return true;
}
});
SearchView searchView = new SearchView(getActivity());
MenuItem searchMenuItem = menu.findItem(R.id.action_search);
MenuItemCompat.setActionView(searchMenuItem, searchView);
MenuItemCompat.setShowAsAction(searchMenuItem, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
searchView.setOnQueryTextListener(this);
return true;
}
@Override
public int getStep() {
return SwapService.STEP_SELECT_APPS;
}
@Override
public int getPreviousStep() {
// TODO: The STEP_JOIN_WIFI step isn't shown first, need to make it so that it is, or so that this doesn't go back there.
return getState().isConnectingWithPeer() ? SwapService.STEP_INTRO : SwapService.STEP_JOIN_WIFI;
}
@ColorRes
public int getToolbarColour() {
return R.color.swap_bright_blue;
}
@Override
public String getToolbarTitle() {
return getResources().getString(R.string.swap_choose_apps);
}
private void toggleAppSelected(int position) {
Cursor c = (Cursor) adapter.getItem(position);
String packageName = c.getString(c.getColumnIndex(InstalledAppTable.Cols.PACKAGE_NAME));
if (getState().hasSelectedPackage(packageName)) {
getState().deselectPackage(packageName);
adapter.updateCheckedIndicatorView(position, false);
} else {
getState().selectPackage(packageName);
adapter.updateCheckedIndicatorView(position, true);
}
}
@Override
public CursorLoader onCreateLoader(int id, Bundle args) {
Uri uri;
if (TextUtils.isEmpty(currentFilterString)) {
uri = InstalledAppProvider.getContentUri();
} else {
uri = InstalledAppProvider.getSearchUri(currentFilterString);
}
return new CursorLoader(
getActivity(),
uri,
InstalledAppTable.Cols.ALL,
null,
null,
InstalledAppTable.Cols.APPLICATION_LABEL);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
adapter.swapCursor(cursor);
for (int i = 0; i < getCount(); i++) {
Cursor c = (Cursor) getItemAtPosition(i);
String packageName = c.getString(c.getColumnIndex(InstalledAppTable.Cols.PACKAGE_NAME));
getState().ensureFDroidSelected();
for (String selected : getState().getAppsToSwap()) {
if (TextUtils.equals(packageName, selected)) {
setItemChecked(i, true);
}
}
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
adapter.swapCursor(null);
}
@Override
public boolean onQueryTextChange(String newText) {
String newFilter = !TextUtils.isEmpty(newText) ? newText : null;
if (currentFilterString == null && newFilter == null) {
return true;
}
if (currentFilterString != null && currentFilterString.equals(newFilter)) {
return true;
}
currentFilterString = newFilter;
getActivity().getSupportLoaderManager().restartLoader(LOADER_INSTALLED_APPS, null, this);
return true;
}
@Override
public boolean onQueryTextSubmit(String query) {
// this is not needed since we respond to every change in text
return true;
}
private class AppListAdapter extends CursorAdapter {
@Nullable
private LayoutInflater inflater;
@Nullable
private Drawable defaultAppIcon;
@NonNull
private final ListView listView;
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;
}
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, parent, false);
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(InstalledAppTable.Cols.PACKAGE_NAME));
String appLabel = cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.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);
final int listPosition = cursor.getPosition();
// 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(listPosition));
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
listView.setItemChecked(listPosition, isChecked);
toggleAppSelected(listPosition);
}
});
}
updateCheckedIndicatorView(view, listView.isItemChecked(listPosition));
}
public void updateCheckedIndicatorView(int position, boolean checked) {
final int firstListItemPosition = listView.getFirstVisiblePosition();
final int lastListItemPosition = firstListItemPosition + listView.getChildCount() - 1;
if (position >= firstListItemPosition && position <= lastListItemPosition) {
final int childIndex = position - firstListItemPosition;
updateCheckedIndicatorView(listView.getChildAt(childIndex), checked);
}
}
private void updateCheckedIndicatorView(View view, boolean checked) {
ImageView imageView = (ImageView) view.findViewById(R.id.checked);
if (imageView != null) {
int resource;
int colour;
if (checked) {
resource = R.drawable.ic_check_circle_white;
colour = getResources().getColor(R.color.swap_bright_blue);
} else {
resource = R.drawable.ic_add_circle_outline_white;
colour = 0xFFD0D0D4;
}
imageView.setImageDrawable(getResources().getDrawable(resource));
imageView.setColorFilter(colour, PorterDuff.Mode.MULTIPLY);
}
}
}
}