Skip to content

Commit 3e770ed

Browse files
committed
Rework --start-app and --list-apps
1 parent d01373c commit 3e770ed

File tree

5 files changed

+262
-169
lines changed

5 files changed

+262
-169
lines changed

server/src/main/java/com/genymobile/scrcpy/control/Controller.java

+46-32
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,17 @@
55
import com.genymobile.scrcpy.CleanUp;
66
import com.genymobile.scrcpy.Options;
77
import com.genymobile.scrcpy.device.Device;
8-
import com.genymobile.scrcpy.device.DeviceApp;
98
import com.genymobile.scrcpy.device.Point;
109
import com.genymobile.scrcpy.device.Position;
1110
import com.genymobile.scrcpy.device.Size;
1211
import com.genymobile.scrcpy.util.Ln;
13-
import com.genymobile.scrcpy.util.LogUtils;
1412
import com.genymobile.scrcpy.video.SurfaceCapture;
1513
import com.genymobile.scrcpy.video.VirtualDisplayListener;
1614
import com.genymobile.scrcpy.wrappers.ClipboardManager;
1715
import com.genymobile.scrcpy.wrappers.InputManager;
1816
import com.genymobile.scrcpy.wrappers.ServiceManager;
1917

18+
import android.content.ComponentName;
2019
import android.content.IOnPrimaryClipChangedListener;
2120
import android.content.Intent;
2221
import android.os.Build;
@@ -27,7 +26,6 @@
2726
import android.view.MotionEvent;
2827

2928
import java.io.IOException;
30-
import java.util.List;
3129
import java.util.concurrent.ExecutorService;
3230
import java.util.concurrent.Executors;
3331
import java.util.concurrent.ScheduledExecutorService;
@@ -610,46 +608,62 @@ private void startAppAsync(String name) {
610608
}
611609

