Implement app filter search bar and sort

This commit is contained in:
emanuele-f 2021-02-14 15:45:51 +01:00
parent 0abb670099
commit 452683f0b8
8 changed files with 183 additions and 54 deletions

View File

@ -32,22 +32,24 @@ import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class AppAdapter extends RecyclerView.Adapter<AppAdapter.AppViewHolder> {
private LayoutInflater layoutInflater;
private List<AppDescriptor> listStorage;
private final LayoutInflater mLayoutInflater;
private View.OnClickListener mListener;
private List<AppDescriptor> listStorage;
AppAdapter(Context context, List<AppDescriptor> customizedListView, final View.OnClickListener listener) {
layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
AppAdapter(Context context, List<AppDescriptor> customizedListView) {
mLayoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
listStorage = customizedListView;
mListener = listener;
mListener = null;
}
@NonNull
@Override
public AppAdapter.AppViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.installed_app_list, parent, false);
View view = mLayoutInflater.inflate(R.layout.installed_app_list, parent, false);
AppViewHolder recyclerViewHolder = new AppViewHolder(view);
view.setOnClickListener(mListener);
if(mListener != null)
view.setOnClickListener(mListener);
return(recyclerViewHolder);
}
@ -77,4 +79,13 @@ public class AppAdapter extends RecyclerView.Adapter<AppAdapter.AppViewHolder> {
packageInListView= view.findViewById(R.id.app_package);
}
}
public void setApps(List<AppDescriptor> apps) {
listStorage = apps;
notifyDataSetChanged();
}
public void setOnClickListener(final View.OnClickListener listener) {
mListener = listener;
}
}

View File

