diff --git a/app/src/main/java/com/emanuelef/remote_capture/AppsLoader.java b/app/src/main/java/com/emanuelef/remote_capture/AppsLoader.java new file mode 100644 index 00000000..a0f8440c --- /dev/null +++ b/app/src/main/java/com/emanuelef/remote_capture/AppsLoader.java @@ -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> { + 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 asyncLoadAppsInfo() { + final PackageManager pm = mContext.getPackageManager(); + HashMap apps = new HashMap<>(); + + Log.d(TAG, "Loading APPs..."); + List 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 apps) { + final PackageManager pm = mContext.getPackageManager(); + long tstart = Utils.now(); + + Log.d(TAG, "Loading " + apps.size() + " app icons..."); + + for (Map.Entry 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> onCreateLoader(int opid, @Nullable Bundle args) { + return new AsyncTaskLoader>(mContext) { + @NonNull + @Override + public HashMap 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 apps = (HashMap) 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> loader, HashMap 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> loader) {} + + private void runLoader(int opid, HashMap data) { + LoaderManager lm = LoaderManager.getInstance(mContext); + Loader> 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); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/emanuelef/remote_capture/Utils.java b/app/src/main/java/com/emanuelef/remote_capture/Utils.java index d477365c..df0870ad 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/Utils.java +++ b/app/src/main/java/com/emanuelef/remote_capture/Utils.java @@ -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 getInstalledApps(Context context) { - PackageManager pm = context.getPackageManager(); - List apps = new ArrayList<>(); - List packs = pm.getInstalledPackages(0); - String app_package = context.getApplicationContext().getPackageName(); - HashSet 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"; diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java b/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java index 54e5d303..ea725e8f 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java +++ b/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java @@ -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> { +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 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 apps) { + // TODO optimize: show the apps even when icon not loaded + } + + @Override + public void onAppsIconsLoaded(Map apps) { + mInstalledApps = new ArrayList<>(); + AppDescriptor filterApp = null; + + for (Map.Entry 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> onCreateLoader(int id, @Nullable Bundle args) { - return new AsyncTaskLoader>(this) { - - @NonNull - @Override - public List loadInBackground() { - Log.d(APPS_LOADER_TAG, "Loading APPs..."); - return Utils.getInstalledApps(getContext()); - } - }; - } - - @Override - public void onLoadFinished(@NonNull Loader> loader, List 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> 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> 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); diff --git a/app/src/main/java/com/emanuelef/remote_capture/adapters/AppsStatsAdapter.java b/app/src/main/java/com/emanuelef/remote_capture/adapters/AppsStatsAdapter.java index 443783c9..257cf11c 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/adapters/AppsStatsAdapter.java +++ b/app/src/main/java/com/emanuelef/remote_capture/adapters/AppsStatsAdapter.java @@ -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 { @@ -49,6 +50,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter mStats; + private Map mApps; public static class ViewHolder extends RecyclerView.ViewHolder { ImageView icon; @@ -63,13 +65,13 @@ public class AppsStatsAdapter extends RecyclerView.Adapter 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 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 apps) { + mApps = apps; + notifyDataSetChanged(); + } } diff --git a/app/src/main/java/com/emanuelef/remote_capture/adapters/ConnectionsAdapter.java b/app/src/main/java/com/emanuelef/remote_capture/adapters/ConnectionsAdapter.java index 9f903933..f4030e68 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/adapters/ConnectionsAdapter.java +++ b/app/src/main/java/com/emanuelef/remote_capture/adapters/ConnectionsAdapter.java @@ -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 { @@ -49,6 +49,7 @@ public class ConnectionsAdapter extends RecyclerView.Adapter mApps; int mUidFilter; public static class ViewHolder extends RecyclerView.ViewHolder { @@ -68,11 +69,11 @@ public class ConnectionsAdapter extends RecyclerView.Adapter 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 apps) { + mApps = apps; + } } diff --git a/app/src/main/java/com/emanuelef/remote_capture/fragments/AppsFragment.java b/app/src/main/java/com/emanuelef/remote_capture/fragments/AppsFragment.java index 592ff795..328dbaf9 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/fragments/AppsFragment.java +++ b/app/src/main/java/com/emanuelef/remote_capture/fragments/AppsFragment.java @@ -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 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 apps) { + // refresh the names + mApps = apps; + mAdapter.setApps(apps); + } + + @Override + public void onAppsIconsLoaded(Map apps) { + // refresh the icons + mApps = apps; + mAdapter.setApps(apps); + } } diff --git a/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java b/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java index 56d26b0e..dbee83e5 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java +++ b/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java @@ -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 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 apps) { + mApps = apps; + } + + @Override + public void onAppsIconsLoaded(Map 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()); + } } diff --git a/app/src/main/java/com/emanuelef/remote_capture/fragments/StatusFragment.java b/app/src/main/java/com/emanuelef/remote_capture/fragments/StatusFragment.java index 1c98d094..d667c7b5 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/fragments/StatusFragment.java +++ b/app/src/main/java/com/emanuelef/remote_capture/fragments/StatusFragment.java @@ -191,7 +191,4 @@ public class StatusFragment extends Fragment implements AppStateListener { break; } } - - @Override - public void appsLoaded() {} } diff --git a/app/src/main/java/com/emanuelef/remote_capture/interfaces/AppStateListener.java b/app/src/main/java/com/emanuelef/remote_capture/interfaces/AppStateListener.java index f330bbcc..1fa98273 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/interfaces/AppStateListener.java +++ b/app/src/main/java/com/emanuelef/remote_capture/interfaces/AppStateListener.java @@ -23,5 +23,4 @@ import com.emanuelef.remote_capture.model.AppState; public interface AppStateListener { void appStateChanged(AppState state); - void appsLoaded(); } diff --git a/app/src/main/java/com/emanuelef/remote_capture/interfaces/AppsLoadListener.java b/app/src/main/java/com/emanuelef/remote_capture/interfaces/AppsLoadListener.java new file mode 100644 index 00000000..ba82778c --- /dev/null +++ b/app/src/main/java/com/emanuelef/remote_capture/interfaces/AppsLoadListener.java @@ -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 apps); + void onAppsIconsLoaded(Map apps); +} diff --git a/app/src/main/java/com/emanuelef/remote_capture/model/AppDescriptor.java b/app/src/main/java/com/emanuelef/remote_capture/model/AppDescriptor.java index 6e98fdeb..c13c83f2 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/model/AppDescriptor.java +++ b/app/src/main/java/com/emanuelef/remote_capture/model/AppDescriptor.java @@ -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, 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, 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());