612610
private void startApp(String name) {
613-
boolean forceStopBeforeStart = name.startsWith("+");
614-
if (forceStopBeforeStart) {
615-
name = name.substring(1);
611+
Intent launchIntent = new Intent();
612+
613+
int startAppDisplayId = getStartAppDisplayId();
614+
if (startAppDisplayId == Device.DISPLAY_ID_NONE) {
615+
Ln.e("No known display id to start app \"" + name + "\"");
616+
return;
616617
}
617618

618-
DeviceApp app;
619-
boolean searchByName = name.startsWith("?");
620-
if (searchByName) {
619+
if (name.contains("+") && name.contains("-")){
620+
Ln.e("Can't make a (+) new instance if (-) force stop is also specified.");
621+
return;
622+
}
623+
624+
boolean newInstance = name.startsWith("+");
625+
if (newInstance) {
621626
name = name.substring(1);
627+
}
622628

623-
Ln.i("Processing Android apps... (this may take some time)");
624-
List<DeviceApp> apps = Device.findByName(name);
625-
if (apps.isEmpty()) {
626-
Ln.w("No app found for name \"" + name + "\"");
627-
return;
628-
}
629+
boolean forceStopBeforeStart = name.startsWith("-");
630+
if (forceStopBeforeStart) {
631+
name = name.substring(1);
632+
}
629633

630-
if (apps.size() > 1) {
631-
String title = "No unique app found for name \"" + name + "\":";
632-
Ln.w(LogUtils.buildAppListMessage(title, apps));
633-
return;
634-
}
634+
if (name.contains("/")){
635+
ComponentName component = new ComponentName(name.split("/")[0], name.split("/")[1]);
636+
launchIntent.setComponent(component);
635637

636-
app = apps.get(0);
638+
Ln.i("Starting activity: " + name);
637639
} else {
638-
app = Device.findByPackageName(name);
639-
if (app == null) {
640-
Ln.w("No app found for package \"" + name + "\"");
641-
return;
640+
boolean searchByName = name.startsWith("?") || name.contains(" ");
641+
if (searchByName) {
642+
if (name.contains("?")) {
643+
name = name.substring(1);
644+
}
645+
launchIntent = Device.getIntentFromAppDrawer(name,false);
646+
if (launchIntent == null){
647+
return;
648+
}
649+
} else {
650+
launchIntent = Device.getIntentFromAppDrawer(name,true);
651+
if (launchIntent == null) {
652+
return;
653+
}
642654
}
643-
}
644655

645-
int startAppDisplayId = getStartAppDisplayId();
646-
if (startAppDisplayId == Device.DISPLAY_ID_NONE) {
647-
Ln.e("No known display id to start app \"" + name + "\"");
648-
return;
656+
String packageName = launchIntent.getComponent().getPackageName();
657+
String label = launchIntent.getStringExtra("APP_LABEL");
658+
launchIntent.removeExtra("APP_LABEL");
659+
Ln.i("Starting app \"" + label + "\" [" + packageName + "] on display " + startAppDisplayId + "...");
649660
}
650661

651-
Ln.i("Starting app \"" + app.getName() + "\" [" + app.getPackageName() + "] on display " + startAppDisplayId + "...");
652-
Device.startApp(app.getPackageName(), startAppDisplayId, forceStopBeforeStart);
662+
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
663+
if (newInstance) {
664+
launchIntent.addFlags(Intent. FLAG_ACTIVITY_MULTIPLE_TASK);
665+
}
666+
Device.startApp(launchIntent, startAppDisplayId, forceStopBeforeStart);
653667
}
654668

655669
private int getStartAppDisplayId() {

server/src/main/java/com/genymobile/scrcpy/device/Device.java

+105-72
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.genymobile.scrcpy.AndroidVersions;
44
import com.genymobile.scrcpy.FakeContext;
5+
import com.genymobile.scrcpy.util.AppListProcessor;
56
import com.genymobile.scrcpy.util.Ln;
67
import com.genymobile.scrcpy.wrappers.ActivityManager;
78
import com.genymobile.scrcpy.wrappers.ClipboardManager;
@@ -12,10 +13,15 @@
1213
import com.genymobile.scrcpy.wrappers.WindowManager;
1314

1415
import android.annotation.SuppressLint;
16+
import android.app.UiModeManager;
17+
import android.content.ComponentName;
18+
import android.content.Context;
1519
import android.content.Intent;
1620
import android.app.ActivityOptions;
1721
import android.content.pm.ApplicationInfo;
1822
import android.content.pm.PackageManager;
23+
import android.content.pm.ResolveInfo;
24+
import android.content.res.Configuration;
1925
import android.os.Build;
2026
import android.os.Bundle;
2127
import android.os.IBinder;
@@ -25,7 +31,6 @@
2531
import android.view.KeyCharacterMap;
2632
import android.view.KeyEvent;
2733

28-
import java.util.ArrayList;
2934
import java.util.List;
3035
import java.util.Locale;
3136

@@ -221,95 +226,123 @@ private static int getCurrentRotation(int displayId) {
221226
return displayInfo.getRotation();
222227
}
223228

224-
public static List<DeviceApp> listApps() {
225-
List<DeviceApp> apps = new ArrayList<>();
226-
PackageManager pm = FakeContext.get().getPackageManager();
227-
for (ApplicationInfo appInfo : getLaunchableApps(pm)) {
228-
apps.add(toApp(pm, appInfo));
229+
public static void startApp(Intent launchIntent, int displayId, boolean forceStop) {
230+
Bundle options = null;
231+
if (Build.VERSION.SDK_INT >= AndroidVersions.API_26_ANDROID_8_0) {
232+
ActivityOptions launchOptions = ActivityOptions.makeBasic();
233+
launchOptions.setLaunchDisplayId(displayId);
234+
options = launchOptions.toBundle();
229235
}
230236

231-
return apps;
232-
}
233-
234-
@SuppressLint("QueryPermissionsNeeded")
235-
private static List<ApplicationInfo> getLaunchableApps(PackageManager pm) {
236-
List<ApplicationInfo> result = new ArrayList<>();
237-
for (ApplicationInfo appInfo : pm.getInstalledApplications(PackageManager.GET_META_DATA)) {
238-
if (appInfo.enabled && getLaunchIntent(pm, appInfo.packageName) != null) {
239-
result.add(appInfo);
240-
}
237+
ActivityManager am = ServiceManager.getActivityManager();
238+
if (forceStop) {
239+
am.forceStopPackage(launchIntent.getComponent().getPackageName());
241240
}
242-
243-
return result;
241+
am.startActivity(launchIntent, options);
244242
}
245243

246-
public static Intent getLaunchIntent(PackageManager pm, String packageName) {
247-
Intent launchIntent = pm.getLaunchIntentForPackage(packageName);
248-
if (launchIntent != null) {
249-
return launchIntent;
250-
}
244+
@SuppressLint("QueryPermissionsNeeded")
245+
public static List<ResolveInfo> getDrawerApps() {
246+
Context context = FakeContext.get();
247+
PackageManager pm = context.getPackageManager();
248+
Intent intent = new Intent(Intent.ACTION_MAIN, null);
251249

252-
return pm.getLeanbackLaunchIntentForPackage(packageName);
253-
}
250+
UiModeManager uiModeManager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE);
251+
intent.addCategory(uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION?
252+
Intent.CATEGORY_LEANBACK_LAUNCHER : Intent.CATEGORY_LAUNCHER);
254253

255-
private static DeviceApp toApp(PackageManager pm, ApplicationInfo appInfo) {
256-
String name = pm.getApplicationLabel(appInfo).toString();
257-
boolean system = (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
258-
return new DeviceApp(appInfo.packageName, name, system);
254+
return pm.queryIntentActivities(intent, 0);
259255
}
260256

261-
@SuppressLint("QueryPermissionsNeeded")
262-
public static DeviceApp findByPackageName(String packageName) {
263-
PackageManager pm = FakeContext.get().getPackageManager();
264-
// No need to filter by "launchable" apps, an error will be reported on start if the app is not launchable
265-
for (ApplicationInfo appInfo : pm.getInstalledApplications(PackageManager.GET_META_DATA)) {
266-
if (packageName.equals(appInfo.packageName)) {
267-
return toApp(pm, appInfo);
257+
public static Intent getIntentFromAppDrawer(String query, boolean isPackageName){
258+
AppListProcessor appListProcessor = new AppListProcessor(isPackageName, true, query);
259+
query = query.toLowerCase(Locale.getDefault());
260+
Context context = FakeContext.get();
261+
PackageManager pm = context.getPackageManager();
262+
263+
for (ResolveInfo drawerApp : getDrawerApps()) {
264+
String packageName = drawerApp.activityInfo.packageName;
265+
String label = drawerApp.loadLabel(pm).toString().toLowerCase(Locale.getDefault());
266+
267+
if (isPackageName){
268+
if (packageName.equals(query)) {
269+
ComponentName componentName = new ComponentName(packageName, drawerApp.activityInfo.name);
270+
return new Intent().setComponent(componentName)
271+
.putExtra("APP_LABEL", drawerApp.loadLabel(pm).toString());
272+
} else if (packageName.contains(query)){
273+
appListProcessor.addPotentialMatchesPkgName(drawerApp);
274+
}
275+
} else {
276+
if (label.equals(query)) {
277+
appListProcessor.addExactMatchesLabel(drawerApp);
278+
} else if (label.contains(query)){
279+
appListProcessor.addPotentialMatchesAppName(drawerApp);
280+
}
268281
}
269282
}
270283

271-
return null;
272-
}
273-
274-
@SuppressLint("QueryPermissionsNeeded")
275-
public static List<DeviceApp> findByName(String searchName) {
276-
List<DeviceApp> result = new ArrayList<>();
277-
searchName = searchName.toLowerCase(Locale.getDefault());
278-
279-
PackageManager pm = FakeContext.get().getPackageManager();
280-
for (ApplicationInfo appInfo : getLaunchableApps(pm)) {
281-
String name = pm.getApplicationLabel(appInfo).toString();
282-
if (name.toLowerCase(Locale.getDefault()).startsWith(searchName)) {
283-
boolean system = (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
284-
result.add(new DeviceApp(appInfo.packageName, name, system));
285-
}
284+
//Suggestions will show regardless if MULTIPLE_EXACT_LABELS
285+
Intent launchIntent = appListProcessor.getIntent(pm,false);
286+
if (launchIntent == null){
287+
Ln.w("Trying to find from list of all apps");
288+
return getIntentFromListOfAllApps(appListProcessor.getOrgQuery(), isPackageName);
286289
}
287-
288-
return result;
290+
else if (launchIntent.getBooleanExtra("MULTIPLE_EXACT_LABELS", false)) {
291+
//Let processResolvedLists() return a "garbage" intent if there are multiple exact labels.
292+
// We want to avoid redundant check. This happens if first check "launchIntent == null"
293+
// becomes true since getIntentFromListOfAllApps() also calls processResolvedLists().
294+
// This limitation can be removed to instead list possible exact label matches
295+
// from list of all apps also.
296+
return null;
297+
}
298+
return launchIntent;
289299
}
290300

291-
public static void startApp(String packageName, int displayId, boolean forceStop) {
292-
PackageManager pm = FakeContext.get().getPackageManager();
293-
294-
Intent launchIntent = getLaunchIntent(pm, packageName);
295-
if (launchIntent == null) {
296-
Ln.w("Cannot create launch intent for app " + packageName);
297-
return;
301+
@SuppressLint("QueryPermissionsNeeded")
302+
public static Intent getIntentFromListOfAllApps(String query, boolean isPackageName){
303+
AppListProcessor appListProcessor = new AppListProcessor(isPackageName, false, query);
304+
query = query.toLowerCase(Locale.getDefault());
305+
Context context = FakeContext.get();
306+
PackageManager pm = context.getPackageManager();
307+
308+
boolean isTV = false;
309+
UiModeManager uiModeManager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE);
310+
if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
311+
isTV = true;
298312
}
299313

300-
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
301-
302-
Bundle options = null;
303-
if (Build.VERSION.SDK_INT >= AndroidVersions.API_26_ANDROID_8_0) {
304-
ActivityOptions launchOptions = ActivityOptions.makeBasic();
305-
launchOptions.setLaunchDisplayId(displayId);
306-
options = launchOptions.toBundle();
314+
for (ApplicationInfo appInfo : pm.getInstalledApplications(PackageManager.GET_META_DATA)) {
315+
String packageName = appInfo.packageName;
316+
String label = appInfo.loadLabel(pm).toString().toLowerCase(Locale.getDefault());
317+
Intent launchIntent = isTV ?
318+
pm.getLeanbackLaunchIntentForPackage(packageName) :
319+
pm.getLaunchIntentForPackage(packageName);
320+
321+
if (isPackageName){
322+
if (packageName.equals(query) && launchIntent == null) {
323+
Ln.e("No launch intent for " + appInfo.loadLabel(pm) + " ["+packageName+"]");
324+
return null;
325+
} else if (packageName.equals(query)) {
326+
return launchIntent.putExtra("APP_LABEL", label);
327+
}else if (packageName.contains(query) && launchIntent != null){
328+
appListProcessor.addPotentialMatchesPkgName(pm.resolveActivity(launchIntent, 0));
329+
}
330+
} else {
331+
if (launchIntent == null) {
332+
if (label.equals(query)){
333+
Ln.w("Ignoring "+ appInfo.loadLabel(pm) + " ["+packageName+"] which has no launch intent");
334+
}
335+
continue;
336+
}
337+
ResolveInfo resolveInfo = pm.resolveActivity(launchIntent, 0);
338+
if (label.equals(query)) {
339+
appListProcessor.addExactMatchesLabel(resolveInfo);
340+
} else if (label.contains(query)){
341+
appListProcessor.addPotentialMatchesAppName(resolveInfo);
342+
}
343+
}
307344
}
308345

309-
ActivityManager am = ServiceManager.getActivityManager();
310-
if (forceStop) {
311-
am.forceStopPackage(packageName);
312-
}
313-
am.startActivity(launchIntent, options);
346+
return appListProcessor.getIntent(pm,true);
314347
}
315348
}

server/src/main/java/com/genymobile/scrcpy/device/DeviceApp.java

-26
This file was deleted.

0 commit comments

Comments
 (0)