@ -21,12 +21,12 @@ package com.emanuelef.remote_capture;
import android.graphics.drawable.Drawable;
class AppDescriptor {
private String name;
private Drawable icon;
private String package_name;
private int uid;
private boolean is_system;
class AppDescriptor implements Comparable<AppDescriptor> {
final private String name;
final private Drawable icon;
final private String package_name;
final private int uid;
final private boolean is_system;
AppDescriptor(String name, Drawable icon, String package_name, int uid, boolean is_system) {
this.name = name;
@ -53,4 +53,14 @@ class AppDescriptor {
}
boolean isSystem() { return is_system; }
@Override
public int compareTo(AppDescriptor o) {
int rv = getName().toLowerCase().compareTo(o.getName().toLowerCase());
if(rv == 0)
rv = getPackageName().compareTo(o.getPackageName());
return rv;
}
}

View File

@ -20,41 +20,105 @@
package com.emanuelef.remote_capture;
import android.content.Context;
import android.view.View;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
// TODO add searchbar
// https://stackoverflow.com/questions/31085086/how-to-implement-floating-searchwidget-android
class AppsView extends EmptyRecyclerView implements SearchView.OnQueryTextListener, Filterable {
private List<AppDescriptor> mInstalledApps;
private AppAdapter mAdapter;
class AppsView extends RecyclerView {
List<AppDescriptor> mInstalledApps;
public AppsView(@NonNull Context context) {
super(context);
initialize(context);
}
public AppsView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initialize(context);
}
public AppsView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context);
}
private void initialize(Context context) {
mInstalledApps = null;
setLayoutManager(new LinearLayoutManager(context));
setHasFixedSize(true);
}
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
String charString = constraint.toString().toLowerCase();
List<AppDescriptor> appsFiltered;
if(charString.isEmpty())
appsFiltered = mInstalledApps;
else {
appsFiltered = new ArrayList<>();
for(AppDescriptor app : mInstalledApps) {
if(app.getPackageName().toLowerCase().contains(charString)
|| app.getName().toLowerCase().contains(charString)) {
appsFiltered.add(app);
}
}
}
FilterResults filterResults = new FilterResults();
filterResults.values = appsFiltered;
return filterResults;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
List<AppDescriptor> appsFiltered = (List<AppDescriptor>) results.values;
mAdapter.setApps(appsFiltered);
}
};
}
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
getFilter().filter(newText);
return true;
}
public interface OnSelectedAppListener {
void onSelectedApp(AppDescriptor app);
}
public AppsView(Context context, List<AppDescriptor> installedApps) {
super(context);
public void setApps(List<AppDescriptor> installedApps) {
mInstalledApps = installedApps;
setLayoutManager(new LinearLayoutManager(context));
setHasFixedSize(true);
mAdapter = new AppAdapter(getContext(), mInstalledApps);
setAdapter(mAdapter);
}
public void setSelectedAppListener(final OnSelectedAppListener listener) {
AppAdapter installedAppAdapter = new AppAdapter(getContext(), mInstalledApps, new OnClickListener() {
@Override
public void onClick(View view) {
int itemPosition = getChildLayoutPosition(view);
AppDescriptor app = mInstalledApps.get(itemPosition);
listener.onSelectedApp(app);
}
mAdapter.setOnClickListener(view -> {
int itemPosition = getChildLayoutPosition(view);
AppDescriptor app = mInstalledApps.get(itemPosition);
listener.onSelectedApp(app);
});
setAdapter(installedAppAdapter);
}
}

View File

@ -76,11 +76,13 @@ class ViewHolder extends RecyclerView.ViewHolder {
public class ConnectionsAdapter extends RecyclerView.Adapter<ViewHolder> {
private static final String TAG = "ConnectionsAdapter";
private final MainActivity mActivity;
private final LayoutInflater mLayoutInflater;
private final Drawable mUnknownIcon;
private View.OnClickListener mListener;
ConnectionsAdapter(MainActivity context) {
mActivity = context;
mLayoutInflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mUnknownIcon = ContextCompat.getDrawable(mActivity, android.R.drawable.ic_menu_help);
mListener = null;
}
@ -95,10 +97,7 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ViewHolder> {
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
assert inflater != null;
View view = inflater.inflate(R.layout.connection_item, parent, false);
View view = mLayoutInflater.inflate(R.layout.connection_item, parent, false);
if(mListener != null)
view.setOnClickListener(mListener);

View File

@ -32,6 +32,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
@ -48,6 +49,8 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
@ -465,22 +468,21 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
return;
}
// Filter non-system apps
List<AppDescriptor> user_apps = new ArrayList<>();
for(int i=0; i<mInstalledApps.size(); i++) {
AppDescriptor app = mInstalledApps.get(i);
if(!app.isSystem())
user_apps.add(app);
}
mOpenAppsWhenDone = false;
AppsView apps = new AppsView(this, user_apps);
View dialogLayout = getLayoutInflater().inflate(R.layout.apps_selector, null);
SearchView searchView = dialogLayout.findViewById(R.id.apps_search);
AppsView apps = (AppsView) dialogLayout.findViewById(R.id.apps_list);
TextView emptyText = dialogLayout.findViewById(R.id.no_apps);
apps.setApps(mInstalledApps);
apps.setEmptyView(emptyText);
searchView.setOnQueryTextListener(apps);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.app_filter);
builder.setView(apps);
builder.setView(dialogLayout);
final AlertDialog alert = builder.create();
apps.setSelectedAppListener(app -> {

View File

@ -95,10 +95,6 @@ public class Utils {
List<AppDescriptor> apps = new ArrayList<>();
List<PackageInfo> packs = pm.getInstalledPackages(0);
// Add the "No Filter" app
Drawable icon = ContextCompat.getDrawable(context, android.R.color.transparent);
apps.add(new AppDescriptor("", icon, context.getResources().getString(R.string.no_filter), -1, false));
Log.d("APPS", "num apps (system+user): " + packs.size());
long tstart = now();
@ -112,7 +108,7 @@ public class Utils {
String appName = p.applicationInfo.loadLabel(pm).toString();
// NOTE: this call is expensive
icon = p.applicationInfo.loadIcon(pm);
Drawable icon = p.applicationInfo.loadIcon(pm);
int uid = p.applicationInfo.uid;
apps.add(new AppDescriptor(appName, icon, package_name, uid, is_system));
@ -121,6 +117,12 @@ public class Utils {
}
}
Collections.sort(apps);
// Add the "No Filter" app
Drawable icon = ContextCompat.getDrawable(context, android.R.color.transparent);
apps.add(0, new AppDescriptor("", icon, context.getResources().getString(R.string.no_filter), -1, false));
Log.d("APPS", packs.size() + " apps loaded in " + (now() - tstart) +" seconds");
return apps;
}

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.appcompat.widget.SearchView
android:id="@+id/apps_search"
android:layout_marginTop="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:iconifiedByDefault="false"
app:queryHint="@string/search_apps"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.emanuelef.remote_capture.AppsView
android:id="@+id/apps_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@+id/no_apps"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:layout_marginTop="20dp"
android:layout_marginBottom="40dp"
android:textStyle="italic"
android:textSize="15sp"
android:text="@string/no_apps">
</TextView>
</RelativeLayout>
</LinearLayout>

View File

@ -79,5 +79,8 @@
<string name="packets_sent">Packets Sent</string>
<string name="packets_rcvd">Packets Received</string>
<string name="dns_queries">DNS Queries</string>
<string name="search_apps">Search Apps</string>
<string name="no_apps">No apps available</string>
<string name="show_system_apps">Show system apps</string>
</resources>