mirror of
https://github.com/emanuele-f/PCAPdroid.git
synced 2026-06-16 21:10:57 +08:00
Implement app filter search bar and sort
This commit is contained in:
parent
0abb670099
commit
452683f0b8
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 -> {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
38
app/src/main/res/layout/apps_selector.xml
Normal file
38
app/src/main/res/layout/apps_selector.xml
Normal 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>
|
||||
@ -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>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user