├── app ├── .gitignore ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ ├── android │ │ │ └── content │ │ │ │ └── IIntentReceiver.java │ │ │ └── com │ │ │ └── example │ │ │ └── termuxam │ │ │ ├── BaseCommand.java │ │ │ ├── ShellCommand.java │ │ │ ├── ActivityManager.java │ │ │ ├── CrossVersionReflectedMethod.java │ │ │ ├── IActivityManager.java │ │ │ ├── IntentCmd.java │ │ │ └── Am.java │ └── androidTest │ │ ├── aidl │ │ └── com │ │ │ └── example │ │ │ └── termuxam │ │ │ └── ITestComponentsService.aidl │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── example │ │ └── termuxam │ │ ├── test │ │ ├── TestActivity.java │ │ ├── TestReceiver.java │ │ ├── TestService.java │ │ └── TestComponentsService.java │ │ └── IActivityManagerTest.java ├── build.gradle └── proguard-rules.pro ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── am-apk-installed ├── am-libexec-packaged ├── gradle.properties ├── README.md ├── gradlew.bat ├── gradlew └── LICENSE /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalbednarski/TermuxAm/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /am-apk-installed: -------------------------------------------------------------------------------- 1 | #!/data/data/com.termux/files/usr/bin/sh 2 | export CLASSPATH=$(pm path com.example.termuxam|cut -d: -f2) 3 | unset LD_LIBRARY_PATH LD_PRELOAD 4 | exec /system/bin/app_process / com.example.termuxam.Am "$@" 5 | -------------------------------------------------------------------------------- /am-libexec-packaged: -------------------------------------------------------------------------------- 1 | #!/data/data/com.termux/files/usr/bin/sh 2 | export CLASSPATH=/data/data/com.termux/files/usr/libexec/termux-am/am.apk 3 | unset LD_LIBRARY_PATH LD_PRELOAD 4 | exec /system/bin/app_process / com.example.termuxam.Am "$@" 5 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jan 07 11:55:09 CET 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/java/android/content/IIntentReceiver.java: -------------------------------------------------------------------------------- 1 | package android.content; 2 | 3 | import android.os.Bundle; 4 | 5 | /** 6 | * Stub - will be replaced by system at runtime 7 | */ 8 | public interface IIntentReceiver { 9 | public static abstract class Stub implements IIntentReceiver { 10 | public abstract void performReceive(Intent intent, int resultCode, String data, Bundle extras, 11 | boolean ordered, boolean sticky, int sendingUser); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/androidTest/aidl/com/example/termuxam/ITestComponentsService.aidl: -------------------------------------------------------------------------------- 1 | // ITestComponentsService.aidl 2 | package com.example.termuxam; 3 | 4 | // Declare any non-default types here with import statements 5 | 6 | interface ITestComponentsService { 7 | /** 8 | * Demonstrates some basic types that you can use as parameters 9 | * and return values in AIDL. 10 | */ 11 | /*void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, 12 | double aDouble, String aString);*/ 13 | void prepareAwait(); 14 | String await(); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/termuxam/test/TestActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.termuxam.test; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | 7 | import com.example.termuxam.IActivityManagerTest; 8 | 9 | /** 10 | * {@link Activity} used for {@link IActivityManagerTest#testStartActivity()} 11 | */ 12 | public class TestActivity extends Activity { 13 | @Override 14 | protected void onCreate(@Nullable Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | TestComponentsService.noteEvent("TestActivity " + getIntent().getAction()); 17 | finish(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/termuxam/test/TestReceiver.java: -------------------------------------------------------------------------------- 1 | package com.example.termuxam.test; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import com.example.termuxam.IActivityManagerTest; 8 | 9 | /** 10 | * {@link BroadcastReceiver} used in {@link IActivityManagerTest#testBroadcastIntent()} 11 | */ 12 | public class TestReceiver extends BroadcastReceiver { 13 | public static final String REPLY_DATA = "TestReceiver.REPLY_DATA:"; 14 | 15 | @Override 16 | public void onReceive(Context context, Intent intent) { 17 | setResultData(REPLY_DATA + intent.getAction()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.example.termuxam" 7 | minSdkVersion 21 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "0.1" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | //implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | //testImplementation 'junit:junit:4.12' 24 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 25 | } 26 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Moved to https://github.com/termux/TermuxAm 2 | 3 | # Android Oreo-compatible `am` command reimplementation 4 | `am` (Activity Manager) command in Android can be used to start Activity 5 | or send Broadcast from shell, however since Android Oreo that command 6 | only works from adb shell, not from apps. This is modified version of that 7 | command that is usable from app. 8 | 9 | # Running 10 | In this repository there are two wrapper scripts: 11 | * `am-libexec-packaged` 12 | * `am-apk-installed` 13 | 14 | First one is for use as installed package in Termux, while second one 15 | is for development, using TermuxAm apk that is installed as app in Android, 16 | allowing installation of Java part from Android Studio 17 | 18 | # Running tests/debugging 19 | Tests checking IActivityManager wrapper class are in `app/src/androidTest/java/com/example/termuxam/IActivityManagerTest.java` 20 | and are runnable/debuggable from Android Studio 21 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/termuxam/test/TestService.java: -------------------------------------------------------------------------------- 1 | package com.example.termuxam.test; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.IBinder; 6 | import android.support.annotation.Nullable; 7 | 8 | import com.example.termuxam.IActivityManagerTest; 9 | 10 | /** 11 | * {@link Service} used in {@link IActivityManagerTest#testStartStopService()} 12 | */ 13 | public class TestService extends Service { 14 | 15 | @Override 16 | public int onStartCommand(Intent intent, int flags, int startId) { 17 | TestComponentsService.noteEvent("Start TestService " + intent.getAction()); 18 | return START_NOT_STICKY; 19 | } 20 | 21 | @Override 22 | public void onDestroy() { 23 | TestComponentsService.noteEvent("Stop TestService"); 24 | super.onDestroy(); 25 | } 26 | 27 | @Nullable 28 | @Override 29 | public IBinder onBind(Intent intent) { 30 | return null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/termuxam/test/TestComponentsService.java: -------------------------------------------------------------------------------- 1 | package com.example.termuxam.test; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.IBinder; 6 | import android.os.RemoteException; 7 | import android.support.annotation.Nullable; 8 | 9 | import com.example.termuxam.ITestComponentsService; 10 | 11 | import java.util.concurrent.CountDownLatch; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * Helper service for reporting operations performed on test 16 | * {@link TestActivity} and {@link TestService} 17 | */ 18 | public class TestComponentsService extends Service { 19 | 20 | private static CountDownLatch awaitedEventLatch; 21 | private static String awaitedEvent; 22 | 23 | static void noteEvent(String name) { 24 | if (awaitedEventLatch != null) { 25 | awaitedEvent = name; 26 | awaitedEventLatch.countDown(); 27 | } 28 | } 29 | 30 | private final ITestComponentsService.Stub aidlImpl = new ITestComponentsService.Stub() { 31 | @Override 32 | public void prepareAwait() throws RemoteException { 33 | awaitedEvent = null; 34 | awaitedEventLatch = new CountDownLatch(1); 35 | } 36 | 37 | @Override 38 | public String await() throws RemoteException { 39 | try { 40 | if (awaitedEventLatch.await(5, TimeUnit.SECONDS)) { 41 | return awaitedEvent; 42 | } 43 | return "timed out"; 44 | } catch (InterruptedException e) { 45 | return "await interrupted"; 46 | } 47 | } 48 | }; 49 | 50 | @Nullable 51 | @Override 52 | public IBinder onBind(Intent intent) { 53 | return aidlImpl; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/termuxam/BaseCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | ** 3 | ** Copyright 2013, The Android Open Source Project 4 | ** 5 | ** Licensed under the Apache License, Version 2.0 (the "License"); 6 | ** you may not use this file except in compliance with the License. 7 | ** You may obtain a copy of the License at 8 | ** 9 | ** http://www.apache.org/licenses/LICENSE-2.0 10 | ** 11 | ** Unless required by applicable law or agreed to in writing, software 12 | ** distributed under the License is distributed on an "AS IS" BASIS, 13 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ** See the License for the specific language governing permissions and 15 | ** limitations under the License. 16 | */ 17 | 18 | package com.example.termuxam; 19 | 20 | import java.io.PrintStream; 21 | 22 | /** 23 | * Copied from android-7.0.0_r1 frameworks/base/core/java/com/android/internal/os 24 | */ 25 | public abstract class BaseCommand { 26 | 27 | final protected ShellCommand mArgs = new ShellCommand(); 28 | 29 | // These are magic strings understood by the Eclipse plugin. 30 | public static final String FATAL_ERROR_CODE = "Error type 1"; 31 | public static final String NO_SYSTEM_ERROR_CODE = "Error type 2"; 32 | public static final String NO_CLASS_ERROR_CODE = "Error type 3"; 33 | 34 | /** 35 | * Call to run the command. 36 | */ 37 | public void run(String[] args) { 38 | if (args.length < 1) { 39 | onShowUsage(System.out); 40 | return; 41 | } 42 | 43 | mArgs.init(args, 0); 44 | 45 | try { 46 | onRun(); 47 | } catch (IllegalArgumentException e) { 48 | onShowUsage(System.err); 49 | System.err.println(); 50 | System.err.println("Error: " + e.getMessage()); 51 | } catch (Exception e) { 52 | e.printStackTrace(System.err); 53 | System.exit(1); 54 | } 55 | } 56 | 57 | /** 58 | * Convenience to show usage information to error output. 59 | */ 60 | public void showUsage() { 61 | onShowUsage(System.err); 62 | } 63 | 64 | /** 65 | * Convenience to show usage information to error output along 66 | * with an error message. 67 | */ 68 | public void showError(String message) { 69 | onShowUsage(System.err); 70 | System.err.println(); 71 | System.err.println(message); 72 | } 73 | 74 | /** 75 | * Implement the command. 76 | */ 77 | public abstract void onRun() throws Exception; 78 | 79 | /** 80 | * Print help text for the command. 81 | */ 82 | public abstract void onShowUsage(PrintStream out); 83 | 84 | /** 85 | * Return the next option on the command line -- that is an argument that 86 | * starts with '-'. If the next argument is not an option, null is returned. 87 | */ 88 | public String nextOption() { 89 | return mArgs.getNextOption(); 90 | } 91 | 92 | /** 93 | * Return the next argument on the command line, whatever it is; if there are 94 | * no arguments left, return null. 95 | */ 96 | public String nextArg() { 97 | return mArgs.getNextArg(); 98 | } 99 | 100 | /** 101 | * Return the next argument on the command line, whatever it is; if there are 102 | * no arguments left, throws an IllegalArgumentException to report this to the user. 103 | */ 104 | public String nextArgRequired() { 105 | return mArgs.getNextArgRequired(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/termuxam/ShellCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.termuxam; 18 | 19 | /** 20 | * Copied from android-7.0.0_r1 frameworks/base/core/java/android/os 21 | */ 22 | public class ShellCommand { 23 | static final String TAG = "ShellCommand"; 24 | static final boolean DEBUG = false; 25 | 26 | private String[] mArgs; 27 | 28 | private int mArgPos; 29 | private String mCurArgData; 30 | 31 | public void init(String[] args, int firstArgPos) { 32 | mArgs = args; 33 | mArgPos = firstArgPos; 34 | mCurArgData = null; 35 | } 36 | 37 | /** 38 | * Return the next option on the command line -- that is an argument that 39 | * starts with '-'. If the next argument is not an option, null is returned. 40 | */ 41 | public String getNextOption() { 42 | if (mCurArgData != null) { 43 | String prev = mArgs[mArgPos - 1]; 44 | throw new IllegalArgumentException("No argument expected after \"" + prev + "\""); 45 | } 46 | if (mArgPos >= mArgs.length) { 47 | return null; 48 | } 49 | String arg = mArgs[mArgPos]; 50 | if (!arg.startsWith("-")) { 51 | return null; 52 | } 53 | mArgPos++; 54 | if (arg.equals("--")) { 55 | return null; 56 | } 57 | if (arg.length() > 1 && arg.charAt(1) != '-') { 58 | if (arg.length() > 2) { 59 | mCurArgData = arg.substring(2); 60 | return arg.substring(0, 2); 61 | } else { 62 | mCurArgData = null; 63 | return arg; 64 | } 65 | } 66 | mCurArgData = null; 67 | return arg; 68 | } 69 | 70 | /** 71 | * Return the next argument on the command line, whatever it is; if there are 72 | * no arguments left, return null. 73 | */ 74 | public String getNextArg() { 75 | if (mCurArgData != null) { 76 | String arg = mCurArgData; 77 | mCurArgData = null; 78 | return arg; 79 | } else if (mArgPos < mArgs.length) { 80 | return mArgs[mArgPos++]; 81 | } else { 82 | return null; 83 | } 84 | } 85 | 86 | public String peekNextArg() { 87 | if (mCurArgData != null) { 88 | return mCurArgData; 89 | } else if (mArgPos < mArgs.length) { 90 | return mArgs[mArgPos]; 91 | } else { 92 | return null; 93 | } 94 | } 95 | 96 | /** 97 | * Return the next argument on the command line, whatever it is; if there are 98 | * no arguments left, throws an IllegalArgumentException to report this to the user. 99 | */ 100 | public String getNextArgRequired() { 101 | String arg = getNextArg(); 102 | if (arg == null) { 103 | String prev = mArgs[mArgPos - 1]; 104 | throw new IllegalArgumentException("Argument expected after \"" + prev + "\""); 105 | } 106 | return arg; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/termuxam/IActivityManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.example.termuxam; 2 | 3 | import android.content.ComponentName; 4 | import android.content.Context; 5 | import android.content.IIntentReceiver; 6 | import android.content.Intent; 7 | import android.content.ServiceConnection; 8 | import android.os.Bundle; 9 | import android.os.IBinder; 10 | import android.support.test.InstrumentationRegistry; 11 | import android.support.test.runner.AndroidJUnit4; 12 | import android.util.Log; 13 | 14 | import com.example.termuxam.test.TestActivity; 15 | import com.example.termuxam.test.TestComponentsService; 16 | import com.example.termuxam.test.TestReceiver; 17 | import com.example.termuxam.test.TestService; 18 | 19 | import org.junit.After; 20 | import org.junit.Before; 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | 24 | import java.lang.reflect.Field; 25 | import java.util.concurrent.CountDownLatch; 26 | import java.util.concurrent.TimeUnit; 27 | 28 | import static org.junit.Assert.assertEquals; 29 | import static org.junit.Assert.assertNotNull; 30 | import static org.junit.Assert.assertTrue; 31 | 32 | @RunWith(AndroidJUnit4.class) 33 | public class IActivityManagerTest { 34 | 35 | private IActivityManager mAm; 36 | private String mAction; 37 | 38 | private ITestComponentsService mTestComponentsService; 39 | private ServiceConnection mServiceConnection; 40 | 41 | @Before 42 | public void setUp() throws Exception { 43 | mAm = new IActivityManager(InstrumentationRegistry.getTargetContext().getPackageName()); 44 | 45 | // Generate Intent action for use in tests 46 | mAction = "com.example.termuxam.test.TEST_INTENT_" + Math.random(); 47 | 48 | // Connect to test components service 49 | final CountDownLatch serviceConnectedLatch = new CountDownLatch(1); 50 | mServiceConnection = new ServiceConnection() { 51 | @Override 52 | public void onServiceConnected(ComponentName name, IBinder service) { 53 | mTestComponentsService = ITestComponentsService.Stub.asInterface(service); 54 | serviceConnectedLatch.countDown(); 55 | } 56 | 57 | @Override 58 | public void onServiceDisconnected(ComponentName name) {} 59 | }; 60 | InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { 61 | @Override 62 | public void run() { 63 | Intent intent = new Intent(InstrumentationRegistry.getContext(), TestComponentsService.class); 64 | InstrumentationRegistry.getTargetContext().bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); 65 | } 66 | }); 67 | serviceConnectedLatch.await(); 68 | } 69 | 70 | @After 71 | public void tearDown() throws Exception { 72 | InstrumentationRegistry.getTargetContext().unbindService(mServiceConnection); 73 | } 74 | 75 | @Test 76 | public void testMethodsAvailable() throws Exception { 77 | 78 | for (Field field : IActivityManager.class.getDeclaredFields()) { 79 | if (field.getType() == CrossVersionReflectedMethod.class) { 80 | field.setAccessible(true); 81 | CrossVersionReflectedMethod method = (CrossVersionReflectedMethod) field.get(mAm); 82 | assertTrue(field.getName(), method.isFound()); 83 | } 84 | } 85 | } 86 | 87 | @Test 88 | public void testStartActivity() throws Exception { 89 | mTestComponentsService.prepareAwait(); 90 | Intent intent = new Intent(mAction, null, InstrumentationRegistry.getContext(), TestActivity.class); 91 | mAm.startActivityAsUser(intent, null, 0, null, 0); 92 | assertEquals("TestActivity " + mAction, mTestComponentsService.await()); 93 | } 94 | 95 | @Test 96 | public void testBroadcastIntent() throws Exception { 97 | final CountDownLatch latch = new CountDownLatch(1); 98 | final Intent[] outIntent = new Intent[1]; 99 | final String[] outData = new String[1]; 100 | IIntentReceiver finishReceiver = new IIntentReceiver.Stub() { 101 | @Override 102 | public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { 103 | outIntent[0] = intent; 104 | outData[0] = data; 105 | latch.countDown(); 106 | } 107 | }; 108 | 109 | // Send the broadcast 110 | mAm.broadcastIntent(new Intent(mAction, null, InstrumentationRegistry.getContext(), TestReceiver.class), finishReceiver, null, true, false, 0); 111 | 112 | // Wait for result and check values 113 | latch.await(); 114 | boolean notTimedOut = latch.await(3, TimeUnit.SECONDS); 115 | assertTrue(notTimedOut); 116 | assertNotNull(outIntent[0]); 117 | assertEquals(mAction, outIntent[0].getAction()); 118 | assertEquals(TestReceiver.REPLY_DATA + mAction, outData[0]); 119 | } 120 | 121 | @Test 122 | public void testStartStopService() throws Exception { 123 | Intent intent = new Intent(mAction, null, InstrumentationRegistry.getContext(), TestService.class); 124 | // Start service 125 | mTestComponentsService.prepareAwait(); 126 | mAm.startService(intent, null, 0); 127 | assertEquals("Start TestService " + mAction, mTestComponentsService.await()); 128 | // Stop service 129 | mTestComponentsService.prepareAwait(); 130 | mAm.stopService(intent, null, 0); 131 | assertEquals("Stop TestService", mTestComponentsService.await()); 132 | } 133 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/termuxam/ActivityManager.java: -------------------------------------------------------------------------------- 1 | package com.example.termuxam; 2 | 3 | /** 4 | * \@hide-hidden constants 5 | */ 6 | public class ActivityManager { 7 | private static final int FIRST_START_FATAL_ERROR_CODE = -100; 8 | private static final int LAST_START_FATAL_ERROR_CODE = -1; 9 | private static final int FIRST_START_SUCCESS_CODE = 0; 10 | private static final int LAST_START_SUCCESS_CODE = 99; 11 | private static final int FIRST_START_NON_FATAL_ERROR_CODE = 100; 12 | private static final int LAST_START_NON_FATAL_ERROR_CODE = 199; 13 | 14 | /** 15 | * Result for IActivityManager.startVoiceActivity: active session is currently hidden. 16 | * @hide 17 | */ 18 | public static final int START_VOICE_HIDDEN_SESSION = FIRST_START_FATAL_ERROR_CODE; 19 | 20 | /** 21 | * Result for IActivityManager.startVoiceActivity: active session does not match 22 | * the requesting token. 23 | * @hide 24 | */ 25 | public static final int START_VOICE_NOT_ACTIVE_SESSION = FIRST_START_FATAL_ERROR_CODE + 1; 26 | 27 | /** 28 | * Result for IActivityManager.startActivity: trying to start a background user 29 | * activity that shouldn't be displayed for all users. 30 | * @hide 31 | */ 32 | public static final int START_NOT_CURRENT_USER_ACTIVITY = FIRST_START_FATAL_ERROR_CODE + 2; 33 | 34 | /** 35 | * Result for IActivityManager.startActivity: trying to start an activity under voice 36 | * control when that activity does not support the VOICE category. 37 | * @hide 38 | */ 39 | public static final int START_NOT_VOICE_COMPATIBLE = FIRST_START_FATAL_ERROR_CODE + 3; 40 | 41 | /** 42 | * Result for IActivityManager.startActivity: an error where the 43 | * start had to be canceled. 44 | * @hide 45 | */ 46 | public static final int START_CANCELED = FIRST_START_FATAL_ERROR_CODE + 4; 47 | 48 | /** 49 | * Result for IActivityManager.startActivity: an error where the 50 | * thing being started is not an activity. 51 | * @hide 52 | */ 53 | public static final int START_NOT_ACTIVITY = FIRST_START_FATAL_ERROR_CODE + 5; 54 | 55 | /** 56 | * Result for IActivityManager.startActivity: an error where the 57 | * caller does not have permission to start the activity. 58 | * @hide 59 | */ 60 | public static final int START_PERMISSION_DENIED = FIRST_START_FATAL_ERROR_CODE + 6; 61 | 62 | /** 63 | * Result for IActivityManager.startActivity: an error where the 64 | * caller has requested both to forward a result and to receive 65 | * a result. 66 | * @hide 67 | */ 68 | public static final int START_FORWARD_AND_REQUEST_CONFLICT = FIRST_START_FATAL_ERROR_CODE + 7; 69 | 70 | /** 71 | * Result for IActivityManager.startActivity: an error where the 72 | * requested class is not found. 73 | * @hide 74 | */ 75 | public static final int START_CLASS_NOT_FOUND = FIRST_START_FATAL_ERROR_CODE + 8; 76 | 77 | /** 78 | * Result for IActivityManager.startActivity: an error where the 79 | * given Intent could not be resolved to an activity. 80 | * @hide 81 | */ 82 | public static final int START_INTENT_NOT_RESOLVED = FIRST_START_FATAL_ERROR_CODE + 9; 83 | 84 | /** 85 | * Result for IActivityManager.startAssistantActivity: active session is currently hidden. 86 | * @hide 87 | */ 88 | public static final int START_ASSISTANT_HIDDEN_SESSION = FIRST_START_FATAL_ERROR_CODE + 10; 89 | 90 | /** 91 | * Result for IActivityManager.startAssistantActivity: active session does not match 92 | * the requesting token. 93 | * @hide 94 | */ 95 | public static final int START_ASSISTANT_NOT_ACTIVE_SESSION = FIRST_START_FATAL_ERROR_CODE + 11; 96 | 97 | /** 98 | * Result for IActivityManaqer.startActivity: the activity was started 99 | * successfully as normal. 100 | * @hide 101 | */ 102 | public static final int START_SUCCESS = FIRST_START_SUCCESS_CODE; 103 | 104 | /** 105 | * Result for IActivityManaqer.startActivity: the caller asked that the Intent not 106 | * be executed if it is the recipient, and that is indeed the case. 107 | * @hide 108 | */ 109 | public static final int START_RETURN_INTENT_TO_CALLER = FIRST_START_SUCCESS_CODE + 1; 110 | 111 | /** 112 | * Result for IActivityManaqer.startActivity: activity wasn't really started, but 113 | * a task was simply brought to the foreground. 114 | * @hide 115 | */ 116 | public static final int START_TASK_TO_FRONT = FIRST_START_SUCCESS_CODE + 2; 117 | 118 | /** 119 | * Result for IActivityManaqer.startActivity: activity wasn't really started, but 120 | * the given Intent was given to the existing top activity. 121 | * @hide 122 | */ 123 | public static final int START_DELIVERED_TO_TOP = FIRST_START_SUCCESS_CODE + 3; 124 | 125 | /** 126 | * Result for IActivityManaqer.startActivity: request was canceled because 127 | * app switches are temporarily canceled to ensure the user's last request 128 | * (such as pressing home) is performed. 129 | * @hide 130 | */ 131 | public static final int START_SWITCHES_CANCELED = FIRST_START_NON_FATAL_ERROR_CODE; 132 | 133 | /** 134 | * Result for IActivityManaqer.startActivity: a new activity was attempted to be started 135 | * while in Lock Task Mode. 136 | * @hide 137 | */ 138 | public static final int START_RETURN_LOCK_TASK_MODE_VIOLATION = 139 | FIRST_START_NON_FATAL_ERROR_CODE + 1; 140 | 141 | /** 142 | * Result for IActivityManaqer.startActivity: a new activity start was aborted. Never returned 143 | * externally. 144 | * @hide 145 | */ 146 | public static final int START_ABORTED = FIRST_START_NON_FATAL_ERROR_CODE + 2; 147 | 148 | /** 149 | * Flag for IActivityManaqer.startActivity: do special start mode where 150 | * a new activity is launched only if it is needed. 151 | * @hide 152 | */ 153 | public static final int START_FLAG_ONLY_IF_NEEDED = 1<<0; 154 | 155 | /** 156 | * Flag for IActivityManaqer.startActivity: launch the app for 157 | * debugging. 158 | * @hide 159 | */ 160 | public static final int START_FLAG_DEBUG = 1<<1; 161 | 162 | /** 163 | * Flag for IActivityManaqer.startActivity: launch the app for 164 | * allocation tracking. 165 | * @hide 166 | */ 167 | public static final int START_FLAG_TRACK_ALLOCATION = 1<<2; 168 | 169 | /** 170 | * Flag for IActivityManaqer.startActivity: launch the app with 171 | * native debugging support. 172 | * @hide 173 | */ 174 | public static final int START_FLAG_NATIVE_DEBUGGING = 1<<3; 175 | 176 | /** 177 | * Result for IActivityManaqer.broadcastIntent: success! 178 | * @hide 179 | */ 180 | public static final int BROADCAST_SUCCESS = 0; 181 | 182 | /** 183 | * Result for IActivityManaqer.broadcastIntent: attempt to broadcast 184 | * a sticky intent without appropriate permission. 185 | * @hide 186 | */ 187 | public static final int BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1; 188 | 189 | /** 190 | * Result for IActivityManager.broadcastIntent: trying to send a broadcast 191 | * to a stopped user. Fail. 192 | * @hide 193 | */ 194 | public static final int BROADCAST_FAILED_USER_STOPPED = -2; 195 | 196 | 197 | } 198 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/termuxam/CrossVersionReflectedMethod.java: -------------------------------------------------------------------------------- 1 | package com.example.termuxam; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.lang.reflect.Method; 5 | import java.util.HashMap; 6 | 7 | /** 8 | * Class wrapping reflection method and using named arguments for invocation 9 | * 10 | * Can have multiple variants of method and find one that actually exists 11 | */ 12 | public class CrossVersionReflectedMethod { 13 | 14 | private final Class mClass; 15 | private Method mMethod = null; 16 | private Object[] mDefaultArgs; 17 | private HashMap mArgNamesToIndexes; 18 | 19 | 20 | public CrossVersionReflectedMethod(Class aClass) { 21 | mClass = aClass; 22 | } 23 | 24 | 25 | /** 26 | * Try finding method method variant in reflected class 27 | * 28 | * @param methodName Name of method to be found 29 | * @param typesNamesAndDefaults 30 | * any amount of (in order, all required for each set) 31 | * - Types (as class, used in reflection) 32 | * - Names (used for {@link #invoke(Object, Object...)} call) 33 | * - Default values 34 | */ 35 | public CrossVersionReflectedMethod tryMethodVariant(String methodName, Object... typesNamesAndDefaults) { 36 | // If we have already found an implementation, skip next checks 37 | if (mMethod != null) { 38 | return this; 39 | } 40 | 41 | try { 42 | // Get list of arguments for reflection call 43 | int argCount = typesNamesAndDefaults.length / 3; 44 | Class[] refArguments = new Class[argCount]; 45 | for (int i = 0; i < argCount; i++) { 46 | Object refArgument = typesNamesAndDefaults[i * 3]; 47 | if (refArgument instanceof Class) { 48 | refArguments[i] = (Class) refArgument; 49 | } else { 50 | refArguments[i] = Class.forName((String) refArgument); 51 | } 52 | 53 | } 54 | 55 | // Get method 56 | mMethod = mClass.getMethod(methodName, (Class[]) refArguments); 57 | 58 | // If we're here - method exists 59 | mArgNamesToIndexes = new HashMap<>(); 60 | mDefaultArgs = new Object[argCount]; 61 | for (int i = 0; i < argCount; i++) { 62 | mArgNamesToIndexes.put((String) typesNamesAndDefaults[i * 3 + 1], i); 63 | mDefaultArgs[i] = typesNamesAndDefaults[i * 3 + 2]; 64 | } 65 | } catch (NoSuchMethodException | ClassNotFoundException ignored) {} 66 | return this; 67 | } 68 | 69 | /** 70 | * Try finding method method variant in reflected class, 71 | * allowing method in class to have additional arguments between provided ones 72 | * 73 | * @param methodName Name of method to be found 74 | * @param typesNamesAndDefaults 75 | * any amount of (in order, all required for each set) 76 | * - Types (as class, used in reflection) 77 | * - Names (used for {@link #invoke(Object, Object...)} call) 78 | * - Default values 79 | */ 80 | public CrossVersionReflectedMethod tryMethodVariantInexact(String methodName, Object... typesNamesAndDefaults) { 81 | // If we have already found an implementation, skip next checks 82 | if (mMethod != null) { 83 | return this; 84 | } 85 | 86 | int expectedArgCount = typesNamesAndDefaults.length / 3; 87 | 88 | for (Method method : mClass.getMethods()) { 89 | if (!methodName.equals(method.getName())) { 90 | continue; 91 | } 92 | 93 | // Matched name, try matching arguments 94 | // Get list of arguments for reflection call 95 | 96 | try { 97 | // These are for arguments provided to this method 98 | Class expectedArgumentClass = null; 99 | int expectedArgumentI = 0; 100 | 101 | // This is for method arguments found through reflection 102 | int actualArgumentI = 0; 103 | 104 | // Parameters for method - we'll copy them to fields 105 | // when we're sure that this is right method 106 | HashMap argNamesToIndexes = new HashMap<>(); 107 | Object[] defaultArgs = new Object[method.getParameterTypes().length]; 108 | 109 | // Iterate over actual method arguments 110 | for (Class methodParam : method.getParameterTypes()) { 111 | // Get expected argument if we haven't it cached 112 | if (expectedArgumentClass == null && expectedArgumentI < expectedArgCount) { 113 | Object refArgument = typesNamesAndDefaults[expectedArgumentI * 3]; 114 | if (refArgument instanceof Class) { 115 | expectedArgumentClass = (Class) refArgument; 116 | } else { 117 | expectedArgumentClass = Class.forName((String) refArgument); 118 | } 119 | } 120 | 121 | // Check if this argument is expected one 122 | if (methodParam == expectedArgumentClass) { 123 | argNamesToIndexes.put((String) typesNamesAndDefaults[expectedArgumentI * 3 + 1], actualArgumentI); 124 | defaultArgs[actualArgumentI] = typesNamesAndDefaults[expectedArgumentI * 3 + 2]; 125 | 126 | // Note this argument is passed 127 | expectedArgumentI++; 128 | expectedArgumentClass = null; 129 | } else { 130 | defaultArgs[actualArgumentI] = getDefaultValueForPrimitiveClass(methodParam); 131 | } 132 | 133 | actualArgumentI++; 134 | } 135 | 136 | // Check if method has all requested arguments 137 | if (expectedArgumentI != expectedArgCount) { 138 | continue; 139 | } 140 | 141 | // Export result if matched 142 | mMethod = method; 143 | mDefaultArgs = defaultArgs; 144 | mArgNamesToIndexes = argNamesToIndexes; 145 | } catch (ClassNotFoundException e) { 146 | // No such class on this system, probably okay 147 | /*if (BuildConfig.DEBUG) { 148 | e.printStackTrace(); 149 | }*/ 150 | } 151 | } 152 | return this; 153 | } 154 | 155 | /** 156 | * Invoke method 157 | * 158 | * @param receiver Object on which we call {@link Method#invoke(Object, Object...)} 159 | * @param namesAndValues 160 | * Any amount of argument name (as used in {@link #tryMethodVariant(String, Object...)} and value pairs 161 | */ 162 | public Object invoke(Object receiver, Object ...namesAndValues) throws InvocationTargetException { 163 | if (mMethod == null) { 164 | throw new RuntimeException("Couldn't find method with matching signature"); 165 | } 166 | Object[] args = mDefaultArgs.clone(); 167 | for (int i = 0; i < namesAndValues.length; i += 2) { 168 | @SuppressWarnings("SuspiciousMethodCalls") 169 | Integer namedArgIndex = mArgNamesToIndexes.get(namesAndValues[i]); 170 | if (namedArgIndex != null) { 171 | args[namedArgIndex] = namesAndValues[i + 1]; 172 | } 173 | } 174 | try { 175 | return mMethod.invoke(receiver, args); 176 | } catch (IllegalAccessException e) { 177 | throw new RuntimeException(e); 178 | } 179 | } 180 | 181 | public static Object getDefaultValueForPrimitiveClass(Class aClass) { 182 | if (aClass == Boolean.TYPE) { 183 | return false; 184 | } else if (aClass == Byte.TYPE) { 185 | return (byte) 0; 186 | } else if (aClass == Character.TYPE) { 187 | return 0; 188 | } else if (aClass == Short.TYPE) { 189 | return (short) 0; 190 | } else if (aClass == Integer.TYPE) { 191 | return 0; 192 | } else if (aClass == Long.TYPE) { 193 | return (long) 0; 194 | } else if (aClass == Float.TYPE) { 195 | return 0; 196 | } else if (aClass == Double.TYPE) { 197 | return 0; 198 | } else { 199 | return null; 200 | } 201 | } 202 | 203 | public boolean isFound() { 204 | return mMethod != null; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/termuxam/IActivityManager.java: -------------------------------------------------------------------------------- 1 | package com.example.termuxam; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.PendingIntent; 5 | import android.content.ComponentName; 6 | import android.content.IIntentReceiver; 7 | import android.content.Intent; 8 | import android.net.Uri; 9 | import android.os.Bundle; 10 | import android.os.IBinder; 11 | 12 | import java.lang.reflect.InvocationTargetException; 13 | 14 | /** 15 | * Wrapper around android.app.IActivityManager internal interface 16 | */ 17 | @SuppressLint("PrivateApi") 18 | class IActivityManager { 19 | 20 | private Object mAm; 21 | private CrossVersionReflectedMethod mGetProviderMimeType; 22 | private CrossVersionReflectedMethod mStartActivity; 23 | /* 24 | private CrossVersionReflectedMethod mBroadcastIntent; 25 | */ 26 | private CrossVersionReflectedMethod mStartServiceMethod; 27 | private CrossVersionReflectedMethod mStopServiceMethod; 28 | private CrossVersionReflectedMethod mGetIntentSenderMethod; 29 | private CrossVersionReflectedMethod mIntentSenderSendMethod; 30 | 31 | IActivityManager() throws Exception { 32 | this("com.termux"); 33 | } 34 | 35 | IActivityManager(String callingAppName) throws Exception { 36 | try { 37 | try { 38 | mAm = android.app.ActivityManager.class 39 | .getMethod("getService") 40 | .invoke(null); 41 | } catch (Exception e) { 42 | mAm = Class.forName("android.app.ActivityManagerNative") 43 | .getMethod("getDefault") 44 | .invoke(null); 45 | } 46 | Class amClass = mAm.getClass(); 47 | mGetProviderMimeType = 48 | new CrossVersionReflectedMethod(amClass) 49 | .tryMethodVariantInexact( 50 | "getProviderMimeType", 51 | Uri.class, "uri", 52 | int.class, "userId" 53 | ); 54 | mStartActivity = 55 | new CrossVersionReflectedMethod(amClass) 56 | .tryMethodVariantInexact( 57 | "startActivityAsUser", 58 | "android.app.IApplicationThread", "caller", null, 59 | String.class, "callingPackage", callingAppName, 60 | Intent.class, "intent", null, 61 | String.class, "resolvedType", null, 62 | IBinder.class, "resultTo", null, 63 | String.class, "resultWho", null, 64 | int.class, "requestCode", -1, 65 | int.class, "flags", 0, 66 | //ProfilerInfo profilerInfo, - let's autodetect 67 | Bundle.class, "options", null, 68 | int.class, "userId", 0 69 | ); 70 | /* 71 | mBroadcastIntent = 72 | new CrossVersionReflectedMethod(amClass) 73 | .tryMethodVariantInexact( 74 | "broadcastIntent", 75 | "android.app.IApplicationThread", "caller", null, 76 | Intent.class, "intent", null, 77 | String.class, "resolvedType", null, 78 | IIntentReceiver.class, "resultTo", null, 79 | int.class, "resultCode", -1, 80 | String.class, "resultData", null, 81 | Bundle.class, "map", null, 82 | String[].class, "requiredPermissions", null, 83 | int.class, "appOp", 0, 84 | Bundle.class, "options", null, 85 | boolean.class, "serialized", false, 86 | boolean.class, "sticky", false, 87 | int.class, "userId", 0 88 | ); 89 | */ 90 | mStartServiceMethod = 91 | new CrossVersionReflectedMethod(amClass) 92 | .tryMethodVariantInexact( 93 | "startService", 94 | "android.app.IApplicationThread", "caller", null, 95 | Intent.class, "service", null, 96 | String.class, "resolvedType", null, 97 | boolean.class, "requireForeground", false, 98 | String.class, "callingPackage", callingAppName, 99 | int.class, "userId", 0 100 | ).tryMethodVariantInexact( 101 | "startService", 102 | "android.app.IApplicationThread", "caller", null, 103 | Intent.class, "service", null, 104 | String.class, "resolvedType", null, 105 | String.class, "callingPackage", callingAppName, 106 | int.class, "userId", 0 107 | ).tryMethodVariantInexact( // Pre frameworks/base 99b6043 108 | "startService", 109 | "android.app.IApplicationThread", "caller", null, 110 | Intent.class, "service", null, 111 | String.class, "resolvedType", null, 112 | int.class, "userId", 0 113 | ); 114 | mStopServiceMethod = 115 | new CrossVersionReflectedMethod(amClass) 116 | .tryMethodVariantInexact( 117 | "stopService", 118 | "android.app.IApplicationThread", "caller", null, 119 | Intent.class, "service", null, 120 | String.class, "resolvedType", null, 121 | int.class, "userId", 0 122 | ); 123 | mGetIntentSenderMethod = 124 | new CrossVersionReflectedMethod(amClass) 125 | .tryMethodVariantInexact( 126 | "getIntentSender", 127 | int.class, "type", 0, 128 | String.class, "packageName", callingAppName, 129 | IBinder.class, "token", null, 130 | String.class, "resultWho", null, 131 | int.class, "requestCode", 0, 132 | Intent[].class, "intents", null, 133 | String[].class, "resolvedTypes", null, 134 | int.class, "flags", 0, 135 | Bundle.class, "options", null, 136 | int.class, "userId", 0 137 | ); 138 | mIntentSenderSendMethod = 139 | new CrossVersionReflectedMethod(Class.forName("android.content.IIntentSender")) 140 | .tryMethodVariantInexact( 141 | "send", 142 | int.class, "code", 0, 143 | Intent.class, "intent", null, 144 | String.class, "resolvedType", null, 145 | //IBinder.class, "android.os.IBinder whitelistToken", null, 146 | "android.content.IIntentReceiver", "finishedReceiver", null, 147 | String.class, "requiredPermission", null, 148 | Bundle.class, "options", null 149 | ).tryMethodVariantInexact( // Pre frameworks/base a750a63 150 | "send", 151 | int.class, "code", 0, 152 | Intent.class, "intent", null, 153 | String.class, "resolvedType", null, 154 | "android.content.IIntentReceiver", "finishedReceiver", null, 155 | String.class, "requiredPermission", null 156 | ); 157 | 158 | 159 | } catch (Exception e) { 160 | throw new RuntimeException(e); 161 | } 162 | } 163 | 164 | int startActivityAsUser(Intent intent, String resolvedType, int flags, Bundle options, int userId) throws InvocationTargetException { 165 | return (Integer) mStartActivity.invoke( 166 | mAm, 167 | "intent", intent, 168 | "resolvedType", resolvedType, 169 | "flags", flags, 170 | "options", options, 171 | "userId", userId 172 | ); 173 | } 174 | 175 | void broadcastIntent(Intent intent, IIntentReceiver resultTo, String[] requiredPermissions, boolean serialized, boolean sticky, int userId) throws InvocationTargetException { 176 | /* 177 | mBroadcastIntent.invoke( 178 | mAm, 179 | "intent", intent, 180 | "resultTo", resultTo, 181 | "requiredPermissions", requiredPermissions, 182 | "serialized", serialized, 183 | "sticky", sticky, 184 | "userId", userId 185 | ); 186 | */ 187 | Object pendingIntent = mGetIntentSenderMethod.invoke( 188 | mAm, 189 | "type", 1 /*ActivityManager.INTENT_SENDER_BROADCAST*/, 190 | "intents", new Intent[] { intent }, 191 | "flags", PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT, 192 | "userId", userId 193 | ); 194 | mIntentSenderSendMethod.invoke( 195 | pendingIntent, 196 | "requiredPermission", (requiredPermissions == null || requiredPermissions.length == 0) ? null : requiredPermissions[0], 197 | "finishedReceiver", resultTo 198 | ); 199 | } 200 | 201 | String getProviderMimeType(Uri uri, int userId) throws InvocationTargetException { 202 | return (String) mGetProviderMimeType.invoke( 203 | mAm, 204 | "uri", uri, 205 | "userId", userId 206 | ); 207 | } 208 | 209 | ComponentName startService(Intent service, String resolvedType, int userId) throws InvocationTargetException { 210 | return (ComponentName) mStartServiceMethod.invoke( 211 | mAm, 212 | "service", service, 213 | "resolvedType", resolvedType, 214 | "userId", userId 215 | ); 216 | } 217 | 218 | int stopService(Intent service, String resolvedType, int userId) throws InvocationTargetException { 219 | return (Integer) mStopServiceMethod.invoke( 220 | mAm, 221 | "service", service, 222 | "resolvedType", resolvedType, 223 | "userId", userId 224 | ); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/termuxam/IntentCmd.java: -------------------------------------------------------------------------------- 1 | package com.example.termuxam; 2 | 3 | import android.content.ComponentName; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.os.Bundle; 7 | 8 | import java.io.PrintWriter; 9 | import java.net.URISyntaxException; 10 | import java.util.ArrayList; 11 | import java.util.HashSet; 12 | 13 | /** 14 | * Copied from android-7.0.0_r1 frameworks/base/core/java/android/content/Intent.java 15 | */ 16 | public class IntentCmd { 17 | /** @hide */ 18 | public interface CommandOptionHandler { 19 | boolean handleOption(String opt, ShellCommand cmd); 20 | } 21 | 22 | /** @hide */ 23 | public static Intent parseCommandArgs(ShellCommand cmd, CommandOptionHandler optionHandler) 24 | throws URISyntaxException { 25 | Intent intent = new Intent(); 26 | Intent baseIntent = intent; 27 | boolean hasIntentInfo = false; 28 | 29 | Uri data = null; 30 | String type = null; 31 | 32 | String opt; 33 | while ((opt=cmd.getNextOption()) != null) { 34 | switch (opt) { 35 | case "-a": 36 | intent.setAction(cmd.getNextArgRequired()); 37 | if (intent == baseIntent) { 38 | hasIntentInfo = true; 39 | } 40 | break; 41 | case "-d": 42 | data = Uri.parse(cmd.getNextArgRequired()); 43 | if (intent == baseIntent) { 44 | hasIntentInfo = true; 45 | } 46 | break; 47 | case "-t": 48 | type = cmd.getNextArgRequired(); 49 | if (intent == baseIntent) { 50 | hasIntentInfo = true; 51 | } 52 | break; 53 | case "-c": 54 | intent.addCategory(cmd.getNextArgRequired()); 55 | if (intent == baseIntent) { 56 | hasIntentInfo = true; 57 | } 58 | break; 59 | case "-e": 60 | case "--es": { 61 | String key = cmd.getNextArgRequired(); 62 | String value = cmd.getNextArgRequired(); 63 | intent.putExtra(key, value); 64 | } 65 | break; 66 | case "--esn": { 67 | String key = cmd.getNextArgRequired(); 68 | intent.putExtra(key, (String) null); 69 | } 70 | break; 71 | case "--ei": { 72 | String key = cmd.getNextArgRequired(); 73 | String value = cmd.getNextArgRequired(); 74 | intent.putExtra(key, Integer.decode(value)); 75 | } 76 | break; 77 | case "--eu": { 78 | String key = cmd.getNextArgRequired(); 79 | String value = cmd.getNextArgRequired(); 80 | intent.putExtra(key, Uri.parse(value)); 81 | } 82 | break; 83 | case "--ecn": { 84 | String key = cmd.getNextArgRequired(); 85 | String value = cmd.getNextArgRequired(); 86 | ComponentName cn = ComponentName.unflattenFromString(value); 87 | if (cn == null) 88 | throw new IllegalArgumentException("Bad component name: " + value); 89 | intent.putExtra(key, cn); 90 | } 91 | break; 92 | case "--eia": { 93 | String key = cmd.getNextArgRequired(); 94 | String value = cmd.getNextArgRequired(); 95 | String[] strings = value.split(","); 96 | int[] list = new int[strings.length]; 97 | for (int i = 0; i < strings.length; i++) { 98 | list[i] = Integer.decode(strings[i]); 99 | } 100 | intent.putExtra(key, list); 101 | } 102 | break; 103 | case "--eial": { 104 | String key = cmd.getNextArgRequired(); 105 | String value = cmd.getNextArgRequired(); 106 | String[] strings = value.split(","); 107 | ArrayList list = new ArrayList<>(strings.length); 108 | for (int i = 0; i < strings.length; i++) { 109 | list.add(Integer.decode(strings[i])); 110 | } 111 | intent.putExtra(key, list); 112 | } 113 | break; 114 | case "--el": { 115 | String key = cmd.getNextArgRequired(); 116 | String value = cmd.getNextArgRequired(); 117 | intent.putExtra(key, Long.valueOf(value)); 118 | } 119 | break; 120 | case "--ela": { 121 | String key = cmd.getNextArgRequired(); 122 | String value = cmd.getNextArgRequired(); 123 | String[] strings = value.split(","); 124 | long[] list = new long[strings.length]; 125 | for (int i = 0; i < strings.length; i++) { 126 | list[i] = Long.valueOf(strings[i]); 127 | } 128 | intent.putExtra(key, list); 129 | hasIntentInfo = true; 130 | } 131 | break; 132 | case "--elal": { 133 | String key = cmd.getNextArgRequired(); 134 | String value = cmd.getNextArgRequired(); 135 | String[] strings = value.split(","); 136 | ArrayList list = new ArrayList<>(strings.length); 137 | for (int i = 0; i < strings.length; i++) { 138 | list.add(Long.valueOf(strings[i])); 139 | } 140 | intent.putExtra(key, list); 141 | hasIntentInfo = true; 142 | } 143 | break; 144 | case "--ef": { 145 | String key = cmd.getNextArgRequired(); 146 | String value = cmd.getNextArgRequired(); 147 | intent.putExtra(key, Float.valueOf(value)); 148 | hasIntentInfo = true; 149 | } 150 | break; 151 | case "--efa": { 152 | String key = cmd.getNextArgRequired(); 153 | String value = cmd.getNextArgRequired(); 154 | String[] strings = value.split(","); 155 | float[] list = new float[strings.length]; 156 | for (int i = 0; i < strings.length; i++) { 157 | list[i] = Float.valueOf(strings[i]); 158 | } 159 | intent.putExtra(key, list); 160 | hasIntentInfo = true; 161 | } 162 | break; 163 | case "--efal": { 164 | String key = cmd.getNextArgRequired(); 165 | String value = cmd.getNextArgRequired(); 166 | String[] strings = value.split(","); 167 | ArrayList list = new ArrayList<>(strings.length); 168 | for (int i = 0; i < strings.length; i++) { 169 | list.add(Float.valueOf(strings[i])); 170 | } 171 | intent.putExtra(key, list); 172 | hasIntentInfo = true; 173 | } 174 | break; 175 | case "--esa": { 176 | String key = cmd.getNextArgRequired(); 177 | String value = cmd.getNextArgRequired(); 178 | // Split on commas unless they are preceeded by an escape. 179 | // The escape character must be escaped for the string and 180 | // again for the regex, thus four escape characters become one. 181 | String[] strings = value.split("(? list = new ArrayList<>(strings.length); 194 | for (int i = 0; i < strings.length; i++) { 195 | list.add(strings[i]); 196 | } 197 | intent.putExtra(key, list); 198 | hasIntentInfo = true; 199 | } 200 | break; 201 | case "--ez": { 202 | String key = cmd.getNextArgRequired(); 203 | String value = cmd.getNextArgRequired().toLowerCase(); 204 | // Boolean.valueOf() results in false for anything that is not "true", which is 205 | // error-prone in shell commands 206 | boolean arg; 207 | if ("true".equals(value) || "t".equals(value)) { 208 | arg = true; 209 | } else if ("false".equals(value) || "f".equals(value)) { 210 | arg = false; 211 | } else { 212 | try { 213 | arg = Integer.decode(value) != 0; 214 | } catch (NumberFormatException ex) { 215 | throw new IllegalArgumentException("Invalid boolean value: " + value); 216 | } 217 | } 218 | 219 | intent.putExtra(key, arg); 220 | } 221 | break; 222 | case "-n": { 223 | String str = cmd.getNextArgRequired(); 224 | ComponentName cn = ComponentName.unflattenFromString(str); 225 | if (cn == null) 226 | throw new IllegalArgumentException("Bad component name: " + str); 227 | intent.setComponent(cn); 228 | if (intent == baseIntent) { 229 | hasIntentInfo = true; 230 | } 231 | } 232 | break; 233 | case "-p": { 234 | String str = cmd.getNextArgRequired(); 235 | intent.setPackage(str); 236 | if (intent == baseIntent) { 237 | hasIntentInfo = true; 238 | } 239 | } 240 | break; 241 | case "-f": 242 | String str = cmd.getNextArgRequired(); 243 | intent.setFlags(Integer.decode(str).intValue()); 244 | break; 245 | case "--grant-read-uri-permission": 246 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 247 | break; 248 | case "--grant-write-uri-permission": 249 | intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 250 | break; 251 | case "--grant-persistable-uri-permission": 252 | intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); 253 | break; 254 | case "--grant-prefix-uri-permission": 255 | intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); 256 | break; 257 | case "--exclude-stopped-packages": 258 | intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES); 259 | break; 260 | case "--include-stopped-packages": 261 | intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); 262 | break; 263 | case "--debug-log-resolution": 264 | intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION); 265 | break; 266 | case "--activity-brought-to-front": 267 | intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); 268 | break; 269 | case "--activity-clear-top": 270 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 271 | break; 272 | case "--activity-clear-when-task-reset": 273 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 274 | break; 275 | case "--activity-exclude-from-recents": 276 | intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 277 | break; 278 | case "--activity-launched-from-history": 279 | intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); 280 | break; 281 | case "--activity-multiple-task": 282 | intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); 283 | break; 284 | case "--activity-no-animation": 285 | intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); 286 | break; 287 | case "--activity-no-history": 288 | intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); 289 | break; 290 | case "--activity-no-user-action": 291 | intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION); 292 | break; 293 | case "--activity-previous-is-top": 294 | intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); 295 | break; 296 | case "--activity-reorder-to-front": 297 | intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); 298 | break; 299 | case "--activity-reset-task-if-needed": 300 | intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 301 | break; 302 | case "--activity-single-top": 303 | intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 304 | break; 305 | case "--activity-clear-task": 306 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 307 | break; 308 | case "--activity-task-on-home": 309 | intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME); 310 | break; 311 | case "--receiver-registered-only": 312 | intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 313 | break; 314 | case "--receiver-replace-pending": 315 | intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 316 | break; 317 | case "--receiver-foreground": 318 | intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 319 | break; 320 | case "--receiver-no-abort": 321 | intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT); 322 | break; 323 | /* 324 | case "--receiver-include-background": 325 | intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 326 | break; 327 | */ 328 | case "--selector": 329 | intent.setDataAndType(data, type); 330 | intent = new Intent(); 331 | break; 332 | default: 333 | if (optionHandler != null && optionHandler.handleOption(opt, cmd)) { 334 | // Okay, caller handled this option. 335 | } else { 336 | throw new IllegalArgumentException("Unknown option: " + opt); 337 | } 338 | break; 339 | } 340 | } 341 | intent.setDataAndType(data, type); 342 | 343 | final boolean hasSelector = intent != baseIntent; 344 | if (hasSelector) { 345 | // A selector was specified; fix up. 346 | baseIntent.setSelector(intent); 347 | intent = baseIntent; 348 | } 349 | 350 | String arg = cmd.getNextArg(); 351 | baseIntent = null; 352 | if (arg == null) { 353 | if (hasSelector) { 354 | // If a selector has been specified, and no arguments 355 | // have been supplied for the main Intent, then we can 356 | // assume it is ACTION_MAIN CATEGORY_LAUNCHER; we don't 357 | // need to have a component name specified yet, the 358 | // selector will take care of that. 359 | baseIntent = new Intent(Intent.ACTION_MAIN); 360 | baseIntent.addCategory(Intent.CATEGORY_LAUNCHER); 361 | } 362 | } else if (arg.indexOf(':') >= 0) { 363 | // The argument is a URI. Fully parse it, and use that result 364 | // to fill in any data not specified so far. 365 | baseIntent = Intent.parseUri(arg, Intent.URI_INTENT_SCHEME 366 | | Intent.URI_ANDROID_APP_SCHEME | Intent.URI_ALLOW_UNSAFE); 367 | } else if (arg.indexOf('/') >= 0) { 368 | // The argument is a component name. Build an Intent to launch 369 | // it. 370 | baseIntent = new Intent(Intent.ACTION_MAIN); 371 | baseIntent.addCategory(Intent.CATEGORY_LAUNCHER); 372 | baseIntent.setComponent(ComponentName.unflattenFromString(arg)); 373 | } else { 374 | // Assume the argument is a package name. 375 | baseIntent = new Intent(Intent.ACTION_MAIN); 376 | baseIntent.addCategory(Intent.CATEGORY_LAUNCHER); 377 | baseIntent.setPackage(arg); 378 | } 379 | if (baseIntent != null) { 380 | Bundle extras = intent.getExtras(); 381 | intent.replaceExtras((Bundle)null); 382 | Bundle uriExtras = baseIntent.getExtras(); 383 | baseIntent.replaceExtras((Bundle)null); 384 | if (intent.getAction() != null && baseIntent.getCategories() != null) { 385 | HashSet cats = new HashSet(baseIntent.getCategories()); 386 | for (String c : cats) { 387 | baseIntent.removeCategory(c); 388 | } 389 | } 390 | intent.fillIn(baseIntent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_SELECTOR); 391 | if (extras == null) { 392 | extras = uriExtras; 393 | } else if (uriExtras != null) { 394 | uriExtras.putAll(extras); 395 | extras = uriExtras; 396 | } 397 | intent.replaceExtras(extras); 398 | hasIntentInfo = true; 399 | } 400 | 401 | if (!hasIntentInfo) throw new IllegalArgumentException("No intent supplied"); 402 | return intent; 403 | } 404 | 405 | /** @hide */ 406 | public static void printIntentArgsHelp(PrintWriter pw, String prefix) { 407 | final String[] lines = new String[] { 408 | " specifications include these flags and arguments:", 409 | " [-a ] [-d ] [-t ]", 410 | " [-c [-c ] ...]", 411 | " [-e|--es ...]", 412 | " [--esn ...]", 413 | " [--ez ...]", 414 | " [--ei ...]", 415 | " [--el ...]", 416 | " [--ef ...]", 417 | " [--eu ...]", 418 | " [--ecn ]", 419 | " [--eia [, [,)", 423 | " [--ela [, [,)", 427 | " [--efa [, [,)", 431 | " [--esa [, [,; to embed a comma into a string,", 436 | " escape it using \"\\,\")", 437 | " [-f ]", 438 | " [--grant-read-uri-permission] [--grant-write-uri-permission]", 439 | " [--grant-persistable-uri-permission] [--grant-prefix-uri-permission]", 440 | " [--debug-log-resolution] [--exclude-stopped-packages]", 441 | " [--include-stopped-packages]", 442 | " [--activity-brought-to-front] [--activity-clear-top]", 443 | " [--activity-clear-when-task-reset] [--activity-exclude-from-recents]", 444 | " [--activity-launched-from-history] [--activity-multiple-task]", 445 | " [--activity-no-animation] [--activity-no-history]", 446 | " [--activity-no-user-action] [--activity-previous-is-top]", 447 | " [--activity-reorder-to-front] [--activity-reset-task-if-needed]", 448 | " [--activity-single-top] [--activity-clear-task]", 449 | " [--activity-task-on-home]", 450 | " [--receiver-registered-only] [--receiver-replace-pending]", 451 | " [--receiver-foreground] [--receiver-no-abort]", 452 | " [--receiver-include-background]", 453 | " [--selector]", 454 | " [ | | ]" 455 | }; 456 | for (String line : lines) { 457 | pw.print(prefix); 458 | pw.println(line); 459 | } 460 | } 461 | 462 | } 463 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/termuxam/Am.java: -------------------------------------------------------------------------------- 1 | /* 2 | ** 3 | ** Copyright 2007, The Android Open Source Project 4 | ** 5 | ** Licensed under the Apache License, Version 2.0 (the "License"); 6 | ** you may not use this file except in compliance with the License. 7 | ** You may obtain a copy of the License at 8 | ** 9 | ** http://www.apache.org/licenses/LICENSE-2.0 10 | ** 11 | ** Unless required by applicable law or agreed to in writing, software 12 | ** distributed under the License is distributed on an "AS IS" BASIS, 13 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ** See the License for the specific language governing permissions and 15 | ** limitations under the License. 16 | */ 17 | 18 | 19 | package com.example.termuxam; 20 | 21 | import android.app.ActivityOptions; 22 | import android.content.ComponentName; 23 | import android.content.IIntentReceiver; 24 | import android.content.Intent; 25 | import android.os.Bundle; 26 | import android.os.SystemClock; 27 | import android.util.AndroidException; 28 | 29 | import java.io.PrintStream; 30 | import java.io.PrintWriter; 31 | import java.net.URISyntaxException; 32 | 33 | public class Am extends BaseCommand { 34 | 35 | /* 36 | private static final String SHELL_PACKAGE_NAME = "com.android.shell"; 37 | 38 | // Is the object moving in a positive direction? 39 | private static final boolean MOVING_FORWARD = true; 40 | // Is the object moving in the horizontal plan? 41 | private static final boolean MOVING_HORIZONTALLY = true; 42 | // Is the object current point great then its target point? 43 | private static final boolean GREATER_THAN_TARGET = true; 44 | // Amount we reduce the stack size by when testing a task re-size. 45 | private static final int STACK_BOUNDS_INSET = 10; 46 | */ 47 | 48 | private IActivityManager mAm; 49 | /* 50 | private IPackageManager mPm; 51 | */ 52 | 53 | private int mStartFlags = 0; 54 | private boolean mWaitOption = false; 55 | private boolean mStopOption = false; 56 | 57 | private int mRepeat = 0; 58 | private int mUserId; 59 | private String mReceiverPermission; 60 | 61 | /* 62 | private String mProfileFile; 63 | private int mSamplingInterval; 64 | private boolean mAutoStop; 65 | private int mStackId; 66 | */ 67 | 68 | /** 69 | * Command-line entry point. 70 | * 71 | * @param args The command-line arguments 72 | */ 73 | public static void main(String[] args) { 74 | (new Am()).run(args); 75 | } 76 | 77 | @Override 78 | public void onShowUsage(PrintStream out) { 79 | PrintWriter pw = new PrintWriter(out); 80 | pw.println( 81 | "usage: am [subcommand] [options]\n" + 82 | "usage: am start [-D] [-N] [-W] [-P ] [--start-profiler ]\n" + 83 | " [--sampling INTERVAL] [-R COUNT] [-S]\n" + 84 | " [--track-allocation] [--user | current] \n" + 85 | " am startservice [--user | current] \n" + 86 | " am stopservice [--user | current] \n" + 87 | /* 88 | " am force-stop [--user | all | current] \n" + 89 | " am kill [--user | all | current] \n" + 90 | " am kill-all\n" + 91 | */ 92 | " am broadcast [--user | all | current] \n" + 93 | /* 94 | " am instrument [-r] [-e ] [-p ] [-w]\n" + 95 | " [--user | current]\n" + 96 | " [--no-window-animation] [--abi ] \n" + 97 | " am profile start [--user current] [--sampling INTERVAL] \n" + 98 | " am profile stop [--user current] []\n" + 99 | " am dumpheap [--user current] [-n] \n" + 100 | " am set-debug-app [-w] [--persistent] \n" + 101 | " am clear-debug-app\n" + 102 | " am set-watch-heap \n" + 103 | " am clear-watch-heap\n" + 104 | " am bug-report [--progress]\n" + 105 | " am monitor [--gdb ]\n" + 106 | " am hang [--allow-restart]\n" + 107 | " am restart\n" + 108 | " am idle-maintenance\n" + 109 | " am screen-compat [on|off] \n" + 110 | " am package-importance \n" + 111 | */ 112 | " am to-uri [INTENT]\n" + 113 | " am to-intent-uri [INTENT]\n" + 114 | " am to-app-uri [INTENT]\n" + 115 | /* 116 | " am switch-user \n" + 117 | " am start-user \n" + 118 | " am unlock-user [TOKEN_HEX]\n" + 119 | " am stop-user [-w] [-f] \n" + 120 | " am stack start \n" + 121 | " am stack movetask [true|false]\n" + 122 | " am stack resize \n" + 123 | " am stack resize-animated \n" + 124 | " am stack resize-docked-stack []\n" + 125 | " am stack size-docked-stack-test: [DELAY_MS]\n" + 126 | " am stack move-top-activity-to-pinned-stack: \n" + 127 | " am stack positiontask \n" + 128 | " am stack list\n" + 129 | " am stack info \n" + 130 | " am stack remove \n" + 131 | " am task lock \n" + 132 | " am task lock stop\n" + 133 | " am task resizeable [0 (unresizeable) | 1 (crop_windows) | 2 (resizeable) | 3 (resizeable_and_pipable)]\n" + 134 | " am task resize \n" + 135 | " am task drag-task-test [DELAY_MS] \n" + 136 | " am task size-task-test [DELAY_MS] \n" + 137 | " am get-config\n" + 138 | " am suppress-resize-config-changes \n" + 139 | " am set-inactive [--user ] true|false\n" + 140 | " am get-inactive [--user ] \n" + 141 | " am send-trim-memory [--user ] \n" + 142 | " [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]\n" + 143 | " am get-current-user\n" + 144 | */ 145 | "\n" + 146 | "am start: start an Activity. Options are:\n" + 147 | " -D: enable debugging\n" + 148 | " -N: enable native debugging\n" + 149 | " -W: wait for launch to complete\n" + 150 | " --start-profiler : start profiler and send results to \n" + 151 | " --sampling INTERVAL: use sample profiling with INTERVAL microseconds\n" + 152 | " between samples (use with --start-profiler)\n" + 153 | " -P : like above, but profiling stops when app goes idle\n" + 154 | " -R: repeat the activity launch times. Prior to each repeat,\n" + 155 | " the top activity will be finished.\n" + 156 | " -S: force stop the target app before starting the activity\n" + 157 | " --track-allocation: enable tracking of object allocations\n" + 158 | " --user | current: Specify which user to run as; if not\n" + 159 | " specified then run as the current user.\n" + 160 | " --stack : Specify into which stack should the activity be put." + 161 | "\n" + 162 | "am startservice: start a Service. Options are:\n" + 163 | " --user | current: Specify which user to run as; if not\n" + 164 | " specified then run as the current user.\n" + 165 | "\n" + 166 | "am stopservice: stop a Service. Options are:\n" + 167 | " --user | current: Specify which user to run as; if not\n" + 168 | " specified then run as the current user.\n" + 169 | "\n" + 170 | /* 171 | "am force-stop: force stop everything associated with .\n" + 172 | " --user | all | current: Specify user to force stop;\n" + 173 | " all users if not specified.\n" + 174 | "\n" + 175 | "am kill: Kill all processes associated with . Only kills.\n" + 176 | " processes that are safe to kill -- that is, will not impact the user\n" + 177 | " experience.\n" + 178 | " --user | all | current: Specify user whose processes to kill;\n" + 179 | " all users if not specified.\n" + 180 | "\n" + 181 | "am kill-all: Kill all background processes.\n" + 182 | "\n" + 183 | */ 184 | "am broadcast: send a broadcast Intent. Options are:\n" + 185 | " --user | all | current: Specify which user to send to; if not\n" + 186 | " specified then send to all users.\n" + 187 | " --receiver-permission : Require receiver to hold permission.\n" + 188 | "\n" + 189 | /* 190 | "am instrument: start an Instrumentation. Typically this target \n" + 191 | " is the form / or only if there \n" + 192 | " is only one instrumentation. Options are:\n" + 193 | " -r: print raw results (otherwise decode REPORT_KEY_STREAMRESULT). Use with\n" + 194 | " [-e perf true] to generate raw output for performance measurements.\n" + 195 | " -e : set argument to . For test runners a\n" + 196 | " common form is [-e [,...]].\n" + 197 | " -p : write profiling data to \n" + 198 | " -w: wait for instrumentation to finish before returning. Required for\n" + 199 | " test runners.\n" + 200 | " --user | current: Specify user instrumentation runs in;\n" + 201 | " current user if not specified.\n" + 202 | " --no-window-animation: turn off window animations while running.\n" + 203 | " --abi : Launch the instrumented process with the selected ABI.\n" + 204 | " This assumes that the process supports the selected ABI.\n" + 205 | "\n" + 206 | "am trace-ipc: Trace IPC transactions.\n" + 207 | " start: start tracing IPC transactions.\n" + 208 | " stop: stop tracing IPC transactions and dump the results to file.\n" + 209 | " --dump-file : Specify the file the trace should be dumped to.\n" + 210 | "\n" + 211 | "am profile: start and stop profiler on a process. The given argument\n" + 212 | " may be either a process name or pid. Options are:\n" + 213 | " --user | current: When supplying a process name,\n" + 214 | " specify user of process to profile; uses current user if not specified.\n" + 215 | "\n" + 216 | "am dumpheap: dump the heap of a process. The given argument may\n" + 217 | " be either a process name or pid. Options are:\n" + 218 | " -n: dump native heap instead of managed heap\n" + 219 | " --user | current: When supplying a process name,\n" + 220 | " specify user of process to dump; uses current user if not specified.\n" + 221 | "\n" + 222 | "am set-debug-app: set application to debug. Options are:\n" + 223 | " -w: wait for debugger when application starts\n" + 224 | " --persistent: retain this value\n" + 225 | "\n" + 226 | "am clear-debug-app: clear the previously set-debug-app.\n" + 227 | "\n" + 228 | "am set-watch-heap: start monitoring pss size of , if it is at or\n" + 229 | " above then a heap dump is collected for the user to report\n" + 230 | "\n" + 231 | "am clear-watch-heap: clear the previously set-watch-heap.\n" + 232 | "\n" + 233 | "am bug-report: request bug report generation; will launch a notification\n" + 234 | " when done to select where it should be delivered. Options are: \n" + 235 | " --progress: will launch a notification right away to show its progress.\n" + 236 | "\n" + 237 | "am monitor: start monitoring for crashes or ANRs.\n" + 238 | " --gdb: start gdbserv on the given port at crash/ANR\n" + 239 | "\n" + 240 | "am hang: hang the system.\n" + 241 | " --allow-restart: allow watchdog to perform normal system restart\n" + 242 | "\n" + 243 | "am restart: restart the user-space system.\n" + 244 | "\n" + 245 | "am idle-maintenance: perform idle maintenance now.\n" + 246 | "\n" + 247 | "am screen-compat: control screen compatibility mode of .\n" + 248 | "\n" + 249 | "am package-importance: print current importance of .\n" + 250 | "\n" + 251 | */ 252 | "am to-uri: print the given Intent specification as a URI.\n" + 253 | "\n" + 254 | "am to-intent-uri: print the given Intent specification as an intent: URI.\n" + 255 | "\n" + 256 | "am to-app-uri: print the given Intent specification as an android-app: URI.\n" + 257 | "\n" //+ 258 | /* 259 | "am switch-user: switch to put USER_ID in the foreground, starting\n" + 260 | " execution of that user if it is currently stopped.\n" + 261 | "\n" + 262 | "am start-user: start USER_ID in background if it is currently stopped,\n" + 263 | " use switch-user if you want to start the user in foreground.\n" + 264 | "\n" + 265 | "am stop-user: stop execution of USER_ID, not allowing it to run any\n" + 266 | " code until a later explicit start or switch to it.\n" + 267 | " -w: wait for stop-user to complete.\n" + 268 | " -f: force stop even if there are related users that cannot be stopped.\n" + 269 | "\n" + 270 | "am stack start: start a new activity on using .\n" + 271 | "\n" + 272 | "am stack movetask: move from its current stack to the top (true) or" + 273 | " bottom (false) of .\n" + 274 | "\n" + 275 | "am stack resize: change size and position to .\n" + 276 | "\n" + 277 | "am stack resize-docked-stack: change docked stack to \n" + 278 | " and supplying temporary different task bounds indicated by\n" + 279 | " \n" + 280 | "\n" + 281 | "am stack size-docked-stack-test: test command for sizing docked stack by\n" + 282 | " increments from the side eft, op, ight, or ottom\n" + 283 | " applying the optional [DELAY_MS] between each step.\n" + 284 | "\n" + 285 | "am stack move-top-activity-to-pinned-stack: moves the top activity from\n" + 286 | " to the pinned stack using for the\n" + 287 | " bounds of the pinned stack.\n" + 288 | "\n" + 289 | "am stack positiontask: place in at " + 290 | "\n" + 291 | "am stack list: list all of the activity stacks and their sizes.\n" + 292 | "\n" + 293 | "am stack info: display the information about activity stack .\n" + 294 | "\n" + 295 | "am stack remove: remove stack .\n" + 296 | "\n" + 297 | "am task lock: bring to the front and don't allow other tasks to run.\n" + 298 | "\n" + 299 | "am task lock stop: end the current task lock.\n" + 300 | "\n" + 301 | "am task resizeable: change resizeable mode of .\n" + 302 | " 0 (unresizeable) | 1 (crop_windows) | 2 (resizeable) | 3 (resizeable_and_pipable)\n" + 303 | "\n" + 304 | "am task resize: makes sure is in a stack with the specified bounds.\n" + 305 | " Forces the task to be resizeable and creates a stack if no existing stack\n" + 306 | " has the specified bounds.\n" + 307 | "\n" + 308 | "am task drag-task-test: test command for dragging/moving by\n" + 309 | " increments around the screen applying the optional [DELAY_MS]\n" + 310 | " between each step.\n" + 311 | "\n" + 312 | "am task size-task-test: test command for sizing by " + 313 | " increments within the screen applying the optional [DELAY_MS] between\n" + 314 | " each step.\n" + 315 | "\n" + 316 | "am get-config: retrieve the configuration and any recent configurations\n" + 317 | " of the device.\n" + 318 | "am suppress-resize-config-changes: suppresses configuration changes due to\n" + 319 | " user resizing an activity/task.\n" + 320 | "\n" + 321 | "am set-inactive: sets the inactive state of an app.\n" + 322 | "\n" + 323 | "am get-inactive: returns the inactive state of an app.\n" + 324 | "\n" + 325 | "am send-trim-memory: send a memory trim event to a .\n" + 326 | "\n" + 327 | "am get-current-user: returns id of the current foreground user.\n" + 328 | "\n" 329 | */ 330 | ); 331 | IntentCmd.printIntentArgsHelp(pw, ""); 332 | pw.flush(); 333 | } 334 | 335 | @Override 336 | public void onRun() throws Exception { 337 | 338 | mAm = new IActivityManager(); 339 | if (mAm == null) { 340 | System.err.println(NO_SYSTEM_ERROR_CODE); 341 | throw new AndroidException("Can't connect to activity manager; is the system running?"); 342 | } 343 | 344 | /*mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); 345 | if (mPm == null) { 346 | System.err.println(NO_SYSTEM_ERROR_CODE); 347 | throw new AndroidException("Can't connect to package manager; is the system running?"); 348 | }*/ 349 | 350 | String op = nextArgRequired(); 351 | 352 | if (op.equals("start")) { 353 | runStart(); 354 | } else if (op.equals("startservice")) { 355 | runStartService(); 356 | } else if (op.equals("stopservice")) { 357 | runStopService(); 358 | /* 359 | } else if (op.equals("force-stop")) { 360 | runForceStop(); 361 | } else if (op.equals("kill")) { 362 | runKill(); 363 | } else if (op.equals("kill-all")) { 364 | runKillAll(); 365 | } else if (op.equals("instrument")) { 366 | runInstrument(); 367 | } else if (op.equals("trace-ipc")) { 368 | runTraceIpc(); 369 | */ 370 | } else if (op.equals("broadcast")) { 371 | sendBroadcast(); 372 | /* 373 | } else if (op.equals("profile")) { 374 | runProfile(); 375 | } else if (op.equals("dumpheap")) { 376 | runDumpHeap(); 377 | } else if (op.equals("set-debug-app")) { 378 | runSetDebugApp(); 379 | } else if (op.equals("clear-debug-app")) { 380 | runClearDebugApp(); 381 | } else if (op.equals("set-watch-heap")) { 382 | runSetWatchHeap(); 383 | } else if (op.equals("clear-watch-heap")) { 384 | runClearWatchHeap(); 385 | } else if (op.equals("bug-report")) { 386 | runBugReport(); 387 | } else if (op.equals("monitor")) { 388 | runMonitor(); 389 | } else if (op.equals("hang")) { 390 | runHang(); 391 | } else if (op.equals("restart")) { 392 | runRestart(); 393 | } else if (op.equals("idle-maintenance")) { 394 | runIdleMaintenance(); 395 | } else if (op.equals("screen-compat")) { 396 | runScreenCompat(); 397 | } else if (op.equals("package-importance")) { 398 | runPackageImportance(); 399 | */ 400 | } else if (op.equals("to-uri")) { 401 | runToUri(0); 402 | } else if (op.equals("to-intent-uri")) { 403 | runToUri(Intent.URI_INTENT_SCHEME); 404 | } else if (op.equals("to-app-uri")) { 405 | runToUri(Intent.URI_ANDROID_APP_SCHEME); 406 | /* 407 | } else if (op.equals("switch-user")) { 408 | runSwitchUser(); 409 | } else if (op.equals("start-user")) { 410 | runStartUserInBackground(); 411 | } else if (op.equals("unlock-user")) { 412 | runUnlockUser(); 413 | } else if (op.equals("stop-user")) { 414 | runStopUser(); 415 | } else if (op.equals("stack")) { 416 | runStack(); 417 | } else if (op.equals("task")) { 418 | runTask(); 419 | } else if (op.equals("get-config")) { 420 | runGetConfig(); 421 | } else if (op.equals("suppress-resize-config-changes")) { 422 | runSuppressResizeConfigChanges(); 423 | } else if (op.equals("set-inactive")) { 424 | runSetInactive(); 425 | } else if (op.equals("get-inactive")) { 426 | runGetInactive(); 427 | } else if (op.equals("send-trim-memory")) { 428 | runSendTrimMemory(); 429 | } else if (op.equals("get-current-user")) { 430 | runGetCurrentUser(); 431 | */ 432 | } else { 433 | showError("Error: unknown command '" + op + "'"); 434 | } 435 | } 436 | 437 | int parseUserArg(String arg) { 438 | return 0; 439 | /* 440 | int userId; 441 | if ("all".equals(arg)) { 442 | userId = UserHandle.USER_ALL; 443 | } else if ("current".equals(arg) || "cur".equals(arg)) { 444 | userId = UserHandle.USER_CURRENT; 445 | } else { 446 | userId = Integer.parseInt(arg); 447 | } 448 | return userId; 449 | */ 450 | } 451 | 452 | private Intent makeIntent() throws URISyntaxException { 453 | int defUser = 0; 454 | mStartFlags = 0; 455 | mWaitOption = false; 456 | mStopOption = false; 457 | mRepeat = 0; 458 | /* 459 | mProfileFile = null; 460 | mSamplingInterval = 0; 461 | mAutoStop = false; 462 | */ 463 | mUserId = defUser; 464 | /* 465 | mStackId = INVALID_STACK_ID; 466 | */ 467 | 468 | return IntentCmd.parseCommandArgs(mArgs, new IntentCmd.CommandOptionHandler() { 469 | @Override 470 | public boolean handleOption(String opt, ShellCommand cmd) { 471 | /*if (opt.equals("-D")) { 472 | mStartFlags |= ActivityManager.START_FLAG_DEBUG; 473 | } else if (opt.equals("-N")) { 474 | mStartFlags |= ActivityManager.START_FLAG_NATIVE_DEBUGGING; 475 | } else */if (opt.equals("-W")) { 476 | mWaitOption = true; 477 | } else if (opt.equals("-P")) { 478 | /* 479 | mProfileFile = nextArgRequired(); 480 | mAutoStop = true; 481 | */ 482 | } else if (opt.equals("--start-profiler")) { 483 | /* 484 | mProfileFile = nextArgRequired(); 485 | mAutoStop = false; 486 | */ 487 | } else if (opt.equals("--sampling")) { 488 | /* 489 | mSamplingInterval = Integer.parseInt(nextArgRequired()); 490 | */ 491 | } else if (opt.equals("-R")) { 492 | mRepeat = Integer.parseInt(nextArgRequired()); 493 | } else if (opt.equals("-S")) { 494 | mStopOption = true; 495 | /* 496 | } else if (opt.equals("--track-allocation")) { 497 | mStartFlags |= ActivityManager.START_FLAG_TRACK_ALLOCATION; 498 | */ 499 | } else if (opt.equals("--user")) { 500 | mUserId = parseUserArg(nextArgRequired()); 501 | } else if (opt.equals("--receiver-permission")) { 502 | mReceiverPermission = nextArgRequired(); 503 | } else if (opt.equals("--stack")) { 504 | /* 505 | mStackId = Integer.parseInt(nextArgRequired()); 506 | */ 507 | } else { 508 | return false; 509 | } 510 | return true; 511 | } 512 | }); 513 | } 514 | 515 | private void runStartService() throws Exception { 516 | Intent intent = makeIntent(); 517 | /* 518 | if (mUserId == UserHandle.USER_ALL) { 519 | System.err.println("Error: Can't start activity with user 'all'"); 520 | return; 521 | } 522 | */ 523 | System.out.println("Starting service: " + intent); 524 | ComponentName cn = mAm.startService(/*null,*/ intent, intent.getType(), 525 | /*SHELL_PACKAGE_NAME,*/ mUserId); 526 | if (cn == null) { 527 | System.err.println("Error: Not found; no service started."); 528 | } else if (cn.getPackageName().equals("!")) { 529 | System.err.println("Error: Requires permission " + cn.getClassName()); 530 | } else if (cn.getPackageName().equals("!!")) { 531 | System.err.println("Error: " + cn.getClassName()); 532 | } 533 | } 534 | 535 | private void runStopService() throws Exception { 536 | Intent intent = makeIntent(); 537 | /* 538 | if (mUserId == UserHandle.USER_ALL) { 539 | System.err.println("Error: Can't stop activity with user 'all'"); 540 | return; 541 | } 542 | */ 543 | System.out.println("Stopping service: " + intent); 544 | int result = mAm.stopService(/*null,*/ intent, intent.getType(), mUserId); 545 | if (result == 0) { 546 | System.err.println("Service not stopped: was not running."); 547 | } else if (result == 1) { 548 | System.err.println("Service stopped"); 549 | } else if (result == -1) { 550 | System.err.println("Error stopping service"); 551 | } 552 | } 553 | 554 | private void runStart() throws Exception { 555 | Intent intent = makeIntent(); 556 | /* 557 | if (mUserId == UserHandle.USER_ALL) { 558 | System.err.println("Error: Can't start service with user 'all'"); 559 | return; 560 | } 561 | */ 562 | String mimeType = intent.getType(); 563 | if (mimeType == null && intent.getData() != null 564 | && "content".equals(intent.getData().getScheme())) { 565 | mimeType = mAm.getProviderMimeType(intent.getData(), mUserId); 566 | } 567 | 568 | 569 | do { 570 | /* 571 | if (mStopOption) { 572 | String packageName; 573 | if (intent.getComponent() != null) { 574 | packageName = intent.getComponent().getPackageName(); 575 | } else { 576 | List activities = mPm.queryIntentActivities(intent, mimeType, 0, 577 | mUserId).getList(); 578 | if (activities == null || activities.size() <= 0) { 579 | System.err.println("Error: Intent does not match any activities: " 580 | + intent); 581 | return; 582 | } else if (activities.size() > 1) { 583 | System.err.println("Error: Intent matches multiple activities; can't stop: " 584 | + intent); 585 | return; 586 | } 587 | packageName = activities.get(0).activityInfo.packageName; 588 | } 589 | System.out.println("Stopping: " + packageName); 590 | mAm.forceStopPackage(packageName, mUserId); 591 | Thread.sleep(250); 592 | } 593 | */ 594 | 595 | System.out.println("Starting: " + intent); 596 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 597 | 598 | /* 599 | ParcelFileDescriptor fd = null; 600 | ProfilerInfo profilerInfo = null; 601 | 602 | if (mProfileFile != null) { 603 | try { 604 | fd = openForSystemServer( 605 | new File(mProfileFile), 606 | ParcelFileDescriptor.MODE_CREATE | 607 | ParcelFileDescriptor.MODE_TRUNCATE | 608 | ParcelFileDescriptor.MODE_WRITE_ONLY); 609 | } catch (FileNotFoundException e) { 610 | System.err.println("Error: Unable to open file: " + mProfileFile); 611 | System.err.println("Consider using a file under /data/local/tmp/"); 612 | return; 613 | } 614 | profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop); 615 | } 616 | 617 | IActivityManager.WaitResult result = null; 618 | */ 619 | int res; 620 | final long startTime = SystemClock.uptimeMillis(); 621 | ActivityOptions options = null; 622 | /* 623 | if (mStackId != INVALID_STACK_ID) { 624 | options = ActivityOptions.makeBasic(); 625 | options.setLaunchStackId(mStackId); 626 | } 627 | 628 | if (mWaitOption) { 629 | result = mAm.startActivityAndWait(null, null, intent, mimeType, 630 | null, null, 0, mStartFlags, profilerInfo, 631 | options != null ? options.toBundle() : null, mUserId); 632 | res = result.result; 633 | } else { 634 | */ 635 | res = mAm.startActivityAsUser(/*null, null,*/ intent, mimeType, 636 | /*null, null, 0,*/ mStartFlags, /*profilerInfo,*/ 637 | options != null ? options.toBundle() : null, mUserId); 638 | /* 639 | } 640 | */ 641 | final long endTime = SystemClock.uptimeMillis(); 642 | PrintStream out = mWaitOption ? System.out : System.err; 643 | boolean launched = false; 644 | switch (res) { 645 | case ActivityManager.START_SUCCESS: 646 | launched = true; 647 | break; 648 | case ActivityManager.START_SWITCHES_CANCELED: 649 | launched = true; 650 | out.println( 651 | "Warning: Activity not started because the " 652 | + " current activity is being kept for the user."); 653 | break; 654 | case ActivityManager.START_DELIVERED_TO_TOP: 655 | launched = true; 656 | out.println( 657 | "Warning: Activity not started, intent has " 658 | + "been delivered to currently running " 659 | + "top-most instance."); 660 | break; 661 | case ActivityManager.START_RETURN_INTENT_TO_CALLER: 662 | launched = true; 663 | out.println( 664 | "Warning: Activity not started because intent " 665 | + "should be handled by the caller"); 666 | break; 667 | case ActivityManager.START_TASK_TO_FRONT: 668 | launched = true; 669 | out.println( 670 | "Warning: Activity not started, its current " 671 | + "task has been brought to the front"); 672 | break; 673 | case ActivityManager.START_INTENT_NOT_RESOLVED: 674 | out.println( 675 | "Error: Activity not started, unable to " 676 | + "resolve " + intent.toString()); 677 | break; 678 | case ActivityManager.START_CLASS_NOT_FOUND: 679 | out.println(NO_CLASS_ERROR_CODE); 680 | out.println("Error: Activity class " + 681 | intent.getComponent().toShortString() 682 | + " does not exist."); 683 | break; 684 | case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT: 685 | out.println( 686 | "Error: Activity not started, you requested to " 687 | + "both forward and receive its result"); 688 | break; 689 | case ActivityManager.START_PERMISSION_DENIED: 690 | out.println( 691 | "Error: Activity not started, you do not " 692 | + "have permission to access it."); 693 | break; 694 | case ActivityManager.START_NOT_VOICE_COMPATIBLE: 695 | out.println( 696 | "Error: Activity not started, voice control not allowed for: " 697 | + intent); 698 | break; 699 | case ActivityManager.START_NOT_CURRENT_USER_ACTIVITY: 700 | out.println( 701 | "Error: Not allowed to start background user activity" 702 | + " that shouldn't be displayed for all users."); 703 | break; 704 | default: 705 | out.println( 706 | "Error: Activity not started, unknown error code " + res); 707 | break; 708 | } 709 | /* 710 | if (mWaitOption && launched) { 711 | if (result == null) { 712 | result = new IActivityManager.WaitResult(); 713 | result.who = intent.getComponent(); 714 | } 715 | System.out.println("Status: " + (result.timeout ? "timeout" : "ok")); 716 | if (result.who != null) { 717 | System.out.println("Activity: " + result.who.flattenToShortString()); 718 | } 719 | if (result.thisTime >= 0) { 720 | System.out.println("ThisTime: " + result.thisTime); 721 | } 722 | if (result.totalTime >= 0) { 723 | System.out.println("TotalTime: " + result.totalTime); 724 | } 725 | System.out.println("WaitTime: " + (endTime-startTime)); 726 | System.out.println("Complete"); 727 | } 728 | */ 729 | mRepeat--; 730 | /* 731 | if (mRepeat > 1) { 732 | mAm.unhandledBack(); 733 | } 734 | */ 735 | } while (mRepeat > 1); 736 | } 737 | 738 | /* 739 | private void runForceStop() throws Exception { 740 | int userId = UserHandle.USER_ALL; 741 | 742 | String opt; 743 | while ((opt=nextOption()) != null) { 744 | if (opt.equals("--user")) { 745 | userId = parseUserArg(nextArgRequired()); 746 | } else { 747 | System.err.println("Error: Unknown option: " + opt); 748 | return; 749 | } 750 | } 751 | mAm.forceStopPackage(nextArgRequired(), userId); 752 | } 753 | 754 | private void runKill() throws Exception { 755 | int userId = UserHandle.USER_ALL; 756 | 757 | String opt; 758 | while ((opt=nextOption()) != null) { 759 | if (opt.equals("--user")) { 760 | userId = parseUserArg(nextArgRequired()); 761 | } else { 762 | System.err.println("Error: Unknown option: " + opt); 763 | return; 764 | } 765 | } 766 | mAm.killBackgroundProcesses(nextArgRequired(), userId); 767 | } 768 | 769 | private void runKillAll() throws Exception { 770 | mAm.killAllBackgroundProcesses(); 771 | } 772 | */ 773 | 774 | private void sendBroadcast() throws Exception { 775 | Intent intent = makeIntent(); 776 | IntentReceiver receiver = new IntentReceiver(); 777 | String[] requiredPermissions = mReceiverPermission == null ? null 778 | : new String[] {mReceiverPermission}; 779 | System.out.println("Broadcasting: " + intent); 780 | mAm.broadcastIntent(/*null,*/ intent, /*null,*/ receiver, /*0, null, null,*/ requiredPermissions, 781 | /*android.app.AppOpsManager.OP_NONE, null,*/ true, false, mUserId); 782 | receiver.waitForFinish(); 783 | } 784 | 785 | /* 786 | private void runInstrument() throws Exception { 787 | String profileFile = null; 788 | boolean wait = false; 789 | boolean rawMode = false; 790 | boolean no_window_animation = false; 791 | int userId = UserHandle.USER_CURRENT; 792 | Bundle args = new Bundle(); 793 | String argKey = null, argValue = null; 794 | IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); 795 | String abi = null; 796 | 797 | String opt; 798 | while ((opt=nextOption()) != null) { 799 | if (opt.equals("-p")) { 800 | profileFile = nextArgRequired(); 801 | } else if (opt.equals("-w")) { 802 | wait = true; 803 | } else if (opt.equals("-r")) { 804 | rawMode = true; 805 | } else if (opt.equals("-e")) { 806 | argKey = nextArgRequired(); 807 | argValue = nextArgRequired(); 808 | args.putString(argKey, argValue); 809 | } else if (opt.equals("--no_window_animation") 810 | || opt.equals("--no-window-animation")) { 811 | no_window_animation = true; 812 | } else if (opt.equals("--user")) { 813 | userId = parseUserArg(nextArgRequired()); 814 | } else if (opt.equals("--abi")) { 815 | abi = nextArgRequired(); 816 | } else { 817 | System.err.println("Error: Unknown option: " + opt); 818 | return; 819 | } 820 | } 821 | 822 | if (userId == UserHandle.USER_ALL) { 823 | System.err.println("Error: Can't start instrumentation with user 'all'"); 824 | return; 825 | } 826 | 827 | String cnArg = nextArgRequired(); 828 | 829 | ComponentName cn; 830 | if (cnArg.contains("/")) { 831 | cn = ComponentName.unflattenFromString(cnArg); 832 | if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg); 833 | } else { 834 | List infos = mPm.queryInstrumentation(null, 0).getList(); 835 | 836 | final int numInfos = infos == null ? 0: infos.size(); 837 | List cns = new ArrayList<>(); 838 | for (int i = 0; i < numInfos; i++) { 839 | InstrumentationInfo info = infos.get(i); 840 | 841 | ComponentName c = new ComponentName(info.packageName, info.name); 842 | if (cnArg.equals(info.packageName)) { 843 | cns.add(c); 844 | } 845 | } 846 | 847 | if (cns.size() == 0) { 848 | throw new IllegalArgumentException("No instrumentation found for: " + cnArg); 849 | } else if (cns.size() == 1) { 850 | cn = cns.get(0); 851 | } else { 852 | StringBuilder cnsStr = new StringBuilder(); 853 | final int numCns = cns.size(); 854 | for (int i = 0; i < numCns; i++) { 855 | cnsStr.append(cns.get(i).flattenToString()); 856 | cnsStr.append(", "); 857 | } 858 | 859 | // Remove last ", " 860 | cnsStr.setLength(cnsStr.length() - 2); 861 | 862 | throw new IllegalArgumentException("Found multiple instrumentations: " 863 | + cnsStr.toString()); 864 | } 865 | } 866 | 867 | InstrumentationWatcher watcher = null; 868 | UiAutomationConnection connection = null; 869 | if (wait) { 870 | watcher = new InstrumentationWatcher(); 871 | watcher.setRawOutput(rawMode); 872 | connection = new UiAutomationConnection(); 873 | } 874 | 875 | float[] oldAnims = null; 876 | if (no_window_animation) { 877 | oldAnims = wm.getAnimationScales(); 878 | wm.setAnimationScale(0, 0.0f); 879 | wm.setAnimationScale(1, 0.0f); 880 | } 881 | 882 | if (abi != null) { 883 | final String[] supportedAbis = Build.SUPPORTED_ABIS; 884 | boolean matched = false; 885 | for (String supportedAbi : supportedAbis) { 886 | if (supportedAbi.equals(abi)) { 887 | matched = true; 888 | break; 889 | } 890 | } 891 | 892 | if (!matched) { 893 | throw new AndroidException( 894 | "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi); 895 | } 896 | } 897 | 898 | if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, abi)) { 899 | throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString()); 900 | } 901 | 902 | if (watcher != null) { 903 | if (!watcher.waitForFinish()) { 904 | System.out.println("INSTRUMENTATION_ABORTED: System has crashed."); 905 | } 906 | } 907 | 908 | if (oldAnims != null) { 909 | wm.setAnimationScales(oldAnims); 910 | } 911 | } 912 | 913 | private void runTraceIpc() throws Exception { 914 | String op = nextArgRequired(); 915 | if (op.equals("start")) { 916 | runTraceIpcStart(); 917 | } else if (op.equals("stop")) { 918 | runTraceIpcStop(); 919 | } else { 920 | showError("Error: unknown command '" + op + "'"); 921 | return; 922 | } 923 | } 924 | 925 | private void runTraceIpcStart() throws Exception { 926 | System.out.println("Starting IPC tracing."); 927 | mAm.startBinderTracking(); 928 | } 929 | 930 | private void runTraceIpcStop() throws Exception { 931 | String opt; 932 | String filename = null; 933 | while ((opt=nextOption()) != null) { 934 | if (opt.equals("--dump-file")) { 935 | filename = nextArgRequired(); 936 | } else { 937 | System.err.println("Error: Unknown option: " + opt); 938 | return; 939 | } 940 | } 941 | if (filename == null) { 942 | System.err.println("Error: Specify filename to dump logs to."); 943 | return; 944 | } 945 | 946 | ParcelFileDescriptor fd = null; 947 | 948 | try { 949 | File file = new File(filename); 950 | file.delete(); 951 | fd = openForSystemServer(file, 952 | ParcelFileDescriptor.MODE_CREATE | 953 | ParcelFileDescriptor.MODE_TRUNCATE | 954 | ParcelFileDescriptor.MODE_WRITE_ONLY); 955 | } catch (FileNotFoundException e) { 956 | System.err.println("Error: Unable to open file: " + filename); 957 | System.err.println("Consider using a file under /data/local/tmp/"); 958 | return; 959 | } 960 | 961 | ; 962 | if (!mAm.stopBinderTrackingAndDump(fd)) { 963 | throw new AndroidException("STOP TRACE FAILED."); 964 | } 965 | 966 | System.out.println("Stopped IPC tracing. Dumping logs to: " + filename); 967 | } 968 | 969 | static void removeWallOption() { 970 | String props = SystemProperties.get("dalvik.vm.extra-opts"); 971 | if (props != null && props.contains("-Xprofile:wallclock")) { 972 | props = props.replace("-Xprofile:wallclock", ""); 973 | props = props.trim(); 974 | SystemProperties.set("dalvik.vm.extra-opts", props); 975 | } 976 | } 977 | 978 | private void runProfile() throws Exception { 979 | String profileFile = null; 980 | boolean start = false; 981 | boolean wall = false; 982 | int userId = UserHandle.USER_CURRENT; 983 | int profileType = 0; 984 | mSamplingInterval = 0; 985 | 986 | String process = null; 987 | 988 | String cmd = nextArgRequired(); 989 | 990 | if ("start".equals(cmd)) { 991 | start = true; 992 | String opt; 993 | while ((opt=nextOption()) != null) { 994 | if (opt.equals("--user")) { 995 | userId = parseUserArg(nextArgRequired()); 996 | } else if (opt.equals("--wall")) { 997 | wall = true; 998 | } else if (opt.equals("--sampling")) { 999 | mSamplingInterval = Integer.parseInt(nextArgRequired()); 1000 | } else { 1001 | System.err.println("Error: Unknown option: " + opt); 1002 | return; 1003 | } 1004 | } 1005 | process = nextArgRequired(); 1006 | } else if ("stop".equals(cmd)) { 1007 | String opt; 1008 | while ((opt=nextOption()) != null) { 1009 | if (opt.equals("--user")) { 1010 | userId = parseUserArg(nextArgRequired()); 1011 | } else { 1012 | System.err.println("Error: Unknown option: " + opt); 1013 | return; 1014 | } 1015 | } 1016 | process = nextArg(); 1017 | } else { 1018 | // Compatibility with old syntax: process is specified first. 1019 | process = cmd; 1020 | cmd = nextArgRequired(); 1021 | if ("start".equals(cmd)) { 1022 | start = true; 1023 | } else if (!"stop".equals(cmd)) { 1024 | throw new IllegalArgumentException("Profile command " + process + " not valid"); 1025 | } 1026 | } 1027 | 1028 | if (userId == UserHandle.USER_ALL) { 1029 | System.err.println("Error: Can't profile with user 'all'"); 1030 | return; 1031 | } 1032 | 1033 | ParcelFileDescriptor fd = null; 1034 | ProfilerInfo profilerInfo = null; 1035 | 1036 | if (start) { 1037 | profileFile = nextArgRequired(); 1038 | try { 1039 | fd = openForSystemServer( 1040 | new File(profileFile), 1041 | ParcelFileDescriptor.MODE_CREATE | 1042 | ParcelFileDescriptor.MODE_TRUNCATE | 1043 | ParcelFileDescriptor.MODE_WRITE_ONLY); 1044 | } catch (FileNotFoundException e) { 1045 | System.err.println("Error: Unable to open file: " + profileFile); 1046 | System.err.println("Consider using a file under /data/local/tmp/"); 1047 | return; 1048 | } 1049 | profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false); 1050 | } 1051 | 1052 | try { 1053 | if (wall) { 1054 | // XXX doesn't work -- this needs to be set before booting. 1055 | String props = SystemProperties.get("dalvik.vm.extra-opts"); 1056 | if (props == null || !props.contains("-Xprofile:wallclock")) { 1057 | props = props + " -Xprofile:wallclock"; 1058 | //SystemProperties.set("dalvik.vm.extra-opts", props); 1059 | } 1060 | } else if (start) { 1061 | //removeWallOption(); 1062 | } 1063 | if (!mAm.profileControl(process, userId, start, profilerInfo, profileType)) { 1064 | wall = false; 1065 | throw new AndroidException("PROFILE FAILED on process " + process); 1066 | } 1067 | } finally { 1068 | if (!wall) { 1069 | //removeWallOption(); 1070 | } 1071 | } 1072 | } 1073 | 1074 | private void runDumpHeap() throws Exception { 1075 | boolean managed = true; 1076 | int userId = UserHandle.USER_CURRENT; 1077 | 1078 | String opt; 1079 | while ((opt=nextOption()) != null) { 1080 | if (opt.equals("--user")) { 1081 | userId = parseUserArg(nextArgRequired()); 1082 | if (userId == UserHandle.USER_ALL) { 1083 | System.err.println("Error: Can't dump heap with user 'all'"); 1084 | return; 1085 | } 1086 | } else if (opt.equals("-n")) { 1087 | managed = false; 1088 | } else { 1089 | System.err.println("Error: Unknown option: " + opt); 1090 | return; 1091 | } 1092 | } 1093 | String process = nextArgRequired(); 1094 | String heapFile = nextArgRequired(); 1095 | ParcelFileDescriptor fd = null; 1096 | 1097 | try { 1098 | File file = new File(heapFile); 1099 | file.delete(); 1100 | fd = openForSystemServer(file, 1101 | ParcelFileDescriptor.MODE_CREATE | 1102 | ParcelFileDescriptor.MODE_TRUNCATE | 1103 | ParcelFileDescriptor.MODE_WRITE_ONLY); 1104 | } catch (FileNotFoundException e) { 1105 | System.err.println("Error: Unable to open file: " + heapFile); 1106 | System.err.println("Consider using a file under /data/local/tmp/"); 1107 | return; 1108 | } 1109 | 1110 | if (!mAm.dumpHeap(process, userId, managed, heapFile, fd)) { 1111 | throw new AndroidException("HEAP DUMP FAILED on process " + process); 1112 | } 1113 | } 1114 | 1115 | private void runSetDebugApp() throws Exception { 1116 | boolean wait = false; 1117 | boolean persistent = false; 1118 | 1119 | String opt; 1120 | while ((opt=nextOption()) != null) { 1121 | if (opt.equals("-w")) { 1122 | wait = true; 1123 | } else if (opt.equals("--persistent")) { 1124 | persistent = true; 1125 | } else { 1126 | System.err.println("Error: Unknown option: " + opt); 1127 | return; 1128 | } 1129 | } 1130 | 1131 | String pkg = nextArgRequired(); 1132 | mAm.setDebugApp(pkg, wait, persistent); 1133 | } 1134 | 1135 | private void runClearDebugApp() throws Exception { 1136 | mAm.setDebugApp(null, false, true); 1137 | } 1138 | 1139 | private void runSetWatchHeap() throws Exception { 1140 | String proc = nextArgRequired(); 1141 | String limit = nextArgRequired(); 1142 | mAm.setDumpHeapDebugLimit(proc, 0, Long.parseLong(limit), null); 1143 | } 1144 | 1145 | private void runClearWatchHeap() throws Exception { 1146 | String proc = nextArgRequired(); 1147 | mAm.setDumpHeapDebugLimit(proc, 0, -1, null); 1148 | } 1149 | 1150 | private void runBugReport() throws Exception { 1151 | String opt; 1152 | int bugreportType = ActivityManager.BUGREPORT_OPTION_FULL; 1153 | while ((opt=nextOption()) != null) { 1154 | if (opt.equals("--progress")) { 1155 | bugreportType = ActivityManager.BUGREPORT_OPTION_INTERACTIVE; 1156 | } else { 1157 | System.err.println("Error: Unknown option: " + opt); 1158 | return; 1159 | } 1160 | } 1161 | mAm.requestBugReport(bugreportType); 1162 | System.out.println("Your lovely bug report is being created; please be patient."); 1163 | } 1164 | 1165 | private void runSwitchUser() throws Exception { 1166 | String user = nextArgRequired(); 1167 | mAm.switchUser(Integer.parseInt(user)); 1168 | } 1169 | 1170 | private void runStartUserInBackground() throws Exception { 1171 | String user = nextArgRequired(); 1172 | boolean success = mAm.startUserInBackground(Integer.parseInt(user)); 1173 | if (success) { 1174 | System.out.println("Success: user started"); 1175 | } else { 1176 | System.err.println("Error: could not start user"); 1177 | } 1178 | } 1179 | 1180 | private byte[] argToBytes(String arg) { 1181 | if (arg.equals("!")) { 1182 | return null; 1183 | } else { 1184 | return HexDump.hexStringToByteArray(arg); 1185 | } 1186 | } 1187 | 1188 | private void runUnlockUser() throws Exception { 1189 | int userId = Integer.parseInt(nextArgRequired()); 1190 | byte[] token = argToBytes(nextArgRequired()); 1191 | byte[] secret = argToBytes(nextArgRequired()); 1192 | boolean success = mAm.unlockUser(userId, token, secret, null); 1193 | if (success) { 1194 | System.out.println("Success: user unlocked"); 1195 | } else { 1196 | System.err.println("Error: could not unlock user"); 1197 | } 1198 | } 1199 | 1200 | private static class StopUserCallback extends IStopUserCallback.Stub { 1201 | private boolean mFinished = false; 1202 | 1203 | public synchronized void waitForFinish() { 1204 | try { 1205 | while (!mFinished) wait(); 1206 | } catch (InterruptedException e) { 1207 | throw new IllegalStateException(e); 1208 | } 1209 | } 1210 | 1211 | @Override 1212 | public synchronized void userStopped(int userId) { 1213 | mFinished = true; 1214 | notifyAll(); 1215 | } 1216 | 1217 | @Override 1218 | public synchronized void userStopAborted(int userId) { 1219 | mFinished = true; 1220 | notifyAll(); 1221 | } 1222 | } 1223 | 1224 | private void runStopUser() throws Exception { 1225 | boolean wait = false; 1226 | boolean force = false; 1227 | String opt; 1228 | while ((opt = nextOption()) != null) { 1229 | if ("-w".equals(opt)) { 1230 | wait = true; 1231 | } else if ("-f".equals(opt)) { 1232 | force = true; 1233 | } else { 1234 | System.err.println("Error: unknown option: " + opt); 1235 | return; 1236 | } 1237 | } 1238 | int user = Integer.parseInt(nextArgRequired()); 1239 | StopUserCallback callback = wait ? new StopUserCallback() : null; 1240 | 1241 | int res = mAm.stopUser(user, force, callback); 1242 | if (res != ActivityManager.USER_OP_SUCCESS) { 1243 | String txt = ""; 1244 | switch (res) { 1245 | case ActivityManager.USER_OP_IS_CURRENT: 1246 | txt = " (Can't stop current user)"; 1247 | break; 1248 | case ActivityManager.USER_OP_UNKNOWN_USER: 1249 | txt = " (Unknown user " + user + ")"; 1250 | break; 1251 | case ActivityManager.USER_OP_ERROR_IS_SYSTEM: 1252 | txt = " (System user cannot be stopped)"; 1253 | break; 1254 | case ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP: 1255 | txt = " (Can't stop user " + user 1256 | + " - one of its related users can't be stopped)"; 1257 | break; 1258 | } 1259 | System.err.println("Switch failed: " + res + txt); 1260 | } else if (callback != null) { 1261 | callback.waitForFinish(); 1262 | } 1263 | } 1264 | 1265 | class MyActivityController extends IActivityController.Stub { 1266 | final String mGdbPort; 1267 | final boolean mMonkey; 1268 | 1269 | static final int STATE_NORMAL = 0; 1270 | static final int STATE_CRASHED = 1; 1271 | static final int STATE_EARLY_ANR = 2; 1272 | static final int STATE_ANR = 3; 1273 | 1274 | int mState; 1275 | 1276 | static final int RESULT_DEFAULT = 0; 1277 | 1278 | static final int RESULT_CRASH_DIALOG = 0; 1279 | static final int RESULT_CRASH_KILL = 1; 1280 | 1281 | static final int RESULT_EARLY_ANR_CONTINUE = 0; 1282 | static final int RESULT_EARLY_ANR_KILL = 1; 1283 | 1284 | static final int RESULT_ANR_DIALOG = 0; 1285 | static final int RESULT_ANR_KILL = 1; 1286 | static final int RESULT_ANR_WAIT = 1; 1287 | 1288 | int mResult; 1289 | 1290 | Process mGdbProcess; 1291 | Thread mGdbThread; 1292 | boolean mGotGdbPrint; 1293 | 1294 | MyActivityController(String gdbPort, boolean monkey) { 1295 | mGdbPort = gdbPort; 1296 | mMonkey = monkey; 1297 | } 1298 | 1299 | @Override 1300 | public boolean activityResuming(String pkg) { 1301 | synchronized (this) { 1302 | System.out.println("** Activity resuming: " + pkg); 1303 | } 1304 | return true; 1305 | } 1306 | 1307 | @Override 1308 | public boolean activityStarting(Intent intent, String pkg) { 1309 | synchronized (this) { 1310 | System.out.println("** Activity starting: " + pkg); 1311 | } 1312 | return true; 1313 | } 1314 | 1315 | @Override 1316 | public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg, 1317 | long timeMillis, String stackTrace) { 1318 | synchronized (this) { 1319 | System.out.println("** ERROR: PROCESS CRASHED"); 1320 | System.out.println("processName: " + processName); 1321 | System.out.println("processPid: " + pid); 1322 | System.out.println("shortMsg: " + shortMsg); 1323 | System.out.println("longMsg: " + longMsg); 1324 | System.out.println("timeMillis: " + timeMillis); 1325 | System.out.println("stack:"); 1326 | System.out.print(stackTrace); 1327 | System.out.println("#"); 1328 | int result = waitControllerLocked(pid, STATE_CRASHED); 1329 | return result == RESULT_CRASH_KILL ? false : true; 1330 | } 1331 | } 1332 | 1333 | @Override 1334 | public int appEarlyNotResponding(String processName, int pid, String annotation) { 1335 | synchronized (this) { 1336 | System.out.println("** ERROR: EARLY PROCESS NOT RESPONDING"); 1337 | System.out.println("processName: " + processName); 1338 | System.out.println("processPid: " + pid); 1339 | System.out.println("annotation: " + annotation); 1340 | int result = waitControllerLocked(pid, STATE_EARLY_ANR); 1341 | if (result == RESULT_EARLY_ANR_KILL) return -1; 1342 | return 0; 1343 | } 1344 | } 1345 | 1346 | @Override 1347 | public int appNotResponding(String processName, int pid, String processStats) { 1348 | synchronized (this) { 1349 | System.out.println("** ERROR: PROCESS NOT RESPONDING"); 1350 | System.out.println("processName: " + processName); 1351 | System.out.println("processPid: " + pid); 1352 | System.out.println("processStats:"); 1353 | System.out.print(processStats); 1354 | System.out.println("#"); 1355 | int result = waitControllerLocked(pid, STATE_ANR); 1356 | if (result == RESULT_ANR_KILL) return -1; 1357 | if (result == RESULT_ANR_WAIT) return 1; 1358 | return 0; 1359 | } 1360 | } 1361 | 1362 | @Override 1363 | public int systemNotResponding(String message) { 1364 | synchronized (this) { 1365 | System.out.println("** ERROR: PROCESS NOT RESPONDING"); 1366 | System.out.println("message: " + message); 1367 | System.out.println("#"); 1368 | System.out.println("Allowing system to die."); 1369 | return -1; 1370 | } 1371 | } 1372 | 1373 | void killGdbLocked() { 1374 | mGotGdbPrint = false; 1375 | if (mGdbProcess != null) { 1376 | System.out.println("Stopping gdbserver"); 1377 | mGdbProcess.destroy(); 1378 | mGdbProcess = null; 1379 | } 1380 | if (mGdbThread != null) { 1381 | mGdbThread.interrupt(); 1382 | mGdbThread = null; 1383 | } 1384 | } 1385 | 1386 | int waitControllerLocked(int pid, int state) { 1387 | if (mGdbPort != null) { 1388 | killGdbLocked(); 1389 | 1390 | try { 1391 | System.out.println("Starting gdbserver on port " + mGdbPort); 1392 | System.out.println("Do the following:"); 1393 | System.out.println(" adb forward tcp:" + mGdbPort + " tcp:" + mGdbPort); 1394 | System.out.println(" gdbclient app_process :" + mGdbPort); 1395 | 1396 | mGdbProcess = Runtime.getRuntime().exec(new String[] { 1397 | "gdbserver", ":" + mGdbPort, "--attach", Integer.toString(pid) 1398 | }); 1399 | final InputStreamReader converter = new InputStreamReader( 1400 | mGdbProcess.getInputStream()); 1401 | mGdbThread = new Thread() { 1402 | @Override 1403 | public void run() { 1404 | BufferedReader in = new BufferedReader(converter); 1405 | String line; 1406 | int count = 0; 1407 | while (true) { 1408 | synchronized (MyActivityController.this) { 1409 | if (mGdbThread == null) { 1410 | return; 1411 | } 1412 | if (count == 2) { 1413 | mGotGdbPrint = true; 1414 | MyActivityController.this.notifyAll(); 1415 | } 1416 | } 1417 | try { 1418 | line = in.readLine(); 1419 | if (line == null) { 1420 | return; 1421 | } 1422 | System.out.println("GDB: " + line); 1423 | count++; 1424 | } catch (IOException e) { 1425 | return; 1426 | } 1427 | } 1428 | } 1429 | }; 1430 | mGdbThread.start(); 1431 | 1432 | // Stupid waiting for .5s. Doesn't matter if we end early. 1433 | try { 1434 | this.wait(500); 1435 | } catch (InterruptedException e) { 1436 | } 1437 | 1438 | } catch (IOException e) { 1439 | System.err.println("Failure starting gdbserver: " + e); 1440 | killGdbLocked(); 1441 | } 1442 | } 1443 | mState = state; 1444 | System.out.println(""); 1445 | printMessageForState(); 1446 | 1447 | while (mState != STATE_NORMAL) { 1448 | try { 1449 | wait(); 1450 | } catch (InterruptedException e) { 1451 | } 1452 | } 1453 | 1454 | killGdbLocked(); 1455 | 1456 | return mResult; 1457 | } 1458 | 1459 | void resumeController(int result) { 1460 | synchronized (this) { 1461 | mState = STATE_NORMAL; 1462 | mResult = result; 1463 | notifyAll(); 1464 | } 1465 | } 1466 | 1467 | void printMessageForState() { 1468 | switch (mState) { 1469 | case STATE_NORMAL: 1470 | System.out.println("Monitoring activity manager... available commands:"); 1471 | break; 1472 | case STATE_CRASHED: 1473 | System.out.println("Waiting after crash... available commands:"); 1474 | System.out.println("(c)ontinue: show crash dialog"); 1475 | System.out.println("(k)ill: immediately kill app"); 1476 | break; 1477 | case STATE_EARLY_ANR: 1478 | System.out.println("Waiting after early ANR... available commands:"); 1479 | System.out.println("(c)ontinue: standard ANR processing"); 1480 | System.out.println("(k)ill: immediately kill app"); 1481 | break; 1482 | case STATE_ANR: 1483 | System.out.println("Waiting after ANR... available commands:"); 1484 | System.out.println("(c)ontinue: show ANR dialog"); 1485 | System.out.println("(k)ill: immediately kill app"); 1486 | System.out.println("(w)ait: wait some more"); 1487 | break; 1488 | } 1489 | System.out.println("(q)uit: finish monitoring"); 1490 | } 1491 | 1492 | void run() throws RemoteException { 1493 | try { 1494 | printMessageForState(); 1495 | 1496 | mAm.setActivityController(this, mMonkey); 1497 | mState = STATE_NORMAL; 1498 | 1499 | InputStreamReader converter = new InputStreamReader(System.in); 1500 | BufferedReader in = new BufferedReader(converter); 1501 | String line; 1502 | 1503 | while ((line = in.readLine()) != null) { 1504 | boolean addNewline = true; 1505 | if (line.length() <= 0) { 1506 | addNewline = false; 1507 | } else if ("q".equals(line) || "quit".equals(line)) { 1508 | resumeController(RESULT_DEFAULT); 1509 | break; 1510 | } else if (mState == STATE_CRASHED) { 1511 | if ("c".equals(line) || "continue".equals(line)) { 1512 | resumeController(RESULT_CRASH_DIALOG); 1513 | } else if ("k".equals(line) || "kill".equals(line)) { 1514 | resumeController(RESULT_CRASH_KILL); 1515 | } else { 1516 | System.out.println("Invalid command: " + line); 1517 | } 1518 | } else if (mState == STATE_ANR) { 1519 | if ("c".equals(line) || "continue".equals(line)) { 1520 | resumeController(RESULT_ANR_DIALOG); 1521 | } else if ("k".equals(line) || "kill".equals(line)) { 1522 | resumeController(RESULT_ANR_KILL); 1523 | } else if ("w".equals(line) || "wait".equals(line)) { 1524 | resumeController(RESULT_ANR_WAIT); 1525 | } else { 1526 | System.out.println("Invalid command: " + line); 1527 | } 1528 | } else if (mState == STATE_EARLY_ANR) { 1529 | if ("c".equals(line) || "continue".equals(line)) { 1530 | resumeController(RESULT_EARLY_ANR_CONTINUE); 1531 | } else if ("k".equals(line) || "kill".equals(line)) { 1532 | resumeController(RESULT_EARLY_ANR_KILL); 1533 | } else { 1534 | System.out.println("Invalid command: " + line); 1535 | } 1536 | } else { 1537 | System.out.println("Invalid command: " + line); 1538 | } 1539 | 1540 | synchronized (this) { 1541 | if (addNewline) { 1542 | System.out.println(""); 1543 | } 1544 | printMessageForState(); 1545 | } 1546 | } 1547 | 1548 | } catch (IOException e) { 1549 | e.printStackTrace(); 1550 | } finally { 1551 | mAm.setActivityController(null, mMonkey); 1552 | } 1553 | } 1554 | } 1555 | 1556 | private void runMonitor() throws Exception { 1557 | String opt; 1558 | String gdbPort = null; 1559 | boolean monkey = false; 1560 | while ((opt=nextOption()) != null) { 1561 | if (opt.equals("--gdb")) { 1562 | gdbPort = nextArgRequired(); 1563 | } else if (opt.equals("-m")) { 1564 | monkey = true; 1565 | } else { 1566 | System.err.println("Error: Unknown option: " + opt); 1567 | return; 1568 | } 1569 | } 1570 | 1571 | MyActivityController controller = new MyActivityController(gdbPort, monkey); 1572 | controller.run(); 1573 | } 1574 | 1575 | private void runHang() throws Exception { 1576 | String opt; 1577 | boolean allowRestart = false; 1578 | while ((opt=nextOption()) != null) { 1579 | if (opt.equals("--allow-restart")) { 1580 | allowRestart = true; 1581 | } else { 1582 | System.err.println("Error: Unknown option: " + opt); 1583 | return; 1584 | } 1585 | } 1586 | 1587 | System.out.println("Hanging the system..."); 1588 | mAm.hang(new Binder(), allowRestart); 1589 | } 1590 | 1591 | private void runRestart() throws Exception { 1592 | String opt; 1593 | while ((opt=nextOption()) != null) { 1594 | System.err.println("Error: Unknown option: " + opt); 1595 | return; 1596 | } 1597 | 1598 | System.out.println("Restart the system..."); 1599 | mAm.restart(); 1600 | } 1601 | 1602 | private void runIdleMaintenance() throws Exception { 1603 | String opt; 1604 | while ((opt=nextOption()) != null) { 1605 | System.err.println("Error: Unknown option: " + opt); 1606 | return; 1607 | } 1608 | 1609 | System.out.println("Performing idle maintenance..."); 1610 | try { 1611 | mAm.sendIdleJobTrigger(); 1612 | } catch (RemoteException e) { 1613 | } 1614 | } 1615 | 1616 | private void runScreenCompat() throws Exception { 1617 | String mode = nextArgRequired(); 1618 | boolean enabled; 1619 | if ("on".equals(mode)) { 1620 | enabled = true; 1621 | } else if ("off".equals(mode)) { 1622 | enabled = false; 1623 | } else { 1624 | System.err.println("Error: enabled mode must be 'on' or 'off' at " + mode); 1625 | return; 1626 | } 1627 | 1628 | String packageName = nextArgRequired(); 1629 | do { 1630 | try { 1631 | mAm.setPackageScreenCompatMode(packageName, enabled 1632 | ? ActivityManager.COMPAT_MODE_ENABLED 1633 | : ActivityManager.COMPAT_MODE_DISABLED); 1634 | } catch (RemoteException e) { 1635 | } 1636 | packageName = nextArg(); 1637 | } while (packageName != null); 1638 | } 1639 | 1640 | private void runPackageImportance() throws Exception { 1641 | String packageName = nextArgRequired(); 1642 | try { 1643 | int procState = mAm.getPackageProcessState(packageName, "com.android.shell"); 1644 | System.out.println( 1645 | ActivityManager.RunningAppProcessInfo.procStateToImportance(procState)); 1646 | } catch (RemoteException e) { 1647 | } 1648 | }*/ 1649 | 1650 | private void runToUri(int flags) throws Exception { 1651 | Intent intent = makeIntent(); 1652 | System.out.println(intent.toUri(flags)); 1653 | } 1654 | 1655 | private class IntentReceiver extends IIntentReceiver.Stub { 1656 | private boolean mFinished = false; 1657 | 1658 | @Override 1659 | public void performReceive(Intent intent, int resultCode, String data, Bundle extras, 1660 | boolean ordered, boolean sticky, int sendingUser) { 1661 | String line = "Broadcast completed: result=" + resultCode; 1662 | if (data != null) line = line + ", data=\"" + data + "\""; 1663 | if (extras != null) line = line + ", extras: " + extras; 1664 | System.out.println(line); 1665 | synchronized (this) { 1666 | mFinished = true; 1667 | notifyAll(); 1668 | } 1669 | } 1670 | 1671 | public synchronized void waitForFinish() { 1672 | try { 1673 | while (!mFinished) wait(); 1674 | } catch (InterruptedException e) { 1675 | throw new IllegalStateException(e); 1676 | } 1677 | } 1678 | } 1679 | 1680 | /* 1681 | private class InstrumentationWatcher extends IInstrumentationWatcher.Stub { 1682 | private boolean mFinished = false; 1683 | private boolean mRawMode = false; 1684 | 1685 | // * 1686 | // * Set or reset "raw mode". In "raw mode", all bundles are dumped. In "pretty mode", 1687 | // * if a bundle includes Instrumentation.REPORT_KEY_STREAMRESULT, just print that. 1688 | // * @param rawMode true for raw mode, false for pretty mode. 1689 | // * 1690 | public void setRawOutput(boolean rawMode) { 1691 | mRawMode = rawMode; 1692 | } 1693 | 1694 | @Override 1695 | public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) { 1696 | synchronized (this) { 1697 | // pretty printer mode? 1698 | String pretty = null; 1699 | if (!mRawMode && results != null) { 1700 | pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT); 1701 | } 1702 | if (pretty != null) { 1703 | System.out.print(pretty); 1704 | } else { 1705 | if (results != null) { 1706 | for (String key : results.keySet()) { 1707 | System.out.println( 1708 | "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key)); 1709 | } 1710 | } 1711 | System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode); 1712 | } 1713 | notifyAll(); 1714 | } 1715 | } 1716 | 1717 | @Override 1718 | public void instrumentationFinished(ComponentName name, int resultCode, 1719 | Bundle results) { 1720 | synchronized (this) { 1721 | // pretty printer mode? 1722 | String pretty = null; 1723 | if (!mRawMode && results != null) { 1724 | pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT); 1725 | } 1726 | if (pretty != null) { 1727 | System.out.println(pretty); 1728 | } else { 1729 | if (results != null) { 1730 | for (String key : results.keySet()) { 1731 | System.out.println( 1732 | "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key)); 1733 | } 1734 | } 1735 | System.out.println("INSTRUMENTATION_CODE: " + resultCode); 1736 | } 1737 | mFinished = true; 1738 | notifyAll(); 1739 | } 1740 | } 1741 | 1742 | public boolean waitForFinish() { 1743 | synchronized (this) { 1744 | while (!mFinished) { 1745 | try { 1746 | if (!mAm.asBinder().pingBinder()) { 1747 | return false; 1748 | } 1749 | wait(1000); 1750 | } catch (InterruptedException e) { 1751 | throw new IllegalStateException(e); 1752 | } 1753 | } 1754 | } 1755 | return true; 1756 | } 1757 | } 1758 | 1759 | private void runStack() throws Exception { 1760 | String op = nextArgRequired(); 1761 | switch (op) { 1762 | case "start": 1763 | runStackStart(); 1764 | break; 1765 | case "movetask": 1766 | runStackMoveTask(); 1767 | break; 1768 | case "resize": 1769 | runStackResize(); 1770 | break; 1771 | case "resize-animated": 1772 | runStackResizeAnimated(); 1773 | break; 1774 | case "resize-docked-stack": 1775 | runStackResizeDocked(); 1776 | break; 1777 | case "positiontask": 1778 | runStackPositionTask(); 1779 | break; 1780 | case "list": 1781 | runStackList(); 1782 | break; 1783 | case "info": 1784 | runStackInfo(); 1785 | break; 1786 | case "move-top-activity-to-pinned-stack": 1787 | runMoveTopActivityToPinnedStack(); 1788 | break; 1789 | case "size-docked-stack-test": 1790 | runStackSizeDockedStackTest(); 1791 | break; 1792 | case "remove": 1793 | runStackRemove(); 1794 | break; 1795 | default: 1796 | showError("Error: unknown command '" + op + "'"); 1797 | break; 1798 | } 1799 | } 1800 | 1801 | private void runStackStart() throws Exception { 1802 | String displayIdStr = nextArgRequired(); 1803 | int displayId = Integer.parseInt(displayIdStr); 1804 | Intent intent = makeIntent(UserHandle.USER_CURRENT); 1805 | 1806 | try { 1807 | IActivityContainer container = mAm.createStackOnDisplay(displayId); 1808 | if (container != null) { 1809 | container.startActivity(intent); 1810 | } 1811 | } catch (RemoteException e) { 1812 | } 1813 | } 1814 | 1815 | private void runStackMoveTask() throws Exception { 1816 | String taskIdStr = nextArgRequired(); 1817 | int taskId = Integer.parseInt(taskIdStr); 1818 | String stackIdStr = nextArgRequired(); 1819 | int stackId = Integer.parseInt(stackIdStr); 1820 | String toTopStr = nextArgRequired(); 1821 | final boolean toTop; 1822 | if ("true".equals(toTopStr)) { 1823 | toTop = true; 1824 | } else if ("false".equals(toTopStr)) { 1825 | toTop = false; 1826 | } else { 1827 | System.err.println("Error: bad toTop arg: " + toTopStr); 1828 | return; 1829 | } 1830 | 1831 | try { 1832 | mAm.moveTaskToStack(taskId, stackId, toTop); 1833 | } catch (RemoteException e) { 1834 | } 1835 | } 1836 | 1837 | private void runStackResize() throws Exception { 1838 | String stackIdStr = nextArgRequired(); 1839 | int stackId = Integer.parseInt(stackIdStr); 1840 | final Rect bounds = getBounds(); 1841 | if (bounds == null) { 1842 | System.err.println("Error: invalid input bounds"); 1843 | return; 1844 | } 1845 | resizeStack(stackId, bounds, 0); 1846 | } 1847 | 1848 | private void runStackResizeAnimated() throws Exception { 1849 | String stackIdStr = nextArgRequired(); 1850 | int stackId = Integer.parseInt(stackIdStr); 1851 | final Rect bounds; 1852 | if ("null".equals(mArgs.peekNextArg())) { 1853 | bounds = null; 1854 | } else { 1855 | bounds = getBounds(); 1856 | if (bounds == null) { 1857 | System.err.println("Error: invalid input bounds"); 1858 | return; 1859 | } 1860 | } 1861 | resizeStackUnchecked(stackId, bounds, 0, true); 1862 | } 1863 | 1864 | private void resizeStackUnchecked(int stackId, Rect bounds, int delayMs, boolean animate) { 1865 | try { 1866 | mAm.resizeStack(stackId, bounds, false, false, animate, -1); 1867 | Thread.sleep(delayMs); 1868 | } catch (RemoteException e) { 1869 | showError("Error: resizing stack " + e); 1870 | } catch (InterruptedException e) { 1871 | } 1872 | } 1873 | 1874 | private void runStackResizeDocked() throws Exception { 1875 | final Rect bounds = getBounds(); 1876 | final Rect taskBounds = getBounds(); 1877 | if (bounds == null || taskBounds == null) { 1878 | System.err.println("Error: invalid input bounds"); 1879 | return; 1880 | } 1881 | try { 1882 | mAm.resizeDockedStack(bounds, taskBounds, null, null, null); 1883 | } catch (RemoteException e) { 1884 | showError("Error: resizing docked stack " + e); 1885 | } 1886 | } 1887 | 1888 | private void resizeStack(int stackId, Rect bounds, int delayMs) 1889 | throws Exception { 1890 | if (bounds == null) { 1891 | showError("Error: invalid input bounds"); 1892 | return; 1893 | } 1894 | resizeStackUnchecked(stackId, bounds, delayMs, false); 1895 | } 1896 | 1897 | private void runStackPositionTask() throws Exception { 1898 | String taskIdStr = nextArgRequired(); 1899 | int taskId = Integer.parseInt(taskIdStr); 1900 | String stackIdStr = nextArgRequired(); 1901 | int stackId = Integer.parseInt(stackIdStr); 1902 | String positionStr = nextArgRequired(); 1903 | int position = Integer.parseInt(positionStr); 1904 | 1905 | try { 1906 | mAm.positionTaskInStack(taskId, stackId, position); 1907 | } catch (RemoteException e) { 1908 | } 1909 | } 1910 | 1911 | private void runStackList() throws Exception { 1912 | try { 1913 | List stacks = mAm.getAllStackInfos(); 1914 | for (StackInfo info : stacks) { 1915 | System.out.println(info); 1916 | } 1917 | } catch (RemoteException e) { 1918 | } 1919 | } 1920 | 1921 | private void runStackInfo() throws Exception { 1922 | try { 1923 | String stackIdStr = nextArgRequired(); 1924 | int stackId = Integer.parseInt(stackIdStr); 1925 | StackInfo info = mAm.getStackInfo(stackId); 1926 | System.out.println(info); 1927 | } catch (RemoteException e) { 1928 | } 1929 | } 1930 | 1931 | private void runStackRemove() throws Exception { 1932 | String stackIdStr = nextArgRequired(); 1933 | int stackId = Integer.parseInt(stackIdStr); 1934 | mAm.removeStack(stackId); 1935 | } 1936 | 1937 | private void runMoveTopActivityToPinnedStack() throws Exception { 1938 | int stackId = Integer.parseInt(nextArgRequired()); 1939 | final Rect bounds = getBounds(); 1940 | if (bounds == null) { 1941 | System.err.println("Error: invalid input bounds"); 1942 | return; 1943 | } 1944 | 1945 | try { 1946 | if (!mAm.moveTopActivityToPinnedStack(stackId, bounds)) { 1947 | showError("Didn't move top activity to pinned stack."); 1948 | } 1949 | } catch (RemoteException e) { 1950 | showError("Unable to move top activity: " + e); 1951 | return; 1952 | } 1953 | } 1954 | 1955 | private void runStackSizeDockedStackTest() throws Exception { 1956 | final int stepSize = Integer.parseInt(nextArgRequired()); 1957 | final String side = nextArgRequired(); 1958 | final String delayStr = nextArg(); 1959 | final int delayMs = (delayStr != null) ? Integer.parseInt(delayStr) : 0; 1960 | 1961 | Rect bounds; 1962 | try { 1963 | StackInfo info = mAm.getStackInfo(DOCKED_STACK_ID); 1964 | if (info == null) { 1965 | showError("Docked stack doesn't exist"); 1966 | return; 1967 | } 1968 | if (info.bounds == null) { 1969 | showError("Docked stack doesn't have a bounds"); 1970 | return; 1971 | } 1972 | bounds = info.bounds; 1973 | } catch (RemoteException e) { 1974 | showError("Unable to get docked stack info:" + e); 1975 | return; 1976 | } 1977 | 1978 | final boolean horizontalGrowth = "l".equals(side) || "r".equals(side); 1979 | final int changeSize = (horizontalGrowth ? bounds.width() : bounds.height()) / 2; 1980 | int currentPoint; 1981 | switch (side) { 1982 | case "l": 1983 | currentPoint = bounds.left; 1984 | break; 1985 | case "r": 1986 | currentPoint = bounds.right; 1987 | break; 1988 | case "t": 1989 | currentPoint = bounds.top; 1990 | break; 1991 | case "b": 1992 | currentPoint = bounds.bottom; 1993 | break; 1994 | default: 1995 | showError("Unknown growth side: " + side); 1996 | return; 1997 | } 1998 | 1999 | final int startPoint = currentPoint; 2000 | final int minPoint = currentPoint - changeSize; 2001 | final int maxPoint = currentPoint + changeSize; 2002 | 2003 | int maxChange; 2004 | System.out.println("Shrinking docked stack side=" + side); 2005 | while (currentPoint > minPoint) { 2006 | maxChange = Math.min(stepSize, currentPoint - minPoint); 2007 | currentPoint -= maxChange; 2008 | setBoundsSide(bounds, side, currentPoint); 2009 | resizeStack(DOCKED_STACK_ID, bounds, delayMs); 2010 | } 2011 | 2012 | System.out.println("Growing docked stack side=" + side); 2013 | while (currentPoint < maxPoint) { 2014 | maxChange = Math.min(stepSize, maxPoint - currentPoint); 2015 | currentPoint += maxChange; 2016 | setBoundsSide(bounds, side, currentPoint); 2017 | resizeStack(DOCKED_STACK_ID, bounds, delayMs); 2018 | } 2019 | 2020 | System.out.println("Back to Original size side=" + side); 2021 | while (currentPoint > startPoint) { 2022 | maxChange = Math.min(stepSize, currentPoint - startPoint); 2023 | currentPoint -= maxChange; 2024 | setBoundsSide(bounds, side, currentPoint); 2025 | resizeStack(DOCKED_STACK_ID, bounds, delayMs); 2026 | } 2027 | } 2028 | 2029 | private void setBoundsSide(Rect bounds, String side, int value) { 2030 | switch (side) { 2031 | case "l": 2032 | bounds.left = value; 2033 | break; 2034 | case "r": 2035 | bounds.right = value; 2036 | break; 2037 | case "t": 2038 | bounds.top = value; 2039 | break; 2040 | case "b": 2041 | bounds.bottom = value; 2042 | break; 2043 | default: 2044 | showError("Unknown set side: " + side); 2045 | break; 2046 | } 2047 | } 2048 | 2049 | private void runTask() throws Exception { 2050 | String op = nextArgRequired(); 2051 | if (op.equals("lock")) { 2052 | runTaskLock(); 2053 | } else if (op.equals("resizeable")) { 2054 | runTaskResizeable(); 2055 | } else if (op.equals("resize")) { 2056 | runTaskResize(); 2057 | } else if (op.equals("drag-task-test")) { 2058 | runTaskDragTaskTest(); 2059 | } else if (op.equals("size-task-test")) { 2060 | runTaskSizeTaskTest(); 2061 | } else { 2062 | showError("Error: unknown command '" + op + "'"); 2063 | return; 2064 | } 2065 | } 2066 | 2067 | private void runTaskLock() throws Exception { 2068 | String taskIdStr = nextArgRequired(); 2069 | try { 2070 | if (taskIdStr.equals("stop")) { 2071 | mAm.stopLockTaskMode(); 2072 | } else { 2073 | int taskId = Integer.parseInt(taskIdStr); 2074 | mAm.startLockTaskMode(taskId); 2075 | } 2076 | System.err.println("Activity manager is " + (mAm.isInLockTaskMode() ? "" : "not ") + 2077 | "in lockTaskMode"); 2078 | } catch (RemoteException e) { 2079 | } 2080 | } 2081 | 2082 | private void runTaskResizeable() throws Exception { 2083 | final String taskIdStr = nextArgRequired(); 2084 | final int taskId = Integer.parseInt(taskIdStr); 2085 | final String resizeableStr = nextArgRequired(); 2086 | final int resizeableMode = Integer.parseInt(resizeableStr); 2087 | 2088 | try { 2089 | mAm.setTaskResizeable(taskId, resizeableMode); 2090 | } catch (RemoteException e) { 2091 | } 2092 | } 2093 | 2094 | private void runTaskResize() throws Exception { 2095 | final String taskIdStr = nextArgRequired(); 2096 | final int taskId = Integer.parseInt(taskIdStr); 2097 | final Rect bounds = getBounds(); 2098 | if (bounds == null) { 2099 | System.err.println("Error: invalid input bounds"); 2100 | return; 2101 | } 2102 | taskResize(taskId, bounds, 0, false); 2103 | } 2104 | 2105 | private void taskResize(int taskId, Rect bounds, int delay_ms, boolean pretendUserResize) { 2106 | try { 2107 | final int resizeMode = pretendUserResize ? RESIZE_MODE_USER : RESIZE_MODE_SYSTEM; 2108 | mAm.resizeTask(taskId, bounds, resizeMode); 2109 | Thread.sleep(delay_ms); 2110 | } catch (RemoteException e) { 2111 | System.err.println("Error changing task bounds: " + e); 2112 | } catch (InterruptedException e) { 2113 | } 2114 | } 2115 | 2116 | private void runTaskDragTaskTest() { 2117 | final int taskId = Integer.parseInt(nextArgRequired()); 2118 | final int stepSize = Integer.parseInt(nextArgRequired()); 2119 | final String delayStr = nextArg(); 2120 | final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0; 2121 | final StackInfo stackInfo; 2122 | Rect taskBounds; 2123 | try { 2124 | stackInfo = mAm.getStackInfo(mAm.getFocusedStackId()); 2125 | taskBounds = mAm.getTaskBounds(taskId); 2126 | } catch (RemoteException e) { 2127 | System.err.println("Error getting focus stack info or task bounds: " + e); 2128 | return; 2129 | } 2130 | final Rect stackBounds = stackInfo.bounds; 2131 | int travelRight = stackBounds.width() - taskBounds.width(); 2132 | int travelLeft = -travelRight; 2133 | int travelDown = stackBounds.height() - taskBounds.height(); 2134 | int travelUp = -travelDown; 2135 | int passes = 0; 2136 | 2137 | // We do 2 passes to get back to the original location of the task. 2138 | while (passes < 2) { 2139 | // Move right 2140 | System.out.println("Moving right..."); 2141 | travelRight = moveTask(taskId, taskBounds, stackBounds, stepSize, 2142 | travelRight, MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms); 2143 | System.out.println("Still need to travel right by " + travelRight); 2144 | 2145 | // Move down 2146 | System.out.println("Moving down..."); 2147 | travelDown = moveTask(taskId, taskBounds, stackBounds, stepSize, 2148 | travelDown, MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms); 2149 | System.out.println("Still need to travel down by " + travelDown); 2150 | 2151 | // Move left 2152 | System.out.println("Moving left..."); 2153 | travelLeft = moveTask(taskId, taskBounds, stackBounds, stepSize, 2154 | travelLeft, !MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms); 2155 | System.out.println("Still need to travel left by " + travelLeft); 2156 | 2157 | // Move up 2158 | System.out.println("Moving up..."); 2159 | travelUp = moveTask(taskId, taskBounds, stackBounds, stepSize, 2160 | travelUp, !MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms); 2161 | System.out.println("Still need to travel up by " + travelUp); 2162 | 2163 | try { 2164 | taskBounds = mAm.getTaskBounds(taskId); 2165 | } catch (RemoteException e) { 2166 | System.err.println("Error getting task bounds: " + e); 2167 | return; 2168 | } 2169 | passes++; 2170 | } 2171 | } 2172 | 2173 | private int moveTask(int taskId, Rect taskRect, Rect stackRect, int stepSize, 2174 | int maxToTravel, boolean movingForward, boolean horizontal, int delay_ms) { 2175 | int maxMove; 2176 | if (movingForward) { 2177 | while (maxToTravel > 0 2178 | && ((horizontal && taskRect.right < stackRect.right) 2179 | ||(!horizontal && taskRect.bottom < stackRect.bottom))) { 2180 | if (horizontal) { 2181 | maxMove = Math.min(stepSize, stackRect.right - taskRect.right); 2182 | maxToTravel -= maxMove; 2183 | taskRect.right += maxMove; 2184 | taskRect.left += maxMove; 2185 | } else { 2186 | maxMove = Math.min(stepSize, stackRect.bottom - taskRect.bottom); 2187 | maxToTravel -= maxMove; 2188 | taskRect.top += maxMove; 2189 | taskRect.bottom += maxMove; 2190 | } 2191 | taskResize(taskId, taskRect, delay_ms, false); 2192 | } 2193 | } else { 2194 | while (maxToTravel < 0 2195 | && ((horizontal && taskRect.left > stackRect.left) 2196 | ||(!horizontal && taskRect.top > stackRect.top))) { 2197 | if (horizontal) { 2198 | maxMove = Math.min(stepSize, taskRect.left - stackRect.left); 2199 | maxToTravel -= maxMove; 2200 | taskRect.right -= maxMove; 2201 | taskRect.left -= maxMove; 2202 | } else { 2203 | maxMove = Math.min(stepSize, taskRect.top - stackRect.top); 2204 | maxToTravel -= maxMove; 2205 | taskRect.top -= maxMove; 2206 | taskRect.bottom -= maxMove; 2207 | } 2208 | taskResize(taskId, taskRect, delay_ms, false); 2209 | } 2210 | } 2211 | // Return the remaining distance we didn't travel because we reached the target location. 2212 | return maxToTravel; 2213 | } 2214 | 2215 | private void runTaskSizeTaskTest() { 2216 | final int taskId = Integer.parseInt(nextArgRequired()); 2217 | final int stepSize = Integer.parseInt(nextArgRequired()); 2218 | final String delayStr = nextArg(); 2219 | final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0; 2220 | final StackInfo stackInfo; 2221 | final Rect initialTaskBounds; 2222 | try { 2223 | stackInfo = mAm.getStackInfo(mAm.getFocusedStackId()); 2224 | initialTaskBounds = mAm.getTaskBounds(taskId); 2225 | } catch (RemoteException e) { 2226 | System.err.println("Error getting focus stack info or task bounds: " + e); 2227 | return; 2228 | } 2229 | final Rect stackBounds = stackInfo.bounds; 2230 | stackBounds.inset(STACK_BOUNDS_INSET, STACK_BOUNDS_INSET); 2231 | final Rect currentTaskBounds = new Rect(initialTaskBounds); 2232 | 2233 | // Size by top-left 2234 | System.out.println("Growing top-left"); 2235 | do { 2236 | currentTaskBounds.top -= getStepSize( 2237 | currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET); 2238 | 2239 | currentTaskBounds.left -= getStepSize( 2240 | currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET); 2241 | 2242 | taskResize(taskId, currentTaskBounds, delay_ms, true); 2243 | } while (stackBounds.top < currentTaskBounds.top 2244 | || stackBounds.left < currentTaskBounds.left); 2245 | 2246 | // Back to original size 2247 | System.out.println("Shrinking top-left"); 2248 | do { 2249 | currentTaskBounds.top += getStepSize( 2250 | currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET); 2251 | 2252 | currentTaskBounds.left += getStepSize( 2253 | currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET); 2254 | 2255 | taskResize(taskId, currentTaskBounds, delay_ms, true); 2256 | } while (initialTaskBounds.top > currentTaskBounds.top 2257 | || initialTaskBounds.left > currentTaskBounds.left); 2258 | 2259 | // Size by top-right 2260 | System.out.println("Growing top-right"); 2261 | do { 2262 | currentTaskBounds.top -= getStepSize( 2263 | currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET); 2264 | 2265 | currentTaskBounds.right += getStepSize( 2266 | currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET); 2267 | 2268 | taskResize(taskId, currentTaskBounds, delay_ms, true); 2269 | } while (stackBounds.top < currentTaskBounds.top 2270 | || stackBounds.right > currentTaskBounds.right); 2271 | 2272 | // Back to original size 2273 | System.out.println("Shrinking top-right"); 2274 | do { 2275 | currentTaskBounds.top += getStepSize( 2276 | currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET); 2277 | 2278 | currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right, 2279 | stepSize, GREATER_THAN_TARGET); 2280 | 2281 | taskResize(taskId, currentTaskBounds, delay_ms, true); 2282 | } while (initialTaskBounds.top > currentTaskBounds.top 2283 | || initialTaskBounds.right < currentTaskBounds.right); 2284 | 2285 | // Size by bottom-left 2286 | System.out.println("Growing bottom-left"); 2287 | do { 2288 | currentTaskBounds.bottom += getStepSize( 2289 | currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET); 2290 | 2291 | currentTaskBounds.left -= getStepSize( 2292 | currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET); 2293 | 2294 | taskResize(taskId, currentTaskBounds, delay_ms, true); 2295 | } while (stackBounds.bottom > currentTaskBounds.bottom 2296 | || stackBounds.left < currentTaskBounds.left); 2297 | 2298 | // Back to original size 2299 | System.out.println("Shrinking bottom-left"); 2300 | do { 2301 | currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom, 2302 | initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET); 2303 | 2304 | currentTaskBounds.left += getStepSize( 2305 | currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET); 2306 | 2307 | taskResize(taskId, currentTaskBounds, delay_ms, true); 2308 | } while (initialTaskBounds.bottom < currentTaskBounds.bottom 2309 | || initialTaskBounds.left > currentTaskBounds.left); 2310 | 2311 | // Size by bottom-right 2312 | System.out.println("Growing bottom-right"); 2313 | do { 2314 | currentTaskBounds.bottom += getStepSize( 2315 | currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET); 2316 | 2317 | currentTaskBounds.right += getStepSize( 2318 | currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET); 2319 | 2320 | taskResize(taskId, currentTaskBounds, delay_ms, true); 2321 | } while (stackBounds.bottom > currentTaskBounds.bottom 2322 | || stackBounds.right > currentTaskBounds.right); 2323 | 2324 | // Back to original size 2325 | System.out.println("Shrinking bottom-right"); 2326 | do { 2327 | currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom, 2328 | initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET); 2329 | 2330 | currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right, 2331 | stepSize, GREATER_THAN_TARGET); 2332 | 2333 | taskResize(taskId, currentTaskBounds, delay_ms, true); 2334 | } while (initialTaskBounds.bottom < currentTaskBounds.bottom 2335 | || initialTaskBounds.right < currentTaskBounds.right); 2336 | } 2337 | 2338 | private int getStepSize(int current, int target, int inStepSize, boolean greaterThanTarget) { 2339 | int stepSize = 0; 2340 | if (greaterThanTarget && target < current) { 2341 | current -= inStepSize; 2342 | stepSize = inStepSize; 2343 | if (target > current) { 2344 | stepSize -= (target - current); 2345 | } 2346 | } 2347 | if (!greaterThanTarget && target > current) { 2348 | current += inStepSize; 2349 | stepSize = inStepSize; 2350 | if (target < current) { 2351 | stepSize += (current - target); 2352 | } 2353 | } 2354 | return stepSize; 2355 | } 2356 | 2357 | private List getRecentConfigurations(int days) { 2358 | IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( 2359 | Context.USAGE_STATS_SERVICE)); 2360 | final long now = System.currentTimeMillis(); 2361 | final long nDaysAgo = now - (days * 24 * 60 * 60 * 1000); 2362 | try { 2363 | @SuppressWarnings("unchecked") 2364 | ParceledListSlice configStatsSlice = usm.queryConfigurationStats( 2365 | UsageStatsManager.INTERVAL_BEST, nDaysAgo, now, "com.android.shell"); 2366 | if (configStatsSlice == null) { 2367 | return Collections.emptyList(); 2368 | } 2369 | 2370 | final ArrayMap recentConfigs = new ArrayMap<>(); 2371 | final List configStatsList = configStatsSlice.getList(); 2372 | final int configStatsListSize = configStatsList.size(); 2373 | for (int i = 0; i < configStatsListSize; i++) { 2374 | final ConfigurationStats stats = configStatsList.get(i); 2375 | final int indexOfKey = recentConfigs.indexOfKey(stats.getConfiguration()); 2376 | if (indexOfKey < 0) { 2377 | recentConfigs.put(stats.getConfiguration(), stats.getActivationCount()); 2378 | } else { 2379 | recentConfigs.setValueAt(indexOfKey, 2380 | recentConfigs.valueAt(indexOfKey) + stats.getActivationCount()); 2381 | } 2382 | } 2383 | 2384 | final Comparator comparator = new Comparator() { 2385 | @Override 2386 | public int compare(Configuration a, Configuration b) { 2387 | return recentConfigs.get(b).compareTo(recentConfigs.get(a)); 2388 | } 2389 | }; 2390 | 2391 | ArrayList configs = new ArrayList<>(recentConfigs.size()); 2392 | configs.addAll(recentConfigs.keySet()); 2393 | Collections.sort(configs, comparator); 2394 | return configs; 2395 | 2396 | } catch (RemoteException e) { 2397 | return Collections.emptyList(); 2398 | } 2399 | } 2400 | 2401 | private void runGetConfig() throws Exception { 2402 | int days = 14; 2403 | String option = nextOption(); 2404 | if (option != null) { 2405 | if (!option.equals("--days")) { 2406 | throw new IllegalArgumentException("unrecognized option " + option); 2407 | } 2408 | 2409 | days = Integer.parseInt(nextArgRequired()); 2410 | if (days <= 0) { 2411 | throw new IllegalArgumentException("--days must be a positive integer"); 2412 | } 2413 | } 2414 | 2415 | try { 2416 | Configuration config = mAm.getConfiguration(); 2417 | if (config == null) { 2418 | System.err.println("Activity manager has no configuration"); 2419 | return; 2420 | } 2421 | 2422 | System.out.println("config: " + Configuration.resourceQualifierString(config)); 2423 | System.out.println("abi: " + TextUtils.join(",", Build.SUPPORTED_ABIS)); 2424 | 2425 | final List recentConfigs = getRecentConfigurations(days); 2426 | final int recentConfigSize = recentConfigs.size(); 2427 | if (recentConfigSize > 0) { 2428 | System.out.println("recentConfigs:"); 2429 | } 2430 | 2431 | for (int i = 0; i < recentConfigSize; i++) { 2432 | System.out.println(" config: " + Configuration.resourceQualifierString( 2433 | recentConfigs.get(i))); 2434 | } 2435 | 2436 | } catch (RemoteException e) { 2437 | } 2438 | } 2439 | 2440 | private void runSuppressResizeConfigChanges() throws Exception { 2441 | boolean suppress = Boolean.valueOf(nextArgRequired()); 2442 | 2443 | try { 2444 | mAm.suppressResizeConfigChanges(suppress); 2445 | } catch (RemoteException e) { 2446 | System.err.println("Error suppressing resize config changes: " + e); 2447 | } 2448 | } 2449 | 2450 | private void runSetInactive() throws Exception { 2451 | int userId = UserHandle.USER_CURRENT; 2452 | 2453 | String opt; 2454 | while ((opt=nextOption()) != null) { 2455 | if (opt.equals("--user")) { 2456 | userId = parseUserArg(nextArgRequired()); 2457 | } else { 2458 | System.err.println("Error: Unknown option: " + opt); 2459 | return; 2460 | } 2461 | } 2462 | String packageName = nextArgRequired(); 2463 | String value = nextArgRequired(); 2464 | 2465 | IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( 2466 | Context.USAGE_STATS_SERVICE)); 2467 | usm.setAppInactive(packageName, Boolean.parseBoolean(value), userId); 2468 | } 2469 | 2470 | private void runGetInactive() throws Exception { 2471 | int userId = UserHandle.USER_CURRENT; 2472 | 2473 | String opt; 2474 | while ((opt=nextOption()) != null) { 2475 | if (opt.equals("--user")) { 2476 | userId = parseUserArg(nextArgRequired()); 2477 | } else { 2478 | System.err.println("Error: Unknown option: " + opt); 2479 | return; 2480 | } 2481 | } 2482 | String packageName = nextArgRequired(); 2483 | 2484 | IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( 2485 | Context.USAGE_STATS_SERVICE)); 2486 | boolean isIdle = usm.isAppInactive(packageName, userId); 2487 | System.out.println("Idle=" + isIdle); 2488 | } 2489 | 2490 | private void runSendTrimMemory() throws Exception { 2491 | int userId = UserHandle.USER_CURRENT; 2492 | String opt; 2493 | while ((opt = nextOption()) != null) { 2494 | if (opt.equals("--user")) { 2495 | userId = parseUserArg(nextArgRequired()); 2496 | if (userId == UserHandle.USER_ALL) { 2497 | System.err.println("Error: Can't use user 'all'"); 2498 | return; 2499 | } 2500 | } else { 2501 | System.err.println("Error: Unknown option: " + opt); 2502 | return; 2503 | } 2504 | } 2505 | 2506 | String proc = nextArgRequired(); 2507 | String levelArg = nextArgRequired(); 2508 | int level; 2509 | switch (levelArg) { 2510 | case "HIDDEN": 2511 | level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN; 2512 | break; 2513 | case "RUNNING_MODERATE": 2514 | level = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE; 2515 | break; 2516 | case "BACKGROUND": 2517 | level = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; 2518 | break; 2519 | case "RUNNING_LOW": 2520 | level = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; 2521 | break; 2522 | case "MODERATE": 2523 | level = ComponentCallbacks2.TRIM_MEMORY_MODERATE; 2524 | break; 2525 | case "RUNNING_CRITICAL": 2526 | level = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL; 2527 | break; 2528 | case "COMPLETE": 2529 | level = ComponentCallbacks2.TRIM_MEMORY_COMPLETE; 2530 | break; 2531 | default: 2532 | System.err.println("Error: Unknown level option: " + levelArg); 2533 | return; 2534 | } 2535 | if (!mAm.setProcessMemoryTrimLevel(proc, userId, level)) { 2536 | System.err.println("Error: Failure to set the level - probably Unknown Process: " + 2537 | proc); 2538 | } 2539 | } 2540 | 2541 | private void runGetCurrentUser() throws Exception { 2542 | UserInfo currentUser = Preconditions.checkNotNull(mAm.getCurrentUser(), 2543 | "Current user not set"); 2544 | System.out.println(currentUser.id); 2545 | } 2546 | 2547 | // * 2548 | // * Open the given file for sending into the system process. This verifies 2549 | // * with SELinux that the system will have access to the file. 2550 | // * 2551 | private static ParcelFileDescriptor openForSystemServer(File file, int mode) 2552 | throws FileNotFoundException { 2553 | final ParcelFileDescriptor fd = ParcelFileDescriptor.open(file, mode); 2554 | final String tcon = SELinux.getFileContext(file.getAbsolutePath()); 2555 | if (!SELinux.checkSELinuxAccess("u:r:system_server:s0", tcon, "file", "read")) { 2556 | throw new FileNotFoundException("System server has no access to file context " + tcon); 2557 | } 2558 | return fd; 2559 | } 2560 | 2561 | private Rect getBounds() { 2562 | String leftStr = nextArgRequired(); 2563 | int left = Integer.parseInt(leftStr); 2564 | String topStr = nextArgRequired(); 2565 | int top = Integer.parseInt(topStr); 2566 | String rightStr = nextArgRequired(); 2567 | int right = Integer.parseInt(rightStr); 2568 | String bottomStr = nextArgRequired(); 2569 | int bottom = Integer.parseInt(bottomStr); 2570 | if (left < 0) { 2571 | System.err.println("Error: bad left arg: " + leftStr); 2572 | return null; 2573 | } 2574 | if (top < 0) { 2575 | System.err.println("Error: bad top arg: " + topStr); 2576 | return null; 2577 | } 2578 | if (right <= 0) { 2579 | System.err.println("Error: bad right arg: " + rightStr); 2580 | return null; 2581 | } 2582 | if (bottom <= 0) { 2583 | System.err.println("Error: bad bottom arg: " + bottomStr); 2584 | return null; 2585 | } 2586 | return new Rect(left, top, right, bottom); 2587 | } 2588 | */ 2589 | } 2590 | --------------------------------------------------------------------------------