Decouple apps loading from MainActivity

This commit is contained in:
emanuele-f 2021-02-27 17:13:47 +01:00
parent 897a464809
commit 62b4c9e1d8
11 changed files with 333 additions and 215 deletions

View File

@ -0,0 +1,184 @@
package com.emanuelef.remote_capture;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.AsyncTaskLoader;
import androidx.loader.content.Loader;
import com.emanuelef.remote_capture.interfaces.AppsLoadListener;
import com.emanuelef.remote_capture.model.AppDescriptor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AppsLoader implements LoaderManager.LoaderCallbacks<HashMap<Integer, AppDescriptor>> {
private static final String TAG = "AppsLoader";
private static final int OPERATION_LOAD_APPS_INFO = 23;
private static final int OPERATION_LOAD_APPS_ICONS = 24;
private AppsLoadListener mListener;
private final AppCompatActivity mContext;
private final Drawable mVirtualAppIcon;
public AppsLoader(AppCompatActivity context) {
mContext = context;
mVirtualAppIcon = ContextCompat.getDrawable(mContext, android.R.drawable.sym_def_app_icon);
}
public AppsLoader setAppsLoadListener(AppsLoadListener listener) {
mListener = listener;
return this;
}
private HashMap<Integer, AppDescriptor> asyncLoadAppsInfo() {
final PackageManager pm = mContext.getPackageManager();
HashMap<Integer, AppDescriptor> apps = new HashMap<>();
Log.d(TAG, "Loading APPs...");
List<PackageInfo> packs = pm.getInstalledPackages(0);
String app_package = mContext.getApplicationContext().getPackageName();
Log.d(TAG, "num apps (system+user): " + packs.size());
long tstart = Utils.now();
// NOTE: these virtual apps cannot be used as a permanent filter (via addAllowedApplication)
// as they miss a valid package name
apps.put(0, new AppDescriptor("Root",
mVirtualAppIcon,"root", 0, true, true));
apps.put(1000, new AppDescriptor("Android",
mVirtualAppIcon,"android", 1000, true, true));
apps.put(1051, new AppDescriptor("netd",
mVirtualAppIcon,"netd", 1051, true, true));
// NOTE: a single uid can correspond to multiple apps, only take the first one
for (int i = 0; i < packs.size(); i++) {
PackageInfo p = packs.get(i);
boolean is_system = (p.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
String package_name = p.applicationInfo.packageName;
if(!apps.containsKey(p.applicationInfo.uid) && !package_name.equals(app_package)) {
String appName = p.applicationInfo.loadLabel(pm).toString();
int uid = p.applicationInfo.uid;
apps.put(uid, new AppDescriptor(appName, null, package_name, uid, is_system, false));
//Log.d(TAG, appName + " - " + package_name + " [" + uid + "]" + (is_system ? " - SYS" : " - USR"));
}
}
Log.d(TAG, packs.size() + " apps loaded in " + (Utils.now() - tstart) +" seconds");
return apps;
}
private void asyncLoadAppsIcons(HashMap<Integer, AppDescriptor> apps) {
final PackageManager pm = mContext.getPackageManager();
long tstart = Utils.now();
Log.d(TAG, "Loading " + apps.size() + " app icons...");
for (Map.Entry<Integer, AppDescriptor> pair : apps.entrySet()) {
AppDescriptor app = pair.getValue();
PackageInfo p;
if(app.getIcon() != null)
continue;
try {
p = pm.getPackageInfo(app.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "could no retrieve package: " + app.getPackageName());
continue;
}
// NOTE: this call is expensive
Drawable icon = p.applicationInfo.loadIcon(pm);
app.setIcon(icon);
}
Log.d(TAG, apps.size() + " apps icons loaded in " + (Utils.now() - tstart) +" seconds");
}
@NonNull
@Override
public Loader<HashMap<Integer, AppDescriptor>> onCreateLoader(int opid, @Nullable Bundle args) {
return new AsyncTaskLoader<HashMap<Integer, AppDescriptor>>(mContext) {
@NonNull
@Override
public HashMap<Integer, AppDescriptor> loadInBackground() {
if(opid == OPERATION_LOAD_APPS_INFO)
return asyncLoadAppsInfo();
else if (opid == OPERATION_LOAD_APPS_ICONS) {
if(args == null) {
Log.e(TAG, "Bad bundle");
return null;
}
HashMap<Integer, AppDescriptor> apps = (HashMap<Integer, AppDescriptor>) args.getSerializable("apps");
if(apps == null) {
Log.e(TAG, "Bad apps");
return null;
}
asyncLoadAppsIcons(apps);
return apps;
}
Log.e(TAG, "unknown loader op: " + opid);
return null;
}
};
}
@Override
public void onLoadFinished(@NonNull Loader<HashMap<Integer, AppDescriptor>> loader, HashMap<Integer, AppDescriptor> data) {
boolean load_finished = (loader.getId() == OPERATION_LOAD_APPS_ICONS);
if(mListener != null) {
if(load_finished)
mListener.onAppsIconsLoaded(data);
else
mListener.onAppsInfoLoaded(data);
}
if(!load_finished)
runLoader(OPERATION_LOAD_APPS_ICONS, data);
}
@Override
public void onLoaderReset(@NonNull Loader<HashMap<Integer, AppDescriptor>> loader) {}
private void runLoader(int opid, HashMap<Integer, AppDescriptor> data) {
LoaderManager lm = LoaderManager.getInstance(mContext);
Loader<HashMap<Integer, AppDescriptor>> loader = lm.getLoader(opid);
Bundle bundle = new Bundle();
bundle.putSerializable("apps", data);
Log.d(TAG, "Existing loader " + opid + "? " + (loader != null));
if(loader==null)
loader = lm.initLoader(opid, bundle, this);
else
loader = lm.restartLoader(opid, bundle, this);
loader.forceLoad();
}
public void loadAllApps() {
// will run OPERATION_LOAD_APPS_ICONS when finished
runLoader(OPERATION_LOAD_APPS_INFO, null);
}
}

View File

@ -21,10 +21,6 @@ package com.emanuelef.remote_capture;
import android.app.Service;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
@ -35,18 +31,14 @@ import android.os.Build;
import android.util.Log;
import android.widget.Toast;
import com.emanuelef.remote_capture.model.AppDescriptor;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@ -91,43 +83,6 @@ public class Utils {
return String.format("> %d h", seconds / 3600);
}
public static List<AppDescriptor> getInstalledApps(Context context) {
PackageManager pm = context.getPackageManager();
List<AppDescriptor> apps = new ArrayList<>();
List<PackageInfo> packs = pm.getInstalledPackages(0);
String app_package = context.getApplicationContext().getPackageName();
HashSet<Integer> uids = new HashSet<>();
Log.d("APPS", "num apps (system+user): " + packs.size());
long tstart = now();
// NOTE: a single uid can correspond to multiple apps, only take the first one
for (int i = 0; i < packs.size(); i++) {
PackageInfo p = packs.get(i);
boolean is_system = (p.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
String package_name = p.applicationInfo.packageName;
if(!uids.contains(p.applicationInfo.uid) && !package_name.equals(app_package)) {
String appName = p.applicationInfo.loadLabel(pm).toString();
// NOTE: this call is expensive
Drawable icon = p.applicationInfo.loadIcon(pm);
int uid = p.applicationInfo.uid;
apps.add(new AppDescriptor(appName, icon, package_name, uid, is_system));
uids.add(uid);
Log.d("APPS", appName + " - " + package_name + " [" + uid + "]" + (is_system ? " - SYS" : " - USR"));
}
}
Collections.sort(apps);
Log.d("APPS", packs.size() + " apps loaded in " + (now() - tstart) +" seconds");
return apps;
}
public static String proto2str(int proto) {
switch(proto) {
case 6: return "TCP";

View File

@ -29,16 +29,12 @@ import android.net.Uri;
import android.net.VpnService;
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;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.AsyncTaskLoader;
import androidx.loader.content.Loader;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager;
import androidx.viewpager2.adapter.FragmentStateAdapter;
@ -52,8 +48,10 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import com.emanuelef.remote_capture.AppsLoader;
import com.emanuelef.remote_capture.ConnectionsRegister;
import com.emanuelef.remote_capture.fragments.AppsFragment;
import com.emanuelef.remote_capture.interfaces.AppsLoadListener;
import com.emanuelef.remote_capture.model.AppDescriptor;
import com.emanuelef.remote_capture.model.AppState;
import com.emanuelef.remote_capture.interfaces.AppStateListener;
@ -68,13 +66,15 @@ import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import cat.ereza.customactivityoncrash.config.CaocConfig;
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<List<AppDescriptor>> {
public class MainActivity extends AppCompatActivity implements AppsLoadListener {
SharedPreferences mPrefs;
Menu mMenu;
MenuItem mMenuItemStats;
@ -91,12 +91,8 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
List<AppStateListener> mStateListeners;
AppsListView.OnSelectedAppListener mTmpAppFilterListener;
AppDescriptor mNoFilterApp;
AppDescriptor mRootApp;
AppDescriptor mAndroidApp;
AppDescriptor mNetdApp;
private static final String TAG = "Main";
private static final String APPS_LOADER_TAG = "AppsLoader";
public static final int POS_STATUS = 0;
public static final int POS_APPS = 1;
@ -104,11 +100,6 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
private static final int TOTAL_COUNT = 3;
private static final int REQUEST_CODE_VPN = 2;
public static final int OPERATION_SEARCH_LOADER = 23;
private static final String virtual_root_package = "root";
private static final String virtual_android_package = "android";
private static final String virtual_netd_package = "netd";
public static final String TELEGRAM_GROUP_NAME = "PCAPdroid";
public static final String GITHUB_PROJECT_URL = "https://github.com/emanuele-f/PCAPdroid";
@ -144,7 +135,7 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
super.onCreate(savedInstanceState);
Drawable icon = ContextCompat.getDrawable(this, android.R.color.transparent);
mNoFilterApp = new AppDescriptor("", icon, this.getResources().getString(R.string.no_filter), -1, false);
mNoFilterApp = new AppDescriptor("", icon, this.getResources().getString(R.string.no_filter), -1, false, true);
mFilterApp = CaptureService.getAppFilter();
@ -158,20 +149,6 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
mTmpAppFilterListener = null;
mStateListeners = new ArrayList<>();
// NOTE: these virtual apps cannot be used as a permanent filter (via addAllowedApplication)
// as they miss a valid package name
mRootApp = new AppDescriptor("Root",
ContextCompat.getDrawable(this, android.R.drawable.sym_def_app_icon),
virtual_root_package, 0, true);
mAndroidApp = new AppDescriptor("Android",
ContextCompat.getDrawable(this, android.R.drawable.sym_def_app_icon),
virtual_android_package, 1000, true);
mNetdApp = new AppDescriptor("netd",
ContextCompat.getDrawable(this, android.R.drawable.sym_def_app_icon),
virtual_netd_package, 1051, true);
CaocConfig.Builder.create()
.errorDrawable(R.drawable.ic_app_crash)
.apply();
@ -200,7 +177,9 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
startLoadingApps();
(new AppsLoader(this))
.setAppsLoadListener(this)
.loadAllApps();
LocalBroadcastManager bcast_man = LocalBroadcastManager.getInstance(this);
@ -245,6 +224,38 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
initAppState();
}
@Override
public void onAppsInfoLoaded(Map<Integer, AppDescriptor> apps) {
// TODO optimize: show the apps even when icon not loaded
}
@Override
public void onAppsIconsLoaded(Map<Integer, AppDescriptor> apps) {
mInstalledApps = new ArrayList<>();
AppDescriptor filterApp = null;
for (Map.Entry<Integer, AppDescriptor> pair : apps.entrySet()) {
AppDescriptor app = pair.getValue();
if(!app.isVirtual()) {
mInstalledApps.add(app);
if (app.getPackageName().equals(mFilterApp))
filterApp = app;
}
}
Collections.sort(mInstalledApps);
if (filterApp != null) {
/* An filter is active, try to set the corresponding app image */
setSelectedApp(filterApp);
}
if (mOpenAppsWhenDone)
openAppSelector();
}
private void notifyAppState() {
for(AppStateListener listener: mStateListeners)
listener.appStateChanged(mState);
@ -407,91 +418,6 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
}
}
@NonNull
@Override
public Loader<List<AppDescriptor>> onCreateLoader(int id, @Nullable Bundle args) {
return new AsyncTaskLoader<List<AppDescriptor>>(this) {
@NonNull
@Override
public List<AppDescriptor> loadInBackground() {
Log.d(APPS_LOADER_TAG, "Loading APPs...");
return Utils.getInstalledApps(getContext());
}
};
}
@Override
public void onLoadFinished(@NonNull Loader<List<AppDescriptor>> loader, List<AppDescriptor> data) {
Log.d(APPS_LOADER_TAG, data.size() + " APPs loaded");
mInstalledApps = data;
if (mFilterApp != null) {
/* An filter is active, try to set the corresponding app image */
AppDescriptor app = findAppByPackage(mFilterApp);
setSelectedApp(app);
}
for(AppStateListener listener: mStateListeners)
listener.appsLoaded();
if (mOpenAppsWhenDone)
openAppSelector();
}
@Override
public void onLoaderReset(@NonNull Loader<List<AppDescriptor>> loader) {
}
public AppDescriptor findAppByUid(int uid) {
if((mInstalledApps == null) || (uid == -1))
return (null);
if(uid == 0) // root
return mRootApp;
else if(uid == 1000) // android system
return mAndroidApp;
else if(uid == 1051) // netd
return mNetdApp;
for (int i = 0; i < mInstalledApps.size(); i++) {
AppDescriptor app = mInstalledApps.get(i);
if (app.getUid() == uid) {
return (app);
}
}
return (null);
}
public AppDescriptor findAppByPackage(String package_name) {
if (mInstalledApps == null)
return (null);
if(package_name.equals(virtual_root_package))
return mRootApp;
if(package_name.equals(virtual_android_package))
return mAndroidApp;
else if(package_name.equals(virtual_netd_package))
return mNetdApp;
for (int i = 0; i < mInstalledApps.size(); i++) {
AppDescriptor app = mInstalledApps.get(i);
if (app.getPackageName().equals(package_name)) {
return (app);
}
}
return (null);
}
public boolean appsLoaded() {
return(mInstalledApps != null);
}
private void initAppState() {
boolean is_active = CaptureService.isServiceActive();
@ -540,20 +466,6 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
return(mState);
}
private void startLoadingApps() {
LoaderManager lm = LoaderManager.getInstance(this);
Loader<List<AppDescriptor>> loader = lm.getLoader(OPERATION_SEARCH_LOADER);
Log.d(APPS_LOADER_TAG, "Loader? " + (loader != null));
if(loader==null)
loader = lm.initLoader(OPERATION_SEARCH_LOADER, null, this);
else
loader = lm.restartLoader(OPERATION_SEARCH_LOADER, null, this);
loader.forceLoad();
}
public void setTmpAppFilterListener(AppsListView.OnSelectedAppListener listener) {
mTmpAppFilterListener = listener;
}
@ -563,7 +475,9 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
if((mFilterApp == null) || !canApplyTmpFilter())
return null;
return findAppByPackage(mFilterApp);
// TODO: fixme
return null;
//return findAppByPackage(mFilterApp);
}
public void setSelectedApp(AppDescriptor app) {
@ -577,9 +491,12 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
mFilterApp = app.getPackageName();
// clone the drawable to avoid a "zoom-in" effect when clicked
Drawable drawable = Objects.requireNonNull(app.getIcon().getConstantState()).newDrawable();
mMenuItemAppSel.setIcon(drawable);
mMenuItemAppSel.setTitle(R.string.remove_app_filter);
Drawable drawable = (app.getIcon() != null) ? Objects.requireNonNull(app.getIcon().getConstantState()).newDrawable() : null;
if(drawable != null) {
mMenuItemAppSel.setIcon(drawable);
mMenuItemAppSel.setTitle(R.string.remove_app_filter);
}
} else {
// no filter
mFilterApp = null;
@ -621,7 +538,7 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
View dialogLayout = getLayoutInflater().inflate(R.layout.apps_selector, null);
SearchView searchView = dialogLayout.findViewById(R.id.apps_search);
AppsListView apps = (AppsListView) dialogLayout.findViewById(R.id.apps_list);
AppsListView apps = dialogLayout.findViewById(R.id.apps_list);
TextView emptyText = dialogLayout.findViewById(R.id.no_apps);
apps.setApps(appsData);

View File

@ -40,6 +40,7 @@ import com.emanuelef.remote_capture.model.AppStats;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.ViewHolder> {
@ -49,6 +50,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
private final Drawable mUnknownIcon;
private View.OnClickListener mListener;
private List<AppStats> mStats;
private Map<Integer, AppDescriptor> mApps;
public static class ViewHolder extends RecyclerView.ViewHolder {
ImageView icon;
@ -63,13 +65,13 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
traffic = itemView.findViewById(R.id.traffic);
}
public void bindAppStats(MainActivity activity, AppStats stats, Drawable unknownIcon) {
public void bindAppStats(MainActivity activity, AppStats stats, Map<Integer, AppDescriptor> apps, Drawable unknownIcon) {
Drawable appIcon;
// NOTE: can be null
AppDescriptor app = activity.findAppByUid(stats.getUid());
AppDescriptor app = (apps != null) ? apps.get(stats.getUid()) : null;
appIcon = (app != null) ? Objects.requireNonNull(app.getIcon().getConstantState()).newDrawable() : unknownIcon;
appIcon = ((app != null) && (app.getIcon() != null)) ? Objects.requireNonNull(app.getIcon().getConstantState()).newDrawable() : unknownIcon;
icon.setImageDrawable(appIcon);
String info_txt = (app != null) ? app.getName() : Integer.toString(stats.getUid());
@ -114,7 +116,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
if(stats == null)
return;
holder.bindAppStats(mActivity, stats, mUnknownIcon);
holder.bindAppStats(mActivity, stats, mApps, mUnknownIcon);
}
@Override
@ -133,10 +135,10 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
}
public void setStats(List<AppStats> stats) {
if(mActivity.appsLoaded()) {
if(mApps != null) {
Collections.sort(stats, (o1, o2) -> {
AppDescriptor a1 = mActivity.findAppByUid(o1.getUid());
AppDescriptor a2 = mActivity.findAppByUid(o2.getUid());
AppDescriptor a1 = mApps.get(o1.getUid());
AppDescriptor a2 = mApps.get(o2.getUid());
if((a1 == null) && (a2 == null))
return 0;
@ -154,4 +156,9 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
mStats = stats;
notifyDataSetChanged();
}
public void setApps(Map<Integer, AppDescriptor> apps) {
mApps = apps;
notifyDataSetChanged();
}
}

View File

@ -21,7 +21,6 @@ package com.emanuelef.remote_capture.adapters;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -40,6 +39,7 @@ import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.Utils;
import com.emanuelef.remote_capture.activities.MainActivity;
import java.util.Map;
import java.util.Objects;
public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.ViewHolder> {
@ -49,6 +49,7 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
private final Drawable mUnknownIcon;
private int mItemCount;
private View.OnClickListener mListener;
private Map<Integer, AppDescriptor> mApps;
int mUidFilter;
public static class ViewHolder extends RecyclerView.ViewHolder {
@ -68,11 +69,11 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
statusInd = itemView.findViewById(R.id.status_ind);
}
public void bindConn(MainActivity activity, ConnectionDescriptor conn, Drawable unknownIcon) {
AppDescriptor app = activity.findAppByUid(conn.uid);
public void bindConn(MainActivity activity, ConnectionDescriptor conn, Map<Integer, AppDescriptor> apps, Drawable unknownIcon) {
AppDescriptor app = (apps != null) ? apps.get(conn.uid) : null;
Drawable appIcon;
appIcon = (app != null) ? Objects.requireNonNull(app.getIcon().getConstantState()).newDrawable() : unknownIcon;
appIcon = ((app != null) && (app.getIcon() != null)) ? Objects.requireNonNull(app.getIcon().getConstantState()).newDrawable() : unknownIcon;
icon.setImageDrawable(appIcon);
if(conn.info.length() > 0)
@ -127,7 +128,7 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
if(conn == null)
return;
holder.bindConn(mActivity, conn, mUnknownIcon);
holder.bindConn(mActivity, conn, mApps, mUnknownIcon);
}
@Override
@ -160,4 +161,8 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
public int getUidFilter() {
return mUidFilter;
}
public void setApps(Map<Integer, AppDescriptor> apps) {
mApps = apps;
}
}

View File

@ -14,19 +14,23 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.emanuelef.remote_capture.AppsLoader;
import com.emanuelef.remote_capture.CaptureService;
import com.emanuelef.remote_capture.ConnectionsRegister;
import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.activities.MainActivity;
import com.emanuelef.remote_capture.adapters.AppsStatsAdapter;
import com.emanuelef.remote_capture.interfaces.AppStateListener;
import com.emanuelef.remote_capture.interfaces.AppsLoadListener;
import com.emanuelef.remote_capture.interfaces.ConnectionsListener;
import com.emanuelef.remote_capture.model.AppDescriptor;
import com.emanuelef.remote_capture.model.AppState;
import com.emanuelef.remote_capture.model.AppStats;
import com.emanuelef.remote_capture.views.EmptyRecyclerView;
public class AppsFragment extends Fragment implements AppStateListener, ConnectionsListener {
import java.util.Map;
public class AppsFragment extends Fragment implements AppStateListener, ConnectionsListener, AppsLoadListener {
private EmptyRecyclerView mRecyclerView;
private AppsStatsAdapter mAdapter;
private static final String TAG = "AppsFragment";
@ -34,6 +38,7 @@ public class AppsFragment extends Fragment implements AppStateListener, Connecti
private Handler mHandler;
private boolean mRefreshApps;
private boolean listenerSet;
private Map<Integer, AppDescriptor> mApps;
@Override
public void onAttach(@NonNull Context context) {
@ -81,7 +86,7 @@ public class AppsFragment extends Fragment implements AppStateListener, Connecti
AppStats item = mAdapter.getItem(pos);
if(item != null) {
AppDescriptor app = mActivity.findAppByUid(item.getUid());
AppDescriptor app = mApps.get(item.getUid());
if(app != null) {
mActivity.setSelectedApp(app);
@ -92,6 +97,10 @@ public class AppsFragment extends Fragment implements AppStateListener, Connecti
registerListener();
mActivity.addAppStateListener(this);
(new AppsLoader(mActivity))
.setAppsLoadListener(this)
.loadAllApps();
}
private void registerListener() {
@ -123,12 +132,6 @@ public class AppsFragment extends Fragment implements AppStateListener, Connecti
}
}
@Override
public void appsLoaded() {
// refresh the icons
mAdapter.notifyDataSetChanged();
}
// NOTE: do not use synchronized as it could cause a deadlock with the ConnectionsRegister lock
private void doRefreshApps() {
mRefreshApps = false;
@ -168,4 +171,18 @@ public class AppsFragment extends Fragment implements AppStateListener, Connecti
public void connectionsUpdated(int[] positions) {
refreshAppsAsync();
}
@Override
public void onAppsInfoLoaded(Map<Integer, AppDescriptor> apps) {
// refresh the names
mApps = apps;
mAdapter.setApps(apps);
}
@Override
public void onAppsIconsLoaded(Map<Integer, AppDescriptor> apps) {
// refresh the icons
mApps = apps;
mAdapter.setApps(apps);
}
}

View File

@ -36,6 +36,8 @@ import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.emanuelef.remote_capture.AppsLoader;
import com.emanuelef.remote_capture.interfaces.AppsLoadListener;
import com.emanuelef.remote_capture.model.AppDescriptor;
import com.emanuelef.remote_capture.model.AppState;
import com.emanuelef.remote_capture.CaptureService;
@ -50,7 +52,9 @@ import com.emanuelef.remote_capture.activities.MainActivity;
import com.emanuelef.remote_capture.interfaces.AppStateListener;
import com.emanuelef.remote_capture.interfaces.ConnectionsListener;
public class ConnectionsFragment extends Fragment implements AppStateListener, ConnectionsListener, AppsListView.OnSelectedAppListener {
import java.util.Map;
public class ConnectionsFragment extends Fragment implements AppStateListener, ConnectionsListener, AppsListView.OnSelectedAppListener, AppsLoadListener {
private static final String TAG = "ConnectionsFragment";
private MainActivity mActivity;
private Handler mHandler;
@ -59,6 +63,7 @@ public class ConnectionsFragment extends Fragment implements AppStateListener, C
private EmptyRecyclerView mRecyclerView;
private boolean autoScroll;
private boolean listenerSet;
private Map<Integer, AppDescriptor> mApps;
@Override
public void onAttach(@NonNull Context context) {
@ -132,7 +137,7 @@ public class ConnectionsFragment extends Fragment implements AppStateListener, C
if(item != null) {
Intent intent = new Intent(getContext(), ConnectionDetailsActivity.class);
AppDescriptor app = mActivity.findAppByUid(item.uid);
AppDescriptor app = (mApps != null) ? mApps.get(item.uid) : null;
String app_name = null;
if(app != null)
@ -164,6 +169,10 @@ public class ConnectionsFragment extends Fragment implements AppStateListener, C
mActivity.addAppStateListener(this);
mActivity.setTmpAppFilterListener(this);
(new AppsLoader(mActivity))
.setAppsLoadListener(this)
.loadAllApps();
}
private void recheckScroll() {
@ -217,13 +226,6 @@ public class ConnectionsFragment extends Fragment implements AppStateListener, C
}
}
@Override
public void appsLoaded() {
// Refresh the adapter to load the apps icons
// Don't use notifyDataSetChanged as connectionsAdded/connectionsRemoved may be pending
mAdapter.notifyItemRangeChanged(0, mAdapter.getItemCount());
}
// This performs an unoptimized adapter refresh
private void refreshUidConnections() {
ConnectionsRegister reg = CaptureService.getConnsRegister();
@ -324,4 +326,18 @@ public class ConnectionsFragment extends Fragment implements AppStateListener, C
registerListener();
}
}
@Override
public void onAppsInfoLoaded(Map<Integer, AppDescriptor> apps) {
mApps = apps;
}
@Override
public void onAppsIconsLoaded(Map<Integer, AppDescriptor> apps) {
// Refresh the adapter to load the apps icons
// Don't use notifyDataSetChanged as connectionsAdded/connectionsRemoved may be pending
mApps = apps;
mAdapter.setApps(apps);
mAdapter.notifyItemRangeChanged(0, mAdapter.getItemCount());
}
}

View File

@ -191,7 +191,4 @@ public class StatusFragment extends Fragment implements AppStateListener {
break;
}
}
@Override
public void appsLoaded() {}
}

View File

@ -23,5 +23,4 @@ import com.emanuelef.remote_capture.model.AppState;
public interface AppStateListener {
void appStateChanged(AppState state);
void appsLoaded();
}

View File

@ -0,0 +1,11 @@
package com.emanuelef.remote_capture.interfaces;
import com.emanuelef.remote_capture.model.AppDescriptor;
import java.util.Map;
public interface AppsLoadListener {
// uid -> AppDescriptor
void onAppsInfoLoaded(Map<Integer, AppDescriptor> apps);
void onAppsIconsLoaded(Map<Integer, AppDescriptor> apps);
}

View File

@ -21,28 +21,36 @@ package com.emanuelef.remote_capture.model;
import android.graphics.drawable.Drawable;
import androidx.annotation.Nullable;
import java.io.Serializable;
public class AppDescriptor implements Comparable<AppDescriptor>, Serializable {
private final String name;
private final Drawable icon;
private final String package_name;
private final int uid;
private final boolean is_system;
private final boolean is_virtual; // the app does not have a package name (e.g. uid 0 is android system)
private Drawable icon;
public AppDescriptor(String name, Drawable icon, String package_name, int uid, boolean is_system) {
public AppDescriptor(String name, Drawable icon, String package_name, int uid, boolean is_system, boolean is_virtual) {
this.name = name;
this.icon = icon;
this.package_name = package_name;
this.uid = uid;
this.is_system = is_system;
this.is_virtual = is_virtual;
}
public String getName() {
return name;
}
public Drawable getIcon() {
public void setIcon(Drawable _icon) {
icon = _icon;
}
public @Nullable Drawable getIcon() {
return icon;
}
@ -56,6 +64,8 @@ public class AppDescriptor implements Comparable<AppDescriptor>, Serializable {
public boolean isSystem() { return is_system; }
public boolean isVirtual() { return is_virtual; }
@Override
public int compareTo(AppDescriptor o) {
int rv = getName().toLowerCase().compareTo(o.getName().toLowerCase());