├── 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 |
--------------------------------------------------------------------------------