diff options
Diffstat (limited to 'cmds')
65 files changed, 4137 insertions, 2976 deletions
diff --git a/cmds/am/Android.mk b/cmds/am/Android.mk index f8350dce7f50..5586dd4e5b18 100644 --- a/cmds/am/Android.mk +++ b/cmds/am/Android.mk @@ -3,8 +3,11 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) \ + $(call all-proto-files-under, proto) LOCAL_MODULE := am +LOCAL_PROTOC_OPTIMIZE_TYPE := stream include $(BUILD_JAVA_LIBRARY) include $(CLEAR_VARS) @@ -13,3 +16,14 @@ LOCAL_SRC_FILES := am LOCAL_MODULE_CLASS := EXECUTABLES LOCAL_MODULE_TAGS := optional include $(BUILD_PREBUILT) + + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := \ + $(call all-proto-files-under, proto) +LOCAL_MODULE := libinstrumentation +LOCAL_PROTOC_OPTIMIZE_TYPE := full +LOCAL_EXPORT_C_INCLUDE_DIRS := \ + $(call intermediates-dir-for,STATIC_LIBRARIES,libinstrumentation,HOST,,,)/proto/$(LOCAL_PATH)/proto +include $(BUILD_HOST_STATIC_LIBRARY) + diff --git a/cmds/am/am b/cmds/am/am index 1d426bc8c8e1..54c2d394be2c 100755 --- a/cmds/am/am +++ b/cmds/am/am @@ -1,9 +1,9 @@ #!/system/bin/sh -# -# Script to start "am" on the device, which has a very rudimentary -# shell. -# -base=/system -export CLASSPATH=$base/framework/am.jar -exec app_process $base/bin com.android.commands.am.Am "$@" +if [ "$1" != "instrument" ] ; then + cmd activity "$@" +else + base=/system + export CLASSPATH=$base/framework/am.jar + exec app_process $base/bin com.android.commands.am.Am "$@" +fi diff --git a/cmds/am/proto/instrumentation_data.proto b/cmds/am/proto/instrumentation_data.proto new file mode 100644 index 000000000000..12a18a2a035f --- /dev/null +++ b/cmds/am/proto/instrumentation_data.proto @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +package android.am; + +option java_package = "com.android.commands.am"; + +message ResultsBundleEntry { + optional string key = 1; + + optional string value_string = 2; + optional sint32 value_int = 3; + optional float value_float = 4; + optional double value_double = 5; + optional sint64 value_long = 6; + optional ResultsBundle value_bundle = 7; +} + +message ResultsBundle { + repeated ResultsBundleEntry entries = 1; +} + +message TestStatus { + optional sint32 result_code = 3; + optional ResultsBundle results = 4; +} + +enum SessionStatusCode { + /** + * The command ran successfully. This does not imply that the tests passed. + */ + SESSION_FINISHED = 0; + + /** + * There was an unrecoverable error running the tests. + */ + SESSION_ABORTED = 1; +} + +message SessionStatus { + optional SessionStatusCode status_code = 1; + optional string error_text = 2; + optional sint32 result_code = 3; + optional ResultsBundle results = 4; +} + +message Session { + repeated TestStatus test_status = 1; + optional SessionStatus session_status = 2; +} + + diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 4e1b6db71f30..f003061cec3a 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -1,20 +1,18 @@ /* -** -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.android.commands.am; @@ -25,8 +23,6 @@ import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import android.app.ActivityManager; import android.app.ActivityManager.StackInfo; -import android.app.ActivityManagerNative; -import android.app.ActivityOptions; import android.app.IActivityContainer; import android.app.IActivityController; import android.app.IActivityManager; @@ -46,7 +42,6 @@ import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.InstrumentationInfo; import android.content.pm.ParceledListSlice; -import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.Rect; @@ -55,15 +50,17 @@ import android.os.Build; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.SELinux; import android.os.ServiceManager; +import android.os.ShellCallback; import android.os.ShellCommand; -import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.text.TextUtils; import android.util.AndroidException; import android.util.ArrayMap; +import android.util.Log; import android.view.IWindowManager; import com.android.internal.os.BaseCommand; @@ -72,6 +69,7 @@ import com.android.internal.util.Preconditions; import java.io.BufferedReader; import java.io.File; +import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; @@ -85,34 +83,9 @@ import java.util.List; public class Am extends BaseCommand { - private static final String SHELL_PACKAGE_NAME = "com.android.shell"; - - // Is the object moving in a positive direction? - private static final boolean MOVING_FORWARD = true; - // Is the object moving in the horizontal plan? - private static final boolean MOVING_HORIZONTALLY = true; - // Is the object current point great then its target point? - private static final boolean GREATER_THAN_TARGET = true; - // Amount we reduce the stack size by when testing a task re-size. - private static final int STACK_BOUNDS_INSET = 10; - private IActivityManager mAm; private IPackageManager mPm; - private int mStartFlags = 0; - private boolean mWaitOption = false; - private boolean mStopOption = false; - - private int mRepeat = 0; - private int mUserId; - private String mReceiverPermission; - - private String mProfileFile; - private int mSamplingInterval; - private boolean mAutoStop; - private boolean mStreaming; // Streaming the profiling output to a file. - private int mStackId; - /** * Command-line entry point. * @@ -124,261 +97,17 @@ public class Am extends BaseCommand { @Override public void onShowUsage(PrintStream out) { - PrintWriter pw = new PrintWriter(out); - pw.println( - "usage: am [subcommand] [options]\n" + - "usage: am start [-D] [-N] [-W] [-P <FILE>] [--start-profiler <FILE>]\n" + - " [--sampling INTERVAL] [--streaming] [-R COUNT] [-S]\n" + - " [--track-allocation] [--user <USER_ID> | current] <INTENT>\n" + - " am startservice [--user <USER_ID> | current] <INTENT>\n" + - " am stopservice [--user <USER_ID> | current] <INTENT>\n" + - " am force-stop [--user <USER_ID> | all | current] <PACKAGE>\n" + - " am kill [--user <USER_ID> | all | current] <PACKAGE>\n" + - " am kill-all\n" + - " am broadcast [--user <USER_ID> | all | current] <INTENT>\n" + - " am instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]\n" + - " [--user <USER_ID> | current]\n" + - " [--no-window-animation] [--abi <ABI>] <COMPONENT>\n" + - " am profile start [--user <USER_ID> current] [--sampling INTERVAL]\n"+ - " [--streaming] <PROCESS> <FILE>\n" + - " am profile stop [--user <USER_ID> current] [<PROCESS>]\n" + - " am dumpheap [--user <USER_ID> current] [-n] <PROCESS> <FILE>\n" + - " am set-debug-app [-w] [--persistent] <PACKAGE>\n" + - " am clear-debug-app\n" + - " am set-watch-heap <PROCESS> <MEM-LIMIT>\n" + - " am clear-watch-heap\n" + - " am bug-report [--progress | --telephony]\n" + - " am monitor [--gdb <port>]\n" + - " am hang [--allow-restart]\n" + - " am restart\n" + - " am idle-maintenance\n" + - " am screen-compat [on|off] <PACKAGE>\n" + - " am package-importance <PACKAGE>\n" + - " am to-uri [INTENT]\n" + - " am to-intent-uri [INTENT]\n" + - " am to-app-uri [INTENT]\n" + - " am switch-user <USER_ID>\n" + - " am start-user <USER_ID>\n" + - " am unlock-user <USER_ID> [TOKEN_HEX]\n" + - " am stop-user [-w] [-f] <USER_ID>\n" + - " am stack start <DISPLAY_ID> <INTENT>\n" + - " am stack movetask <TASK_ID> <STACK_ID> [true|false]\n" + - " am stack resize <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" + - " am stack resize-animated <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" + - " am stack resize-docked-stack <LEFT,TOP,RIGHT,BOTTOM> [<TASK_LEFT,TASK_TOP,TASK_RIGHT,TASK_BOTTOM>]\n" + - " am stack size-docked-stack-test: <STEP_SIZE> <l|t|r|b> [DELAY_MS]\n" + - " am stack move-top-activity-to-pinned-stack: <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" + - " am stack positiontask <TASK_ID> <STACK_ID> <POSITION>\n" + - " am stack list\n" + - " am stack info <STACK_ID>\n" + - " am stack remove <STACK_ID>\n" + - " am task lock <TASK_ID>\n" + - " am task lock stop\n" + - " am task resizeable <TASK_ID> [0 (unresizeable) | 1 (crop_windows) | 2 (resizeable) | 3 (resizeable_and_pipable)]\n" + - " am task resize <TASK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" + - " am task drag-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS] \n" + - " am task size-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS] \n" + - " am get-config\n" + - " am suppress-resize-config-changes <true|false>\n" + - " am set-inactive [--user <USER_ID>] <PACKAGE> true|false\n" + - " am get-inactive [--user <USER_ID>] <PACKAGE>\n" + - " am send-trim-memory [--user <USER_ID>] <PROCESS>\n" + - " [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]\n" + - " am get-current-user\n" + - "\n" + - "am start: start an Activity. Options are:\n" + - " -D: enable debugging\n" + - " -N: enable native debugging\n" + - " -W: wait for launch to complete\n" + - " --start-profiler <FILE>: start profiler and send results to <FILE>\n" + - " --sampling INTERVAL: use sample profiling with INTERVAL microseconds\n" + - " between samples (use with --start-profiler)\n" + - " --streaming: stream the profiling output to the specified file (use\n" + - " with --start-profiler)\n" + - " -P <FILE>: like above, but profiling stops when app goes idle\n" + - " -R: repeat the activity launch <COUNT> times. Prior to each repeat,\n" + - " the top activity will be finished.\n" + - " -S: force stop the target app before starting the activity\n" + - " --track-allocation: enable tracking of object allocations\n" + - " --user <USER_ID> | current: Specify which user to run as; if not\n" + - " specified then run as the current user.\n" + - " --stack <STACK_ID>: Specify into which stack should the activity be put." + - "\n" + - "am startservice: start a Service. Options are:\n" + - " --user <USER_ID> | current: Specify which user to run as; if not\n" + - " specified then run as the current user.\n" + - "\n" + - "am stopservice: stop a Service. Options are:\n" + - " --user <USER_ID> | current: Specify which user to run as; if not\n" + - " specified then run as the current user.\n" + - "\n" + - "am force-stop: force stop everything associated with <PACKAGE>.\n" + - " --user <USER_ID> | all | current: Specify user to force stop;\n" + - " all users if not specified.\n" + - "\n" + - "am kill: Kill all processes associated with <PACKAGE>. Only kills.\n" + - " processes that are safe to kill -- that is, will not impact the user\n" + - " experience.\n" + - " --user <USER_ID> | all | current: Specify user whose processes to kill;\n" + - " all users if not specified.\n" + - "\n" + - "am kill-all: Kill all background processes.\n" + - "\n" + - "am broadcast: send a broadcast Intent. Options are:\n" + - " --user <USER_ID> | all | current: Specify which user to send to; if not\n" + - " specified then send to all users.\n" + - " --receiver-permission <PERMISSION>: Require receiver to hold permission.\n" + - "\n" + - "am instrument: start an Instrumentation. Typically this target <COMPONENT>\n" + - " is the form <TEST_PACKAGE>/<RUNNER_CLASS> or only <TEST_PACKAGE> if there \n" + - " is only one instrumentation. Options are:\n" + - " -r: print raw results (otherwise decode REPORT_KEY_STREAMRESULT). Use with\n" + - " [-e perf true] to generate raw output for performance measurements.\n" + - " -e <NAME> <VALUE>: set argument <NAME> to <VALUE>. For test runners a\n" + - " common form is [-e <testrunner_flag> <value>[,<value>...]].\n" + - " -p <FILE>: write profiling data to <FILE>\n" + - " -w: wait for instrumentation to finish before returning. Required for\n" + - " test runners.\n" + - " --user <USER_ID> | current: Specify user instrumentation runs in;\n" + - " current user if not specified.\n" + - " --no-window-animation: turn off window animations while running.\n" + - " --abi <ABI>: Launch the instrumented process with the selected ABI.\n" + - " This assumes that the process supports the selected ABI.\n" + - "\n" + - "am trace-ipc: Trace IPC transactions.\n" + - " start: start tracing IPC transactions.\n" + - " stop: stop tracing IPC transactions and dump the results to file.\n" + - " --dump-file <FILE>: Specify the file the trace should be dumped to.\n" + - "\n" + - "am profile: start and stop profiler on a process. The given <PROCESS> argument\n" + - " may be either a process name or pid. Options are:\n" + - " --user <USER_ID> | current: When supplying a process name,\n" + - " specify user of process to profile; uses current user if not specified.\n" + - " --sampling INTERVAL: use sample profiling with INTERVAL microseconds\n" + - " between samples\n" + - " --streaming: stream the profiling output to the specified file\n" + - "\n" + - "am dumpheap: dump the heap of a process. The given <PROCESS> argument may\n" + - " be either a process name or pid. Options are:\n" + - " -n: dump native heap instead of managed heap\n" + - " --user <USER_ID> | current: When supplying a process name,\n" + - " specify user of process to dump; uses current user if not specified.\n" + - "\n" + - "am set-debug-app: set application <PACKAGE> to debug. Options are:\n" + - " -w: wait for debugger when application starts\n" + - " --persistent: retain this value\n" + - "\n" + - "am clear-debug-app: clear the previously set-debug-app.\n" + - "\n" + - "am set-watch-heap: start monitoring pss size of <PROCESS>, if it is at or\n" + - " above <HEAP-LIMIT> then a heap dump is collected for the user to report\n" + - "\n" + - "am clear-watch-heap: clear the previously set-watch-heap.\n" + - "\n" + - "am bug-report: request bug report generation; will launch a notification\n" + - " when done to select where it should be delivered. Options are: \n" + - " --progress: will launch a notification right away to show its progress.\n" + - " --telephony: will dump only telephony sections.\n" + - "\n" + - "am monitor: start monitoring for crashes or ANRs.\n" + - " --gdb: start gdbserv on the given port at crash/ANR\n" + - "\n" + - "am hang: hang the system.\n" + - " --allow-restart: allow watchdog to perform normal system restart\n" + - "\n" + - "am restart: restart the user-space system.\n" + - "\n" + - "am idle-maintenance: perform idle maintenance now.\n" + - "\n" + - "am screen-compat: control screen compatibility mode of <PACKAGE>.\n" + - "\n" + - "am package-importance: print current importance of <PACKAGE>.\n" + - "\n" + - "am to-uri: print the given Intent specification as a URI.\n" + - "\n" + - "am to-intent-uri: print the given Intent specification as an intent: URI.\n" + - "\n" + - "am to-app-uri: print the given Intent specification as an android-app: URI.\n" + - "\n" + - "am switch-user: switch to put USER_ID in the foreground, starting\n" + - " execution of that user if it is currently stopped.\n" + - "\n" + - "am start-user: start USER_ID in background if it is currently stopped,\n" + - " use switch-user if you want to start the user in foreground.\n" + - "\n" + - "am stop-user: stop execution of USER_ID, not allowing it to run any\n" + - " code until a later explicit start or switch to it.\n" + - " -w: wait for stop-user to complete.\n" + - " -f: force stop even if there are related users that cannot be stopped.\n" + - "\n" + - "am stack start: start a new activity on <DISPLAY_ID> using <INTENT>.\n" + - "\n" + - "am stack movetask: move <TASK_ID> from its current stack to the top (true) or" + - " bottom (false) of <STACK_ID>.\n" + - "\n" + - "am stack resize: change <STACK_ID> size and position to <LEFT,TOP,RIGHT,BOTTOM>.\n" + - "\n" + - "am stack resize-docked-stack: change docked stack to <LEFT,TOP,RIGHT,BOTTOM>\n" + - " and supplying temporary different task bounds indicated by\n" + - " <TASK_LEFT,TOP,RIGHT,BOTTOM>\n" + - "\n" + - "am stack size-docked-stack-test: test command for sizing docked stack by\n" + - " <STEP_SIZE> increments from the side <l>eft, <t>op, <r>ight, or <b>ottom\n" + - " applying the optional [DELAY_MS] between each step.\n" + - "\n" + - "am stack move-top-activity-to-pinned-stack: moves the top activity from\n" + - " <STACK_ID> to the pinned stack using <LEFT,TOP,RIGHT,BOTTOM> for the\n" + - " bounds of the pinned stack.\n" + - "\n" + - "am stack positiontask: place <TASK_ID> in <STACK_ID> at <POSITION>" + - "\n" + - "am stack list: list all of the activity stacks and their sizes.\n" + - "\n" + - "am stack info: display the information about activity stack <STACK_ID>.\n" + - "\n" + - "am stack remove: remove stack <STACK_ID>.\n" + - "\n" + - "am task lock: bring <TASK_ID> to the front and don't allow other tasks to run.\n" + - "\n" + - "am task lock stop: end the current task lock.\n" + - "\n" + - "am task resizeable: change resizeable mode of <TASK_ID>.\n" + - " 0 (unresizeable) | 1 (crop_windows) | 2 (resizeable) | 3 (resizeable_and_pipable)\n" + - "\n" + - "am task resize: makes sure <TASK_ID> is in a stack with the specified bounds.\n" + - " Forces the task to be resizeable and creates a stack if no existing stack\n" + - " has the specified bounds.\n" + - "\n" + - "am task drag-task-test: test command for dragging/moving <TASK_ID> by\n" + - " <STEP_SIZE> increments around the screen applying the optional [DELAY_MS]\n" + - " between each step.\n" + - "\n" + - "am task size-task-test: test command for sizing <TASK_ID> by <STEP_SIZE>" + - " increments within the screen applying the optional [DELAY_MS] between\n" + - " each step.\n" + - "\n" + - "am get-config: retrieve the configuration and any recent configurations\n" + - " of the device.\n" + - "am suppress-resize-config-changes: suppresses configuration changes due to\n" + - " user resizing an activity/task.\n" + - "\n" + - "am set-inactive: sets the inactive state of an app.\n" + - "\n" + - "am get-inactive: returns the inactive state of an app.\n" + - "\n" + - "am send-trim-memory: send a memory trim event to a <PROCESS>.\n" + - "\n" + - "am get-current-user: returns id of the current foreground user.\n" + - "\n" - ); - Intent.printIntentArgsHelp(pw, ""); - pw.flush(); + try { + runAmCmd(new String[] { "help" }); + } catch (AndroidException e) { + e.printStackTrace(System.err); + } } @Override public void onRun() throws Exception { - mAm = ActivityManagerNative.getDefault(); + mAm = ActivityManager.getService(); if (mAm == null) { System.err.println(NO_SYSTEM_ERROR_CODE); throw new AndroidException("Can't connect to activity manager; is the system running?"); @@ -392,82 +121,10 @@ public class Am extends BaseCommand { String op = nextArgRequired(); - if (op.equals("start")) { - runStart(); - } else if (op.equals("startservice")) { - runStartService(); - } else if (op.equals("stopservice")) { - runStopService(); - } else if (op.equals("force-stop")) { - runForceStop(); - } else if (op.equals("kill")) { - runKill(); - } else if (op.equals("kill-all")) { - runKillAll(); - } else if (op.equals("instrument")) { + if (op.equals("instrument")) { runInstrument(); - } else if (op.equals("trace-ipc")) { - runTraceIpc(); - } else if (op.equals("broadcast")) { - sendBroadcast(); - } else if (op.equals("profile")) { - runProfile(); - } else if (op.equals("dumpheap")) { - runDumpHeap(); - } else if (op.equals("set-debug-app")) { - runSetDebugApp(); - } else if (op.equals("clear-debug-app")) { - runClearDebugApp(); - } else if (op.equals("set-watch-heap")) { - runSetWatchHeap(); - } else if (op.equals("clear-watch-heap")) { - runClearWatchHeap(); - } else if (op.equals("bug-report")) { - runBugReport(); - } else if (op.equals("monitor")) { - runMonitor(); - } else if (op.equals("hang")) { - runHang(); - } else if (op.equals("restart")) { - runRestart(); - } else if (op.equals("idle-maintenance")) { - runIdleMaintenance(); - } else if (op.equals("screen-compat")) { - runScreenCompat(); - } else if (op.equals("package-importance")) { - runPackageImportance(); - } else if (op.equals("to-uri")) { - runToUri(0); - } else if (op.equals("to-intent-uri")) { - runToUri(Intent.URI_INTENT_SCHEME); - } else if (op.equals("to-app-uri")) { - runToUri(Intent.URI_ANDROID_APP_SCHEME); - } else if (op.equals("switch-user")) { - runSwitchUser(); - } else if (op.equals("start-user")) { - runStartUserInBackground(); - } else if (op.equals("unlock-user")) { - runUnlockUser(); - } else if (op.equals("stop-user")) { - runStopUser(); - } else if (op.equals("stack")) { - runStack(); - } else if (op.equals("task")) { - runTask(); - } else if (op.equals("get-config")) { - runGetConfig(); - } else if (op.equals("suppress-resize-config-changes")) { - runSuppressResizeConfigChanges(); - } else if (op.equals("set-inactive")) { - runSetInactive(); - } else if (op.equals("get-inactive")) { - runGetInactive(); - } else if (op.equals("send-trim-memory")) { - runSendTrimMemory(); - } else if (op.equals("get-current-user")) { - runGetCurrentUser(); } else { - showError("Error: unknown command '" + op + "'"); + runAmCmd(getRawArgs()); } } @@ -483,2113 +140,95 @@ public class Am extends BaseCommand { return userId; } - private Intent makeIntent(int defUser) throws URISyntaxException { - mStartFlags = 0; - mWaitOption = false; - mStopOption = false; - mRepeat = 0; - mProfileFile = null; - mSamplingInterval = 0; - mAutoStop = false; - mStreaming = false; - mUserId = defUser; - mStackId = INVALID_STACK_ID; - - return Intent.parseCommandArgs(mArgs, new Intent.CommandOptionHandler() { - @Override - public boolean handleOption(String opt, ShellCommand cmd) { - if (opt.equals("-D")) { - mStartFlags |= ActivityManager.START_FLAG_DEBUG; - } else if (opt.equals("-N")) { - mStartFlags |= ActivityManager.START_FLAG_NATIVE_DEBUGGING; - } else if (opt.equals("-W")) { - mWaitOption = true; - } else if (opt.equals("-P")) { - mProfileFile = nextArgRequired(); - mAutoStop = true; - } else if (opt.equals("--start-profiler")) { - mProfileFile = nextArgRequired(); - mAutoStop = false; - } else if (opt.equals("--sampling")) { - mSamplingInterval = Integer.parseInt(nextArgRequired()); - } else if (opt.equals("--streaming")) { - mStreaming = true; - } else if (opt.equals("-R")) { - mRepeat = Integer.parseInt(nextArgRequired()); - } else if (opt.equals("-S")) { - mStopOption = true; - } else if (opt.equals("--track-allocation")) { - mStartFlags |= ActivityManager.START_FLAG_TRACK_ALLOCATION; - } else if (opt.equals("--user")) { - mUserId = parseUserArg(nextArgRequired()); - } else if (opt.equals("--receiver-permission")) { - mReceiverPermission = nextArgRequired(); - } else if (opt.equals("--stack")) { - mStackId = Integer.parseInt(nextArgRequired()); - } else { - return false; - } - return true; - } - }); - } - - private void runStartService() throws Exception { - Intent intent = makeIntent(UserHandle.USER_CURRENT); - if (mUserId == UserHandle.USER_ALL) { - System.err.println("Error: Can't start activity with user 'all'"); - return; - } - System.out.println("Starting service: " + intent); - ComponentName cn = mAm.startService(null, intent, intent.getType(), - SHELL_PACKAGE_NAME, mUserId); - if (cn == null) { - System.err.println("Error: Not found; no service started."); - } else if (cn.getPackageName().equals("!")) { - System.err.println("Error: Requires permission " + cn.getClassName()); - } else if (cn.getPackageName().equals("!!")) { - System.err.println("Error: " + cn.getClassName()); - } - } - - private void runStopService() throws Exception { - Intent intent = makeIntent(UserHandle.USER_CURRENT); - if (mUserId == UserHandle.USER_ALL) { - System.err.println("Error: Can't stop activity with user 'all'"); - return; - } - System.out.println("Stopping service: " + intent); - int result = mAm.stopService(null, intent, intent.getType(), mUserId); - if (result == 0) { - System.err.println("Service not stopped: was not running."); - } else if (result == 1) { - System.err.println("Service stopped"); - } else if (result == -1) { - System.err.println("Error stopping service"); - } - } - - private void runStart() throws Exception { - Intent intent = makeIntent(UserHandle.USER_CURRENT); - - if (mUserId == UserHandle.USER_ALL) { - System.err.println("Error: Can't start service with user 'all'"); - return; - } + static final class MyShellCallback extends ShellCallback { + boolean mActive = true; - String mimeType = intent.getType(); - if (mimeType == null && intent.getData() != null - && "content".equals(intent.getData().getScheme())) { - mimeType = mAm.getProviderMimeType(intent.getData(), mUserId); - } - - - do { - if (mStopOption) { - String packageName; - if (intent.getComponent() != null) { - packageName = intent.getComponent().getPackageName(); - } else { - List<ResolveInfo> activities = mPm.queryIntentActivities(intent, mimeType, 0, - mUserId).getList(); - if (activities == null || activities.size() <= 0) { - System.err.println("Error: Intent does not match any activities: " - + intent); - return; - } else if (activities.size() > 1) { - System.err.println("Error: Intent matches multiple activities; can't stop: " - + intent); - return; - } - packageName = activities.get(0).activityInfo.packageName; - } - System.out.println("Stopping: " + packageName); - mAm.forceStopPackage(packageName, mUserId); - Thread.sleep(250); + @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) { + if (!mActive) { + System.err.println("Open attempt after active for: " + path); + return null; } - - System.out.println("Starting: " + intent); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - ParcelFileDescriptor fd = null; - ProfilerInfo profilerInfo = null; - - if (mProfileFile != null) { - try { - fd = openForSystemServer( - new File(mProfileFile), - ParcelFileDescriptor.MODE_CREATE | - ParcelFileDescriptor.MODE_TRUNCATE | - ParcelFileDescriptor.MODE_WRITE_ONLY); - } catch (FileNotFoundException e) { - System.err.println("Error: Unable to open file: " + mProfileFile); - System.err.println("Consider using a file under /data/local/tmp/"); - return; - } - profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop, - mStreaming); - } - - IActivityManager.WaitResult result = null; - int res; - final long startTime = SystemClock.uptimeMillis(); - ActivityOptions options = null; - if (mStackId != INVALID_STACK_ID) { - options = ActivityOptions.makeBasic(); - options.setLaunchStackId(mStackId); - } - if (mWaitOption) { - result = mAm.startActivityAndWait(null, null, intent, mimeType, - null, null, 0, mStartFlags, profilerInfo, - options != null ? options.toBundle() : null, mUserId); - res = result.result; - } else { - res = mAm.startActivityAsUser(null, null, intent, mimeType, - null, null, 0, mStartFlags, profilerInfo, - options != null ? options.toBundle() : null, mUserId); - } - final long endTime = SystemClock.uptimeMillis(); - PrintStream out = mWaitOption ? System.out : System.err; - boolean launched = false; - switch (res) { - case ActivityManager.START_SUCCESS: - launched = true; - break; - case ActivityManager.START_SWITCHES_CANCELED: - launched = true; - out.println( - "Warning: Activity not started because the " - + " current activity is being kept for the user."); - break; - case ActivityManager.START_DELIVERED_TO_TOP: - launched = true; - out.println( - "Warning: Activity not started, intent has " - + "been delivered to currently running " - + "top-most instance."); - break; - case ActivityManager.START_RETURN_INTENT_TO_CALLER: - launched = true; - out.println( - "Warning: Activity not started because intent " - + "should be handled by the caller"); - break; - case ActivityManager.START_TASK_TO_FRONT: - launched = true; - out.println( - "Warning: Activity not started, its current " - + "task has been brought to the front"); - break; - case ActivityManager.START_INTENT_NOT_RESOLVED: - out.println( - "Error: Activity not started, unable to " - + "resolve " + intent.toString()); - break; - case ActivityManager.START_CLASS_NOT_FOUND: - out.println(NO_CLASS_ERROR_CODE); - out.println("Error: Activity class " + - intent.getComponent().toShortString() - + " does not exist."); - break; - case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT: - out.println( - "Error: Activity not started, you requested to " - + "both forward and receive its result"); - break; - case ActivityManager.START_PERMISSION_DENIED: - out.println( - "Error: Activity not started, you do not " - + "have permission to access it."); - break; - case ActivityManager.START_NOT_VOICE_COMPATIBLE: - out.println( - "Error: Activity not started, voice control not allowed for: " - + intent); - break; - case ActivityManager.START_NOT_CURRENT_USER_ACTIVITY: - out.println( - "Error: Not allowed to start background user activity" - + " that shouldn't be displayed for all users."); - break; - default: - out.println( - "Error: Activity not started, unknown error code " + res); - break; + File file = new File(path); + //System.err.println("Opening file: " + file.getAbsolutePath()); + //Log.i("Am", "Opening file: " + file.getAbsolutePath()); + final ParcelFileDescriptor fd; + try { + fd = ParcelFileDescriptor.open(file, + ParcelFileDescriptor.MODE_CREATE | + ParcelFileDescriptor.MODE_TRUNCATE | + ParcelFileDescriptor.MODE_WRITE_ONLY); + } catch (FileNotFoundException e) { + String msg = "Unable to open file " + path + ": " + e; + System.err.println(msg); + throw new IllegalArgumentException(msg); } - if (mWaitOption && launched) { - if (result == null) { - result = new IActivityManager.WaitResult(); - result.who = intent.getComponent(); - } - System.out.println("Status: " + (result.timeout ? "timeout" : "ok")); - if (result.who != null) { - System.out.println("Activity: " + result.who.flattenToShortString()); - } - if (result.thisTime >= 0) { - System.out.println("ThisTime: " + result.thisTime); - } - if (result.totalTime >= 0) { - System.out.println("TotalTime: " + result.totalTime); + if (seLinuxContext != null) { + final String tcon = SELinux.getFileContext(file.getAbsolutePath()); + if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) { + try { + fd.close(); + } catch (IOException e) { + } + String msg = "System server has no access to file context " + tcon; + System.err.println(msg + " (from path " + file.getAbsolutePath() + + ", context " + seLinuxContext + ")"); + throw new IllegalArgumentException(msg); } - System.out.println("WaitTime: " + (endTime-startTime)); - System.out.println("Complete"); - } - mRepeat--; - if (mRepeat > 0) { - mAm.unhandledBack(); - } - } while (mRepeat > 0); - } - - private void runForceStop() throws Exception { - int userId = UserHandle.USER_ALL; - - String opt; - while ((opt=nextOption()) != null) { - if (opt.equals("--user")) { - userId = parseUserArg(nextArgRequired()); - } else { - System.err.println("Error: Unknown option: " + opt); - return; } + return fd; } - mAm.forceStopPackage(nextArgRequired(), userId); } - private void runKill() throws Exception { - int userId = UserHandle.USER_ALL; - - String opt; - while ((opt=nextOption()) != null) { - if (opt.equals("--user")) { - userId = parseUserArg(nextArgRequired()); - } else { - System.err.println("Error: Unknown option: " + opt); - return; - } + void runAmCmd(String[] args) throws AndroidException { + final MyShellCallback cb = new MyShellCallback(); + try { + mAm.asBinder().shellCommand(FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, + args, cb, new ResultReceiver(null) { }); + } catch (RemoteException e) { + System.err.println(NO_SYSTEM_ERROR_CODE); + throw new AndroidException("Can't call activity manager; is the system running?"); + } finally { + cb.mActive = false; } - mAm.killBackgroundProcesses(nextArgRequired(), userId); } - private void runKillAll() throws Exception { - mAm.killAllBackgroundProcesses(); - } - - private void sendBroadcast() throws Exception { - Intent intent = makeIntent(UserHandle.USER_CURRENT); - IntentReceiver receiver = new IntentReceiver(); - String[] requiredPermissions = mReceiverPermission == null ? null - : new String[] {mReceiverPermission}; - System.out.println("Broadcasting: " + intent); - mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, requiredPermissions, - android.app.AppOpsManager.OP_NONE, null, true, false, mUserId); - receiver.waitForFinish(); - } - - private void runInstrument() throws Exception { - String profileFile = null; - boolean wait = false; - boolean rawMode = false; - boolean no_window_animation = false; - int userId = UserHandle.USER_CURRENT; - Bundle args = new Bundle(); - String argKey = null, argValue = null; - IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); - String abi = null; + public void runInstrument() throws Exception { + Instrument instrument = new Instrument(mAm, mPm); String opt; while ((opt=nextOption()) != null) { if (opt.equals("-p")) { - profileFile = nextArgRequired(); + instrument.profileFile = nextArgRequired(); } else if (opt.equals("-w")) { - wait = true; + instrument.wait = true; } else if (opt.equals("-r")) { - rawMode = true; + instrument.rawMode = true; + } else if (opt.equals("-m")) { + instrument.proto = true; } else if (opt.equals("-e")) { - argKey = nextArgRequired(); - argValue = nextArgRequired(); - args.putString(argKey, argValue); + final String argKey = nextArgRequired(); + final String argValue = nextArgRequired(); + instrument.args.putString(argKey, argValue); } else if (opt.equals("--no_window_animation") || opt.equals("--no-window-animation")) { - no_window_animation = true; + instrument.noWindowAnimation = true; } else if (opt.equals("--user")) { - userId = parseUserArg(nextArgRequired()); + instrument.userId = parseUserArg(nextArgRequired()); } else if (opt.equals("--abi")) { - abi = nextArgRequired(); + instrument.abi = nextArgRequired(); } else { System.err.println("Error: Unknown option: " + opt); return; } } - if (userId == UserHandle.USER_ALL) { + if (instrument.userId == UserHandle.USER_ALL) { System.err.println("Error: Can't start instrumentation with user 'all'"); return; } - String cnArg = nextArgRequired(); - - ComponentName cn; - if (cnArg.contains("/")) { - cn = ComponentName.unflattenFromString(cnArg); - if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg); - } else { - List<InstrumentationInfo> infos = mPm.queryInstrumentation(null, 0).getList(); - - final int numInfos = infos == null ? 0: infos.size(); - List<ComponentName> cns = new ArrayList<>(); - for (int i = 0; i < numInfos; i++) { - InstrumentationInfo info = infos.get(i); - - ComponentName c = new ComponentName(info.packageName, info.name); - if (cnArg.equals(info.packageName)) { - cns.add(c); - } - } - - if (cns.size() == 0) { - throw new IllegalArgumentException("No instrumentation found for: " + cnArg); - } else if (cns.size() == 1) { - cn = cns.get(0); - } else { - StringBuilder cnsStr = new StringBuilder(); - final int numCns = cns.size(); - for (int i = 0; i < numCns; i++) { - cnsStr.append(cns.get(i).flattenToString()); - cnsStr.append(", "); - } - - // Remove last ", " - cnsStr.setLength(cnsStr.length() - 2); - - throw new IllegalArgumentException("Found multiple instrumentations: " - + cnsStr.toString()); - } - } - - InstrumentationWatcher watcher = null; - UiAutomationConnection connection = null; - if (wait) { - watcher = new InstrumentationWatcher(); - watcher.setRawOutput(rawMode); - connection = new UiAutomationConnection(); - } - - float[] oldAnims = null; - if (no_window_animation) { - oldAnims = wm.getAnimationScales(); - wm.setAnimationScale(0, 0.0f); - wm.setAnimationScale(1, 0.0f); - } - - if (abi != null) { - final String[] supportedAbis = Build.SUPPORTED_ABIS; - boolean matched = false; - for (String supportedAbi : supportedAbis) { - if (supportedAbi.equals(abi)) { - matched = true; - break; - } - } - - if (!matched) { - throw new AndroidException( - "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi); - } - } - - if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, abi)) { - throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString()); - } - - if (watcher != null) { - if (!watcher.waitForFinish()) { - System.out.println("INSTRUMENTATION_ABORTED: System has crashed."); - } - } - - if (oldAnims != null) { - wm.setAnimationScales(oldAnims); - } - } - - private void runTraceIpc() throws Exception { - String op = nextArgRequired(); - if (op.equals("start")) { - runTraceIpcStart(); - } else if (op.equals("stop")) { - runTraceIpcStop(); - } else { - showError("Error: unknown command '" + op + "'"); - return; - } - } - - private void runTraceIpcStart() throws Exception { - System.out.println("Starting IPC tracing."); - mAm.startBinderTracking(); - } - - private void runTraceIpcStop() throws Exception { - String opt; - String filename = null; - while ((opt=nextOption()) != null) { - if (opt.equals("--dump-file")) { - filename = nextArgRequired(); - } else { - System.err.println("Error: Unknown option: " + opt); - return; - } - } - if (filename == null) { - System.err.println("Error: Specify filename to dump logs to."); - return; - } - - ParcelFileDescriptor fd = null; - - try { - File file = new File(filename); - file.delete(); - fd = openForSystemServer(file, - ParcelFileDescriptor.MODE_CREATE | - ParcelFileDescriptor.MODE_TRUNCATE | - ParcelFileDescriptor.MODE_WRITE_ONLY); - } catch (FileNotFoundException e) { - System.err.println("Error: Unable to open file: " + filename); - System.err.println("Consider using a file under /data/local/tmp/"); - return; - } - - ; - if (!mAm.stopBinderTrackingAndDump(fd)) { - throw new AndroidException("STOP TRACE FAILED."); - } - - System.out.println("Stopped IPC tracing. Dumping logs to: " + filename); - } - - static void removeWallOption() { - String props = SystemProperties.get("dalvik.vm.extra-opts"); - if (props != null && props.contains("-Xprofile:wallclock")) { - props = props.replace("-Xprofile:wallclock", ""); - props = props.trim(); - SystemProperties.set("dalvik.vm.extra-opts", props); - } - } - - private void runProfile() throws Exception { - String profileFile = null; - boolean start = false; - boolean wall = false; - int userId = UserHandle.USER_CURRENT; - int profileType = 0; - mSamplingInterval = 0; - mStreaming = false; - - String process = null; - - String cmd = nextArgRequired(); - - if ("start".equals(cmd)) { - start = true; - String opt; - while ((opt=nextOption()) != null) { - if (opt.equals("--user")) { - userId = parseUserArg(nextArgRequired()); - } else if (opt.equals("--wall")) { - wall = true; - } else if (opt.equals("--streaming")) { - mStreaming = true; - } else if (opt.equals("--sampling")) { - mSamplingInterval = Integer.parseInt(nextArgRequired()); - } else { - System.err.println("Error: Unknown option: " + opt); - return; - } - } - process = nextArgRequired(); - } else if ("stop".equals(cmd)) { - String opt; - while ((opt=nextOption()) != null) { - if (opt.equals("--user")) { - userId = parseUserArg(nextArgRequired()); - } else { - System.err.println("Error: Unknown option: " + opt); - return; - } - } - process = nextArg(); - } else { - // Compatibility with old syntax: process is specified first. - process = cmd; - cmd = nextArgRequired(); - if ("start".equals(cmd)) { - start = true; - } else if (!"stop".equals(cmd)) { - throw new IllegalArgumentException("Profile command " + process + " not valid"); - } - } - - if (userId == UserHandle.USER_ALL) { - System.err.println("Error: Can't profile with user 'all'"); - return; - } - - ParcelFileDescriptor fd = null; - ProfilerInfo profilerInfo = null; - - if (start) { - profileFile = nextArgRequired(); - try { - fd = openForSystemServer( - new File(profileFile), - ParcelFileDescriptor.MODE_CREATE | - ParcelFileDescriptor.MODE_TRUNCATE | - ParcelFileDescriptor.MODE_WRITE_ONLY); - } catch (FileNotFoundException e) { - System.err.println("Error: Unable to open file: " + profileFile); - System.err.println("Consider using a file under /data/local/tmp/"); - return; - } - profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false, mStreaming); - } - - try { - if (wall) { - // XXX doesn't work -- this needs to be set before booting. - String props = SystemProperties.get("dalvik.vm.extra-opts"); - if (props == null || !props.contains("-Xprofile:wallclock")) { - props = props + " -Xprofile:wallclock"; - //SystemProperties.set("dalvik.vm.extra-opts", props); - } - } else if (start) { - //removeWallOption(); - } - if (!mAm.profileControl(process, userId, start, profilerInfo, profileType)) { - wall = false; - throw new AndroidException("PROFILE FAILED on process " + process); - } - } finally { - if (!wall) { - //removeWallOption(); - } - } - } - - private void runDumpHeap() throws Exception { - boolean managed = true; - int userId = UserHandle.USER_CURRENT; - - String opt; - while ((opt=nextOption()) != null) { - if (opt.equals("--user")) { - userId = parseUserArg(nextArgRequired()); - if (userId == UserHandle.USER_ALL) { - System.err.println("Error: Can't dump heap with user 'all'"); - return; - } - } else if (opt.equals("-n")) { - managed = false; - } else { - System.err.println("Error: Unknown option: " + opt); - return; - } - } - String process = nextArgRequired(); - String heapFile = nextArgRequired(); - ParcelFileDescriptor fd = null; - - try { - File file = new File(heapFile); - file.delete(); - fd = openForSystemServer(file, - ParcelFileDescriptor.MODE_CREATE | - ParcelFileDescriptor.MODE_TRUNCATE | - ParcelFileDescriptor.MODE_WRITE_ONLY); - } catch (FileNotFoundException e) { - System.err.println("Error: Unable to open file: " + heapFile); - System.err.println("Consider using a file under /data/local/tmp/"); - return; - } - - if (!mAm.dumpHeap(process, userId, managed, heapFile, fd)) { - throw new AndroidException("HEAP DUMP FAILED on process " + process); - } - } - - private void runSetDebugApp() throws Exception { - boolean wait = false; - boolean persistent = false; - - String opt; - while ((opt=nextOption()) != null) { - if (opt.equals("-w")) { - wait = true; - } else if (opt.equals("--persistent")) { - persistent = true; - } else { - System.err.println("Error: Unknown option: " + opt); - return; - } - } - - String pkg = nextArgRequired(); - mAm.setDebugApp(pkg, wait, persistent); - } - - private void runClearDebugApp() throws Exception { - mAm.setDebugApp(null, false, true); - } - - private void runSetWatchHeap() throws Exception { - String proc = nextArgRequired(); - String limit = nextArgRequired(); - mAm.setDumpHeapDebugLimit(proc, 0, Long.parseLong(limit), null); - } - - private void runClearWatchHeap() throws Exception { - String proc = nextArgRequired(); - mAm.setDumpHeapDebugLimit(proc, 0, -1, null); - } - - private void runBugReport() throws Exception { - String opt; - int bugreportType = ActivityManager.BUGREPORT_OPTION_FULL; - while ((opt=nextOption()) != null) { - if (opt.equals("--progress")) { - bugreportType = ActivityManager.BUGREPORT_OPTION_INTERACTIVE; - } else if (opt.equals("--telephony")) { - bugreportType = ActivityManager.BUGREPORT_OPTION_TELEPHONY; - } else { - System.err.println("Error: Unknown option: " + opt); - return; - } - } - mAm.requestBugReport(bugreportType); - System.out.println("Your lovely bug report is being created; please be patient."); - } - - private void runSwitchUser() throws Exception { - String user = nextArgRequired(); - mAm.switchUser(Integer.parseInt(user)); - } - - private void runStartUserInBackground() throws Exception { - String user = nextArgRequired(); - boolean success = mAm.startUserInBackground(Integer.parseInt(user)); - if (success) { - System.out.println("Success: user started"); - } else { - System.err.println("Error: could not start user"); - } - } - - private byte[] argToBytes(String arg) { - if (arg.equals("!")) { - return null; - } else { - return HexDump.hexStringToByteArray(arg); - } - } - - private void runUnlockUser() throws Exception { - int userId = Integer.parseInt(nextArgRequired()); - byte[] token = argToBytes(nextArgRequired()); - byte[] secret = argToBytes(nextArgRequired()); - boolean success = mAm.unlockUser(userId, token, secret, null); - if (success) { - System.out.println("Success: user unlocked"); - } else { - System.err.println("Error: could not unlock user"); - } - } - - private static class StopUserCallback extends IStopUserCallback.Stub { - private boolean mFinished = false; - - public synchronized void waitForFinish() { - try { - while (!mFinished) wait(); - } catch (InterruptedException e) { - throw new IllegalStateException(e); - } - } - - @Override - public synchronized void userStopped(int userId) { - mFinished = true; - notifyAll(); - } - - @Override - public synchronized void userStopAborted(int userId) { - mFinished = true; - notifyAll(); - } - } - - private void runStopUser() throws Exception { - boolean wait = false; - boolean force = false; - String opt; - while ((opt = nextOption()) != null) { - if ("-w".equals(opt)) { - wait = true; - } else if ("-f".equals(opt)) { - force = true; - } else { - System.err.println("Error: unknown option: " + opt); - return; - } - } - int user = Integer.parseInt(nextArgRequired()); - StopUserCallback callback = wait ? new StopUserCallback() : null; - - int res = mAm.stopUser(user, force, callback); - if (res != ActivityManager.USER_OP_SUCCESS) { - String txt = ""; - switch (res) { - case ActivityManager.USER_OP_IS_CURRENT: - txt = " (Can't stop current user)"; - break; - case ActivityManager.USER_OP_UNKNOWN_USER: - txt = " (Unknown user " + user + ")"; - break; - case ActivityManager.USER_OP_ERROR_IS_SYSTEM: - txt = " (System user cannot be stopped)"; - break; - case ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP: - txt = " (Can't stop user " + user - + " - one of its related users can't be stopped)"; - break; - } - System.err.println("Switch failed: " + res + txt); - } else if (callback != null) { - callback.waitForFinish(); - } - } - - class MyActivityController extends IActivityController.Stub { - final String mGdbPort; - final boolean mMonkey; - - static final int STATE_NORMAL = 0; - static final int STATE_CRASHED = 1; - static final int STATE_EARLY_ANR = 2; - static final int STATE_ANR = 3; - - int mState; - - static final int RESULT_DEFAULT = 0; + instrument.componentNameArg = nextArgRequired(); - static final int RESULT_CRASH_DIALOG = 0; - static final int RESULT_CRASH_KILL = 1; - - static final int RESULT_EARLY_ANR_CONTINUE = 0; - static final int RESULT_EARLY_ANR_KILL = 1; - - static final int RESULT_ANR_DIALOG = 0; - static final int RESULT_ANR_KILL = 1; - static final int RESULT_ANR_WAIT = 1; - - int mResult; - - Process mGdbProcess; - Thread mGdbThread; - boolean mGotGdbPrint; - - MyActivityController(String gdbPort, boolean monkey) { - mGdbPort = gdbPort; - mMonkey = monkey; - } - - @Override - public boolean activityResuming(String pkg) { - synchronized (this) { - System.out.println("** Activity resuming: " + pkg); - } - return true; - } - - @Override - public boolean activityStarting(Intent intent, String pkg) { - synchronized (this) { - System.out.println("** Activity starting: " + pkg); - } - return true; - } - - @Override - public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg, - long timeMillis, String stackTrace) { - synchronized (this) { - System.out.println("** ERROR: PROCESS CRASHED"); - System.out.println("processName: " + processName); - System.out.println("processPid: " + pid); - System.out.println("shortMsg: " + shortMsg); - System.out.println("longMsg: " + longMsg); - System.out.println("timeMillis: " + timeMillis); - System.out.println("stack:"); - System.out.print(stackTrace); - System.out.println("#"); - int result = waitControllerLocked(pid, STATE_CRASHED); - return result == RESULT_CRASH_KILL ? false : true; - } - } - - @Override - public int appEarlyNotResponding(String processName, int pid, String annotation) { - synchronized (this) { - System.out.println("** ERROR: EARLY PROCESS NOT RESPONDING"); - System.out.println("processName: " + processName); - System.out.println("processPid: " + pid); - System.out.println("annotation: " + annotation); - int result = waitControllerLocked(pid, STATE_EARLY_ANR); - if (result == RESULT_EARLY_ANR_KILL) return -1; - return 0; - } - } - - @Override - public int appNotResponding(String processName, int pid, String processStats) { - synchronized (this) { - System.out.println("** ERROR: PROCESS NOT RESPONDING"); - System.out.println("processName: " + processName); - System.out.println("processPid: " + pid); - System.out.println("processStats:"); - System.out.print(processStats); - System.out.println("#"); - int result = waitControllerLocked(pid, STATE_ANR); - if (result == RESULT_ANR_KILL) return -1; - if (result == RESULT_ANR_WAIT) return 1; - return 0; - } - } - - @Override - public int systemNotResponding(String message) { - synchronized (this) { - System.out.println("** ERROR: PROCESS NOT RESPONDING"); - System.out.println("message: " + message); - System.out.println("#"); - System.out.println("Allowing system to die."); - return -1; - } - } - - void killGdbLocked() { - mGotGdbPrint = false; - if (mGdbProcess != null) { - System.out.println("Stopping gdbserver"); - mGdbProcess.destroy(); - mGdbProcess = null; - } - if (mGdbThread != null) { - mGdbThread.interrupt(); - mGdbThread = null; - } - } - - int waitControllerLocked(int pid, int state) { - if (mGdbPort != null) { - killGdbLocked(); - - try { - System.out.println("Starting gdbserver on port " + mGdbPort); - System.out.println("Do the following:"); - System.out.println(" adb forward tcp:" + mGdbPort + " tcp:" + mGdbPort); - System.out.println(" gdbclient app_process :" + mGdbPort); - - mGdbProcess = Runtime.getRuntime().exec(new String[] { - "gdbserver", ":" + mGdbPort, "--attach", Integer.toString(pid) - }); - final InputStreamReader converter = new InputStreamReader( - mGdbProcess.getInputStream()); - mGdbThread = new Thread() { - @Override - public void run() { - BufferedReader in = new BufferedReader(converter); - String line; - int count = 0; - while (true) { - synchronized (MyActivityController.this) { - if (mGdbThread == null) { - return; - } - if (count == 2) { - mGotGdbPrint = true; - MyActivityController.this.notifyAll(); - } - } - try { - line = in.readLine(); - if (line == null) { - return; - } - System.out.println("GDB: " + line); - count++; - } catch (IOException e) { - return; - } - } - } - }; - mGdbThread.start(); - - // Stupid waiting for .5s. Doesn't matter if we end early. - try { - this.wait(500); - } catch (InterruptedException e) { - } - - } catch (IOException e) { - System.err.println("Failure starting gdbserver: " + e); - killGdbLocked(); - } - } - mState = state; - System.out.println(""); - printMessageForState(); - - while (mState != STATE_NORMAL) { - try { - wait(); - } catch (InterruptedException e) { - } - } - - killGdbLocked(); - - return mResult; - } - - void resumeController(int result) { - synchronized (this) { - mState = STATE_NORMAL; - mResult = result; - notifyAll(); - } - } - - void printMessageForState() { - switch (mState) { - case STATE_NORMAL: - System.out.println("Monitoring activity manager... available commands:"); - break; - case STATE_CRASHED: - System.out.println("Waiting after crash... available commands:"); - System.out.println("(c)ontinue: show crash dialog"); - System.out.println("(k)ill: immediately kill app"); - break; - case STATE_EARLY_ANR: - System.out.println("Waiting after early ANR... available commands:"); - System.out.println("(c)ontinue: standard ANR processing"); - System.out.println("(k)ill: immediately kill app"); - break; - case STATE_ANR: - System.out.println("Waiting after ANR... available commands:"); - System.out.println("(c)ontinue: show ANR dialog"); - System.out.println("(k)ill: immediately kill app"); - System.out.println("(w)ait: wait some more"); - break; - } - System.out.println("(q)uit: finish monitoring"); - } - - void run() throws RemoteException { - try { - printMessageForState(); - - mAm.setActivityController(this, mMonkey); - mState = STATE_NORMAL; - - InputStreamReader converter = new InputStreamReader(System.in); - BufferedReader in = new BufferedReader(converter); - String line; - - while ((line = in.readLine()) != null) { - boolean addNewline = true; - if (line.length() <= 0) { - addNewline = false; - } else if ("q".equals(line) || "quit".equals(line)) { - resumeController(RESULT_DEFAULT); - break; - } else if (mState == STATE_CRASHED) { - if ("c".equals(line) || "continue".equals(line)) { - resumeController(RESULT_CRASH_DIALOG); - } else if ("k".equals(line) || "kill".equals(line)) { - resumeController(RESULT_CRASH_KILL); - } else { - System.out.println("Invalid command: " + line); - } - } else if (mState == STATE_ANR) { - if ("c".equals(line) || "continue".equals(line)) { - resumeController(RESULT_ANR_DIALOG); - } else if ("k".equals(line) || "kill".equals(line)) { - resumeController(RESULT_ANR_KILL); - } else if ("w".equals(line) || "wait".equals(line)) { - resumeController(RESULT_ANR_WAIT); - } else { - System.out.println("Invalid command: " + line); - } - } else if (mState == STATE_EARLY_ANR) { - if ("c".equals(line) || "continue".equals(line)) { - resumeController(RESULT_EARLY_ANR_CONTINUE); - } else if ("k".equals(line) || "kill".equals(line)) { - resumeController(RESULT_EARLY_ANR_KILL); - } else { - System.out.println("Invalid command: " + line); - } - } else { - System.out.println("Invalid command: " + line); - } - - synchronized (this) { - if (addNewline) { - System.out.println(""); - } - printMessageForState(); - } - } - - } catch (IOException e) { - e.printStackTrace(); - } finally { - mAm.setActivityController(null, mMonkey); - } - } - } - - private void runMonitor() throws Exception { - String opt; - String gdbPort = null; - boolean monkey = false; - while ((opt=nextOption()) != null) { - if (opt.equals("--gdb")) { - gdbPort = nextArgRequired(); - } else if (opt.equals("-m")) { - monkey = true; - } else { - System.err.println("Error: Unknown option: " + opt); - return; - } - } - - MyActivityController controller = new MyActivityController(gdbPort, monkey); - controller.run(); - } - - private void runHang() throws Exception { - String opt; - boolean allowRestart = false; - while ((opt=nextOption()) != null) { - if (opt.equals("--allow-restart")) { - allowRestart = true; - } else { - System.err.println("Error: Unknown option: " + opt); - return; - } - } - - System.out.println("Hanging the system..."); - mAm.hang(new Binder(), allowRestart); - } - - private void runRestart() throws Exception { - String opt; - while ((opt=nextOption()) != null) { - System.err.println("Error: Unknown option: " + opt); - return; - } - - System.out.println("Restart the system..."); - mAm.restart(); - } - - private void runIdleMaintenance() throws Exception { - String opt; - while ((opt=nextOption()) != null) { - System.err.println("Error: Unknown option: " + opt); - return; - } - - System.out.println("Performing idle maintenance..."); - try { - mAm.sendIdleJobTrigger(); - } catch (RemoteException e) { - } - } - - private void runScreenCompat() throws Exception { - String mode = nextArgRequired(); - boolean enabled; - if ("on".equals(mode)) { - enabled = true; - } else if ("off".equals(mode)) { - enabled = false; - } else { - System.err.println("Error: enabled mode must be 'on' or 'off' at " + mode); - return; - } - - String packageName = nextArgRequired(); - do { - try { - mAm.setPackageScreenCompatMode(packageName, enabled - ? ActivityManager.COMPAT_MODE_ENABLED - : ActivityManager.COMPAT_MODE_DISABLED); - } catch (RemoteException e) { - } - packageName = nextArg(); - } while (packageName != null); - } - - private void runPackageImportance() throws Exception { - String packageName = nextArgRequired(); - try { - int procState = mAm.getPackageProcessState(packageName, "com.android.shell"); - System.out.println( - ActivityManager.RunningAppProcessInfo.procStateToImportance(procState)); - } catch (RemoteException e) { - } - } - - private void runToUri(int flags) throws Exception { - Intent intent = makeIntent(UserHandle.USER_CURRENT); - System.out.println(intent.toUri(flags)); - } - - private class IntentReceiver extends IIntentReceiver.Stub { - private boolean mFinished = false; - - @Override - public void performReceive(Intent intent, int resultCode, String data, Bundle extras, - boolean ordered, boolean sticky, int sendingUser) { - String line = "Broadcast completed: result=" + resultCode; - if (data != null) line = line + ", data=\"" + data + "\""; - if (extras != null) line = line + ", extras: " + extras; - System.out.println(line); - synchronized (this) { - mFinished = true; - notifyAll(); - } - } - - public synchronized void waitForFinish() { - try { - while (!mFinished) wait(); - } catch (InterruptedException e) { - throw new IllegalStateException(e); - } - } - } - - private class InstrumentationWatcher extends IInstrumentationWatcher.Stub { - private boolean mFinished = false; - private boolean mRawMode = false; - - /** - * Set or reset "raw mode". In "raw mode", all bundles are dumped. In "pretty mode", - * if a bundle includes Instrumentation.REPORT_KEY_STREAMRESULT, just print that. - * @param rawMode true for raw mode, false for pretty mode. - */ - public void setRawOutput(boolean rawMode) { - mRawMode = rawMode; - } - - @Override - public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) { - synchronized (this) { - // pretty printer mode? - String pretty = null; - if (!mRawMode && results != null) { - pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT); - } - if (pretty != null) { - System.out.print(pretty); - } else { - if (results != null) { - for (String key : results.keySet()) { - System.out.println( - "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key)); - } - } - System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode); - } - notifyAll(); - } - } - - @Override - public void instrumentationFinished(ComponentName name, int resultCode, - Bundle results) { - synchronized (this) { - // pretty printer mode? - String pretty = null; - if (!mRawMode && results != null) { - pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT); - } - if (pretty != null) { - System.out.println(pretty); - } else { - if (results != null) { - for (String key : results.keySet()) { - System.out.println( - "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key)); - } - } - System.out.println("INSTRUMENTATION_CODE: " + resultCode); - } - mFinished = true; - notifyAll(); - } - } - - public boolean waitForFinish() { - synchronized (this) { - while (!mFinished) { - try { - if (!mAm.asBinder().pingBinder()) { - return false; - } - wait(1000); - } catch (InterruptedException e) { - throw new IllegalStateException(e); - } - } - } - return true; - } - } - - private void runStack() throws Exception { - String op = nextArgRequired(); - switch (op) { - case "start": - runStackStart(); - break; - case "movetask": - runStackMoveTask(); - break; - case "resize": - runStackResize(); - break; - case "resize-animated": - runStackResizeAnimated(); - break; - case "resize-docked-stack": - runStackResizeDocked(); - break; - case "positiontask": - runStackPositionTask(); - break; - case "list": - runStackList(); - break; - case "info": - runStackInfo(); - break; - case "move-top-activity-to-pinned-stack": - runMoveTopActivityToPinnedStack(); - break; - case "size-docked-stack-test": - runStackSizeDockedStackTest(); - break; - case "remove": - runStackRemove(); - break; - default: - showError("Error: unknown command '" + op + "'"); - break; - } - } - - private void runStackStart() throws Exception { - String displayIdStr = nextArgRequired(); - int displayId = Integer.parseInt(displayIdStr); - Intent intent = makeIntent(UserHandle.USER_CURRENT); - - try { - IActivityContainer container = mAm.createStackOnDisplay(displayId); - if (container != null) { - container.startActivity(intent); - } - } catch (RemoteException e) { - } - } - - private void runStackMoveTask() throws Exception { - String taskIdStr = nextArgRequired(); - int taskId = Integer.parseInt(taskIdStr); - String stackIdStr = nextArgRequired(); - int stackId = Integer.parseInt(stackIdStr); - String toTopStr = nextArgRequired(); - final boolean toTop; - if ("true".equals(toTopStr)) { - toTop = true; - } else if ("false".equals(toTopStr)) { - toTop = false; - } else { - System.err.println("Error: bad toTop arg: " + toTopStr); - return; - } - - try { - mAm.moveTaskToStack(taskId, stackId, toTop); - } catch (RemoteException e) { - } - } - - private void runStackResize() throws Exception { - String stackIdStr = nextArgRequired(); - int stackId = Integer.parseInt(stackIdStr); - final Rect bounds = getBounds(); - if (bounds == null) { - System.err.println("Error: invalid input bounds"); - return; - } - resizeStack(stackId, bounds, 0); - } - - private void runStackResizeAnimated() throws Exception { - String stackIdStr = nextArgRequired(); - int stackId = Integer.parseInt(stackIdStr); - final Rect bounds; - if ("null".equals(mArgs.peekNextArg())) { - bounds = null; - } else { - bounds = getBounds(); - if (bounds == null) { - System.err.println("Error: invalid input bounds"); - return; - } - } - resizeStackUnchecked(stackId, bounds, 0, true); - } - - private void resizeStackUnchecked(int stackId, Rect bounds, int delayMs, boolean animate) { - try { - mAm.resizeStack(stackId, bounds, false, false, animate, -1); - Thread.sleep(delayMs); - } catch (RemoteException e) { - showError("Error: resizing stack " + e); - } catch (InterruptedException e) { - } - } - - private void runStackResizeDocked() throws Exception { - final Rect bounds = getBounds(); - final Rect taskBounds = getBounds(); - if (bounds == null || taskBounds == null) { - System.err.println("Error: invalid input bounds"); - return; - } - try { - mAm.resizeDockedStack(bounds, taskBounds, null, null, null); - } catch (RemoteException e) { - showError("Error: resizing docked stack " + e); - } - } - - private void resizeStack(int stackId, Rect bounds, int delayMs) - throws Exception { - if (bounds == null) { - showError("Error: invalid input bounds"); - return; - } - resizeStackUnchecked(stackId, bounds, delayMs, false); - } - - private void runStackPositionTask() throws Exception { - String taskIdStr = nextArgRequired(); - int taskId = Integer.parseInt(taskIdStr); - String stackIdStr = nextArgRequired(); - int stackId = Integer.parseInt(stackIdStr); - String positionStr = nextArgRequired(); - int position = Integer.parseInt(positionStr); - - try { - mAm.positionTaskInStack(taskId, stackId, position); - } catch (RemoteException e) { - } - } - - private void runStackList() throws Exception { - try { - List<StackInfo> stacks = mAm.getAllStackInfos(); - for (StackInfo info : stacks) { - System.out.println(info); - } - } catch (RemoteException e) { - } - } - - private void runStackInfo() throws Exception { - try { - String stackIdStr = nextArgRequired(); - int stackId = Integer.parseInt(stackIdStr); - StackInfo info = mAm.getStackInfo(stackId); - System.out.println(info); - } catch (RemoteException e) { - } - } - - private void runStackRemove() throws Exception { - String stackIdStr = nextArgRequired(); - int stackId = Integer.parseInt(stackIdStr); - mAm.removeStack(stackId); - } - - private void runMoveTopActivityToPinnedStack() throws Exception { - int stackId = Integer.parseInt(nextArgRequired()); - final Rect bounds = getBounds(); - if (bounds == null) { - System.err.println("Error: invalid input bounds"); - return; - } - - try { - if (!mAm.moveTopActivityToPinnedStack(stackId, bounds)) { - showError("Didn't move top activity to pinned stack."); - } - } catch (RemoteException e) { - showError("Unable to move top activity: " + e); - return; - } - } - - private void runStackSizeDockedStackTest() throws Exception { - final int stepSize = Integer.parseInt(nextArgRequired()); - final String side = nextArgRequired(); - final String delayStr = nextArg(); - final int delayMs = (delayStr != null) ? Integer.parseInt(delayStr) : 0; - - Rect bounds; - try { - StackInfo info = mAm.getStackInfo(DOCKED_STACK_ID); - if (info == null) { - showError("Docked stack doesn't exist"); - return; - } - if (info.bounds == null) { - showError("Docked stack doesn't have a bounds"); - return; - } - bounds = info.bounds; - } catch (RemoteException e) { - showError("Unable to get docked stack info:" + e); - return; - } - - final boolean horizontalGrowth = "l".equals(side) || "r".equals(side); - final int changeSize = (horizontalGrowth ? bounds.width() : bounds.height()) / 2; - int currentPoint; - switch (side) { - case "l": - currentPoint = bounds.left; - break; - case "r": - currentPoint = bounds.right; - break; - case "t": - currentPoint = bounds.top; - break; - case "b": - currentPoint = bounds.bottom; - break; - default: - showError("Unknown growth side: " + side); - return; - } - - final int startPoint = currentPoint; - final int minPoint = currentPoint - changeSize; - final int maxPoint = currentPoint + changeSize; - - int maxChange; - System.out.println("Shrinking docked stack side=" + side); - while (currentPoint > minPoint) { - maxChange = Math.min(stepSize, currentPoint - minPoint); - currentPoint -= maxChange; - setBoundsSide(bounds, side, currentPoint); - resizeStack(DOCKED_STACK_ID, bounds, delayMs); - } - - System.out.println("Growing docked stack side=" + side); - while (currentPoint < maxPoint) { - maxChange = Math.min(stepSize, maxPoint - currentPoint); - currentPoint += maxChange; - setBoundsSide(bounds, side, currentPoint); - resizeStack(DOCKED_STACK_ID, bounds, delayMs); - } - - System.out.println("Back to Original size side=" + side); - while (currentPoint > startPoint) { - maxChange = Math.min(stepSize, currentPoint - startPoint); - currentPoint -= maxChange; - setBoundsSide(bounds, side, currentPoint); - resizeStack(DOCKED_STACK_ID, bounds, delayMs); - } - } - - private void setBoundsSide(Rect bounds, String side, int value) { - switch (side) { - case "l": - bounds.left = value; - break; - case "r": - bounds.right = value; - break; - case "t": - bounds.top = value; - break; - case "b": - bounds.bottom = value; - break; - default: - showError("Unknown set side: " + side); - break; - } - } - - private void runTask() throws Exception { - String op = nextArgRequired(); - if (op.equals("lock")) { - runTaskLock(); - } else if (op.equals("resizeable")) { - runTaskResizeable(); - } else if (op.equals("resize")) { - runTaskResize(); - } else if (op.equals("drag-task-test")) { - runTaskDragTaskTest(); - } else if (op.equals("size-task-test")) { - runTaskSizeTaskTest(); - } else { - showError("Error: unknown command '" + op + "'"); - return; - } - } - - private void runTaskLock() throws Exception { - String taskIdStr = nextArgRequired(); - try { - if (taskIdStr.equals("stop")) { - mAm.stopLockTaskMode(); - } else { - int taskId = Integer.parseInt(taskIdStr); - mAm.startLockTaskMode(taskId); - } - System.err.println("Activity manager is " + (mAm.isInLockTaskMode() ? "" : "not ") + - "in lockTaskMode"); - } catch (RemoteException e) { - } - } - - private void runTaskResizeable() throws Exception { - final String taskIdStr = nextArgRequired(); - final int taskId = Integer.parseInt(taskIdStr); - final String resizeableStr = nextArgRequired(); - final int resizeableMode = Integer.parseInt(resizeableStr); - - try { - mAm.setTaskResizeable(taskId, resizeableMode); - } catch (RemoteException e) { - } - } - - private void runTaskResize() throws Exception { - final String taskIdStr = nextArgRequired(); - final int taskId = Integer.parseInt(taskIdStr); - final Rect bounds = getBounds(); - if (bounds == null) { - System.err.println("Error: invalid input bounds"); - return; - } - taskResize(taskId, bounds, 0, false); - } - - private void taskResize(int taskId, Rect bounds, int delay_ms, boolean pretendUserResize) { - try { - final int resizeMode = pretendUserResize ? RESIZE_MODE_USER : RESIZE_MODE_SYSTEM; - mAm.resizeTask(taskId, bounds, resizeMode); - Thread.sleep(delay_ms); - } catch (RemoteException e) { - System.err.println("Error changing task bounds: " + e); - } catch (InterruptedException e) { - } - } - - private void runTaskDragTaskTest() { - final int taskId = Integer.parseInt(nextArgRequired()); - final int stepSize = Integer.parseInt(nextArgRequired()); - final String delayStr = nextArg(); - final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0; - final StackInfo stackInfo; - Rect taskBounds; - try { - stackInfo = mAm.getStackInfo(mAm.getFocusedStackId()); - taskBounds = mAm.getTaskBounds(taskId); - } catch (RemoteException e) { - System.err.println("Error getting focus stack info or task bounds: " + e); - return; - } - final Rect stackBounds = stackInfo.bounds; - int travelRight = stackBounds.width() - taskBounds.width(); - int travelLeft = -travelRight; - int travelDown = stackBounds.height() - taskBounds.height(); - int travelUp = -travelDown; - int passes = 0; - - // We do 2 passes to get back to the original location of the task. - while (passes < 2) { - // Move right - System.out.println("Moving right..."); - travelRight = moveTask(taskId, taskBounds, stackBounds, stepSize, - travelRight, MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms); - System.out.println("Still need to travel right by " + travelRight); - - // Move down - System.out.println("Moving down..."); - travelDown = moveTask(taskId, taskBounds, stackBounds, stepSize, - travelDown, MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms); - System.out.println("Still need to travel down by " + travelDown); - - // Move left - System.out.println("Moving left..."); - travelLeft = moveTask(taskId, taskBounds, stackBounds, stepSize, - travelLeft, !MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms); - System.out.println("Still need to travel left by " + travelLeft); - - // Move up - System.out.println("Moving up..."); - travelUp = moveTask(taskId, taskBounds, stackBounds, stepSize, - travelUp, !MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms); - System.out.println("Still need to travel up by " + travelUp); - - try { - taskBounds = mAm.getTaskBounds(taskId); - } catch (RemoteException e) { - System.err.println("Error getting task bounds: " + e); - return; - } - passes++; - } - } - - private int moveTask(int taskId, Rect taskRect, Rect stackRect, int stepSize, - int maxToTravel, boolean movingForward, boolean horizontal, int delay_ms) { - int maxMove; - if (movingForward) { - while (maxToTravel > 0 - && ((horizontal && taskRect.right < stackRect.right) - ||(!horizontal && taskRect.bottom < stackRect.bottom))) { - if (horizontal) { - maxMove = Math.min(stepSize, stackRect.right - taskRect.right); - maxToTravel -= maxMove; - taskRect.right += maxMove; - taskRect.left += maxMove; - } else { - maxMove = Math.min(stepSize, stackRect.bottom - taskRect.bottom); - maxToTravel -= maxMove; - taskRect.top += maxMove; - taskRect.bottom += maxMove; - } - taskResize(taskId, taskRect, delay_ms, false); - } - } else { - while (maxToTravel < 0 - && ((horizontal && taskRect.left > stackRect.left) - ||(!horizontal && taskRect.top > stackRect.top))) { - if (horizontal) { - maxMove = Math.min(stepSize, taskRect.left - stackRect.left); - maxToTravel -= maxMove; - taskRect.right -= maxMove; - taskRect.left -= maxMove; - } else { - maxMove = Math.min(stepSize, taskRect.top - stackRect.top); - maxToTravel -= maxMove; - taskRect.top -= maxMove; - taskRect.bottom -= maxMove; - } - taskResize(taskId, taskRect, delay_ms, false); - } - } - // Return the remaining distance we didn't travel because we reached the target location. - return maxToTravel; - } - - private void runTaskSizeTaskTest() { - final int taskId = Integer.parseInt(nextArgRequired()); - final int stepSize = Integer.parseInt(nextArgRequired()); - final String delayStr = nextArg(); - final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0; - final StackInfo stackInfo; - final Rect initialTaskBounds; - try { - stackInfo = mAm.getStackInfo(mAm.getFocusedStackId()); - initialTaskBounds = mAm.getTaskBounds(taskId); - } catch (RemoteException e) { - System.err.println("Error getting focus stack info or task bounds: " + e); - return; - } - final Rect stackBounds = stackInfo.bounds; - stackBounds.inset(STACK_BOUNDS_INSET, STACK_BOUNDS_INSET); - final Rect currentTaskBounds = new Rect(initialTaskBounds); - - // Size by top-left - System.out.println("Growing top-left"); - do { - currentTaskBounds.top -= getStepSize( - currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET); - - currentTaskBounds.left -= getStepSize( - currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET); - - taskResize(taskId, currentTaskBounds, delay_ms, true); - } while (stackBounds.top < currentTaskBounds.top - || stackBounds.left < currentTaskBounds.left); - - // Back to original size - System.out.println("Shrinking top-left"); - do { - currentTaskBounds.top += getStepSize( - currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET); - - currentTaskBounds.left += getStepSize( - currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET); - - taskResize(taskId, currentTaskBounds, delay_ms, true); - } while (initialTaskBounds.top > currentTaskBounds.top - || initialTaskBounds.left > currentTaskBounds.left); - - // Size by top-right - System.out.println("Growing top-right"); - do { - currentTaskBounds.top -= getStepSize( - currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET); - - currentTaskBounds.right += getStepSize( - currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET); - - taskResize(taskId, currentTaskBounds, delay_ms, true); - } while (stackBounds.top < currentTaskBounds.top - || stackBounds.right > currentTaskBounds.right); - - // Back to original size - System.out.println("Shrinking top-right"); - do { - currentTaskBounds.top += getStepSize( - currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET); - - currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right, - stepSize, GREATER_THAN_TARGET); - - taskResize(taskId, currentTaskBounds, delay_ms, true); - } while (initialTaskBounds.top > currentTaskBounds.top - || initialTaskBounds.right < currentTaskBounds.right); - - // Size by bottom-left - System.out.println("Growing bottom-left"); - do { - currentTaskBounds.bottom += getStepSize( - currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET); - - currentTaskBounds.left -= getStepSize( - currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET); - - taskResize(taskId, currentTaskBounds, delay_ms, true); - } while (stackBounds.bottom > currentTaskBounds.bottom - || stackBounds.left < currentTaskBounds.left); - - // Back to original size - System.out.println("Shrinking bottom-left"); - do { - currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom, - initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET); - - currentTaskBounds.left += getStepSize( - currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET); - - taskResize(taskId, currentTaskBounds, delay_ms, true); - } while (initialTaskBounds.bottom < currentTaskBounds.bottom - || initialTaskBounds.left > currentTaskBounds.left); - - // Size by bottom-right - System.out.println("Growing bottom-right"); - do { - currentTaskBounds.bottom += getStepSize( - currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET); - - currentTaskBounds.right += getStepSize( - currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET); - - taskResize(taskId, currentTaskBounds, delay_ms, true); - } while (stackBounds.bottom > currentTaskBounds.bottom - || stackBounds.right > currentTaskBounds.right); - - // Back to original size - System.out.println("Shrinking bottom-right"); - do { - currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom, - initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET); - - currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right, - stepSize, GREATER_THAN_TARGET); - - taskResize(taskId, currentTaskBounds, delay_ms, true); - } while (initialTaskBounds.bottom < currentTaskBounds.bottom - || initialTaskBounds.right < currentTaskBounds.right); - } - - private int getStepSize(int current, int target, int inStepSize, boolean greaterThanTarget) { - int stepSize = 0; - if (greaterThanTarget && target < current) { - current -= inStepSize; - stepSize = inStepSize; - if (target > current) { - stepSize -= (target - current); - } - } - if (!greaterThanTarget && target > current) { - current += inStepSize; - stepSize = inStepSize; - if (target < current) { - stepSize += (current - target); - } - } - return stepSize; - } - - private List<Configuration> getRecentConfigurations(int days) { - IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( - Context.USAGE_STATS_SERVICE)); - final long now = System.currentTimeMillis(); - final long nDaysAgo = now - (days * 24 * 60 * 60 * 1000); - try { - @SuppressWarnings("unchecked") - ParceledListSlice<ConfigurationStats> configStatsSlice = usm.queryConfigurationStats( - UsageStatsManager.INTERVAL_BEST, nDaysAgo, now, "com.android.shell"); - if (configStatsSlice == null) { - return Collections.emptyList(); - } - - final ArrayMap<Configuration, Integer> recentConfigs = new ArrayMap<>(); - final List<ConfigurationStats> configStatsList = configStatsSlice.getList(); - final int configStatsListSize = configStatsList.size(); - for (int i = 0; i < configStatsListSize; i++) { - final ConfigurationStats stats = configStatsList.get(i); - final int indexOfKey = recentConfigs.indexOfKey(stats.getConfiguration()); - if (indexOfKey < 0) { - recentConfigs.put(stats.getConfiguration(), stats.getActivationCount()); - } else { - recentConfigs.setValueAt(indexOfKey, - recentConfigs.valueAt(indexOfKey) + stats.getActivationCount()); - } - } - - final Comparator<Configuration> comparator = new Comparator<Configuration>() { - @Override - public int compare(Configuration a, Configuration b) { - return recentConfigs.get(b).compareTo(recentConfigs.get(a)); - } - }; - - ArrayList<Configuration> configs = new ArrayList<>(recentConfigs.size()); - configs.addAll(recentConfigs.keySet()); - Collections.sort(configs, comparator); - return configs; - - } catch (RemoteException e) { - return Collections.emptyList(); - } - } - - private void runGetConfig() throws Exception { - int days = 14; - String option = nextOption(); - if (option != null) { - if (!option.equals("--days")) { - throw new IllegalArgumentException("unrecognized option " + option); - } - - days = Integer.parseInt(nextArgRequired()); - if (days <= 0) { - throw new IllegalArgumentException("--days must be a positive integer"); - } - } - - try { - Configuration config = mAm.getConfiguration(); - if (config == null) { - System.err.println("Activity manager has no configuration"); - return; - } - - System.out.println("config: " + Configuration.resourceQualifierString(config)); - System.out.println("abi: " + TextUtils.join(",", Build.SUPPORTED_ABIS)); - - final List<Configuration> recentConfigs = getRecentConfigurations(days); - final int recentConfigSize = recentConfigs.size(); - if (recentConfigSize > 0) { - System.out.println("recentConfigs:"); - } - - for (int i = 0; i < recentConfigSize; i++) { - System.out.println(" config: " + Configuration.resourceQualifierString( - recentConfigs.get(i))); - } - - } catch (RemoteException e) { - } - } - - private void runSuppressResizeConfigChanges() throws Exception { - boolean suppress = Boolean.valueOf(nextArgRequired()); - - try { - mAm.suppressResizeConfigChanges(suppress); - } catch (RemoteException e) { - System.err.println("Error suppressing resize config changes: " + e); - } - } - - private void runSetInactive() throws Exception { - int userId = UserHandle.USER_CURRENT; - - String opt; - while ((opt=nextOption()) != null) { - if (opt.equals("--user")) { - userId = parseUserArg(nextArgRequired()); - } else { - System.err.println("Error: Unknown option: " + opt); - return; - } - } - String packageName = nextArgRequired(); - String value = nextArgRequired(); - - IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( - Context.USAGE_STATS_SERVICE)); - usm.setAppInactive(packageName, Boolean.parseBoolean(value), userId); - } - - private void runGetInactive() throws Exception { - int userId = UserHandle.USER_CURRENT; - - String opt; - while ((opt=nextOption()) != null) { - if (opt.equals("--user")) { - userId = parseUserArg(nextArgRequired()); - } else { - System.err.println("Error: Unknown option: " + opt); - return; - } - } - String packageName = nextArgRequired(); - - IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( - Context.USAGE_STATS_SERVICE)); - boolean isIdle = usm.isAppInactive(packageName, userId); - System.out.println("Idle=" + isIdle); - } - - private void runSendTrimMemory() throws Exception { - int userId = UserHandle.USER_CURRENT; - String opt; - while ((opt = nextOption()) != null) { - if (opt.equals("--user")) { - userId = parseUserArg(nextArgRequired()); - if (userId == UserHandle.USER_ALL) { - System.err.println("Error: Can't use user 'all'"); - return; - } - } else { - System.err.println("Error: Unknown option: " + opt); - return; - } - } - - String proc = nextArgRequired(); - String levelArg = nextArgRequired(); - int level; - switch (levelArg) { - case "HIDDEN": - level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN; - break; - case "RUNNING_MODERATE": - level = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE; - break; - case "BACKGROUND": - level = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; - break; - case "RUNNING_LOW": - level = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; - break; - case "MODERATE": - level = ComponentCallbacks2.TRIM_MEMORY_MODERATE; - break; - case "RUNNING_CRITICAL": - level = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL; - break; - case "COMPLETE": - level = ComponentCallbacks2.TRIM_MEMORY_COMPLETE; - break; - default: - System.err.println("Error: Unknown level option: " + levelArg); - return; - } - if (!mAm.setProcessMemoryTrimLevel(proc, userId, level)) { - System.err.println("Error: Failure to set the level - probably Unknown Process: " + - proc); - } - } - - private void runGetCurrentUser() throws Exception { - UserInfo currentUser = Preconditions.checkNotNull(mAm.getCurrentUser(), - "Current user not set"); - System.out.println(currentUser.id); - } - - /** - * Open the given file for sending into the system process. This verifies - * with SELinux that the system will have access to the file. - */ - private static ParcelFileDescriptor openForSystemServer(File file, int mode) - throws FileNotFoundException { - final ParcelFileDescriptor fd = ParcelFileDescriptor.open(file, mode); - final String tcon = SELinux.getFileContext(file.getAbsolutePath()); - if (!SELinux.checkSELinuxAccess("u:r:system_server:s0", tcon, "file", "read")) { - throw new FileNotFoundException("System server has no access to file context " + tcon); - } - return fd; - } - - private Rect getBounds() { - String leftStr = nextArgRequired(); - int left = Integer.parseInt(leftStr); - String topStr = nextArgRequired(); - int top = Integer.parseInt(topStr); - String rightStr = nextArgRequired(); - int right = Integer.parseInt(rightStr); - String bottomStr = nextArgRequired(); - int bottom = Integer.parseInt(bottomStr); - if (left < 0) { - System.err.println("Error: bad left arg: " + leftStr); - return null; - } - if (top < 0) { - System.err.println("Error: bad top arg: " + topStr); - return null; - } - if (right <= 0) { - System.err.println("Error: bad right arg: " + rightStr); - return null; - } - if (bottom <= 0) { - System.err.println("Error: bad bottom arg: " + bottomStr); - return null; - } - return new Rect(left, top, right, bottom); + instrument.run(); } } diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java new file mode 100644 index 000000000000..8eefd256a69d --- /dev/null +++ b/cmds/am/src/com/android/commands/am/Instrument.java @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.commands.am; + +import android.app.IActivityManager; +import android.app.IInstrumentationWatcher; +import android.app.Instrumentation; +import android.app.UiAutomationConnection; +import android.content.ComponentName; +import android.content.pm.IPackageManager; +import android.content.pm.InstrumentationInfo; +import android.os.Build; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.util.AndroidException; +import android.util.proto.ProtoOutputStream; +import android.view.IWindowManager; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + + +/** + * Runs the am instrument command + */ +public class Instrument { + private final IActivityManager mAm; + private final IPackageManager mPm; + private final IWindowManager mWm; + + // Command line arguments + public String profileFile = null; + public boolean wait = false; + public boolean rawMode = false; + public boolean proto = false; + public boolean noWindowAnimation = false; + public String abi = null; + public int userId = UserHandle.USER_CURRENT; + public Bundle args = new Bundle(); + // Required + public String componentNameArg; + + /** + * Construct the instrument command runner. + */ + public Instrument(IActivityManager am, IPackageManager pm) { + mAm = am; + mPm = pm; + mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); + } + + /** + * Base class for status reporting. + * + * All the methods on this interface are called within the synchronized block + * of the InstrumentationWatcher, so calls are in order. However, that means + * you must be careful not to do blocking operations because you don't know + * exactly the locking dependencies. + */ + private interface StatusReporter { + /** + * Status update for tests. + */ + public void onInstrumentationStatusLocked(ComponentName name, int resultCode, + Bundle results); + + /** + * The tests finished. + */ + public void onInstrumentationFinishedLocked(ComponentName name, int resultCode, + Bundle results); + + /** + * @param errorText a description of the error + * @param commandError True if the error is related to the commandline, as opposed + * to a test failing. + */ + public void onError(String errorText, boolean commandError); + } + + /** + * Printer for the 'classic' text based status reporting. + */ + private class TextStatusReporter implements StatusReporter { + private boolean mRawMode; + + /** + * Human-ish readable output. + * + * @param rawMode In "raw mode" (true), all bundles are dumped. + * In "pretty mode" (false), if a bundle includes + * Instrumentation.REPORT_KEY_STREAMRESULT, just print that. + */ + public TextStatusReporter(boolean rawMode) { + mRawMode = rawMode; + } + + @Override + public void onInstrumentationStatusLocked(ComponentName name, int resultCode, + Bundle results) { + // pretty printer mode? + String pretty = null; + if (!mRawMode && results != null) { + pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT); + } + if (pretty != null) { + System.out.print(pretty); + } else { + if (results != null) { + for (String key : results.keySet()) { + System.out.println( + "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key)); + } + } + System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode); + } + } + + @Override + public void onInstrumentationFinishedLocked(ComponentName name, int resultCode, + Bundle results) { + // pretty printer mode? + String pretty = null; + if (!mRawMode && results != null) { + pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT); + } + if (pretty != null) { + System.out.println(pretty); + } else { + if (results != null) { + for (String key : results.keySet()) { + System.out.println( + "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key)); + } + } + System.out.println("INSTRUMENTATION_CODE: " + resultCode); + } + } + + @Override + public void onError(String errorText, boolean commandError) { + // The regular BaseCommand error printing will print the commandErrors. + if (!commandError) { + System.out.println(errorText); + } + } + } + + /** + * Printer for the protobuf based status reporting. + */ + private class ProtoStatusReporter implements StatusReporter { + @Override + public void onInstrumentationStatusLocked(ComponentName name, int resultCode, + Bundle results) { + final ProtoOutputStream proto = new ProtoOutputStream(); + + final long token = proto.startRepeatedObject(InstrumentationData.Session.TEST_STATUS); + + proto.writeSInt32(InstrumentationData.TestStatus.RESULT_CODE, resultCode); + writeBundle(proto, InstrumentationData.TestStatus.RESULTS, results); + + proto.endRepeatedObject(token); + writeProtoToStdout(proto); + } + + @Override + public void onInstrumentationFinishedLocked(ComponentName name, int resultCode, + Bundle results) { + final ProtoOutputStream proto = new ProtoOutputStream(); + + final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS); + + proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE, + InstrumentationData.SESSION_FINISHED); + proto.writeSInt32(InstrumentationData.SessionStatus.RESULT_CODE, resultCode); + writeBundle(proto, InstrumentationData.SessionStatus.RESULTS, results); + + proto.endObject(token); + writeProtoToStdout(proto); + } + + @Override + public void onError(String errorText, boolean commandError) { + final ProtoOutputStream proto = new ProtoOutputStream(); + + final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS); + + proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE, + InstrumentationData.SESSION_ABORTED); + proto.writeString(InstrumentationData.SessionStatus.ERROR_TEXT, errorText); + + proto.endObject(token); + writeProtoToStdout(proto); + } + + private void writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle) { + final long bundleToken = proto.startObject(fieldId); + + for (final String key: bundle.keySet()) { + final long entryToken = proto.startRepeatedObject( + InstrumentationData.ResultsBundle.ENTRIES); + + proto.writeString(InstrumentationData.ResultsBundleEntry.KEY, key); + + final Object val = bundle.get(key); + if (val instanceof String) { + proto.writeString(InstrumentationData.ResultsBundleEntry.VALUE_STRING, + (String)val); + } else if (val instanceof Byte) { + proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT, + ((Byte)val).intValue()); + } else if (val instanceof Double) { + proto.writeDouble(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE, + ((Double)val).doubleValue()); + } else if (val instanceof Float) { + proto.writeFloat(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT, + ((Float)val).floatValue()); + } else if (val instanceof Integer) { + proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT, + ((Integer)val).intValue()); + } else if (val instanceof Long) { + proto.writeSInt64(InstrumentationData.ResultsBundleEntry.VALUE_LONG, + ((Long)val).longValue()); + } else if (val instanceof Short) { + proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT, + ((Short)val).intValue()); + } else if (val instanceof Bundle) { + writeBundle(proto, InstrumentationData.ResultsBundleEntry.VALUE_BUNDLE, + (Bundle)val); + } + + proto.endRepeatedObject(entryToken); + } + + proto.endObject(bundleToken); + } + + private void writeProtoToStdout(ProtoOutputStream proto) { + try { + System.out.write(proto.getBytes()); + System.out.flush(); + } catch (IOException ex) { + System.err.println("Error writing finished response: "); + ex.printStackTrace(System.err); + } + } + } + + + /** + * Callbacks from the remote instrumentation instance. + */ + private class InstrumentationWatcher extends IInstrumentationWatcher.Stub { + private final StatusReporter mReporter; + + private boolean mFinished = false; + + public InstrumentationWatcher(StatusReporter reporter) { + mReporter = reporter; + } + + @Override + public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) { + synchronized (this) { + mReporter.onInstrumentationStatusLocked(name, resultCode, results); + notifyAll(); + } + } + + @Override + public void instrumentationFinished(ComponentName name, int resultCode, Bundle results) { + synchronized (this) { + mReporter.onInstrumentationFinishedLocked(name, resultCode, results); + mFinished = true; + notifyAll(); + } + } + + public boolean waitForFinish() { + synchronized (this) { + while (!mFinished) { + try { + if (!mAm.asBinder().pingBinder()) { + return false; + } + wait(1000); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + } + return true; + } + } + + /** + * Figure out which component they really meant. + */ + private ComponentName parseComponentName(String cnArg) throws Exception { + if (cnArg.contains("/")) { + ComponentName cn = ComponentName.unflattenFromString(cnArg); + if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg); + return cn; + } else { + List<InstrumentationInfo> infos = mPm.queryInstrumentation(null, 0).getList(); + + final int numInfos = infos == null ? 0: infos.size(); + ArrayList<ComponentName> cns = new ArrayList<>(); + for (int i = 0; i < numInfos; i++) { + InstrumentationInfo info = infos.get(i); + + ComponentName c = new ComponentName(info.packageName, info.name); + if (cnArg.equals(info.packageName)) { + cns.add(c); + } + } + + if (cns.size() == 0) { + throw new IllegalArgumentException("No instrumentation found for: " + cnArg); + } else if (cns.size() == 1) { + return cns.get(0); + } else { + StringBuilder cnsStr = new StringBuilder(); + final int numCns = cns.size(); + for (int i = 0; i < numCns; i++) { + cnsStr.append(cns.get(i).flattenToString()); + cnsStr.append(", "); + } + + // Remove last ", " + cnsStr.setLength(cnsStr.length() - 2); + + throw new IllegalArgumentException("Found multiple instrumentations: " + + cnsStr.toString()); + } + } + } + + /** + * Run the instrumentation. + */ + public void run() throws Exception { + StatusReporter reporter = null; + float[] oldAnims = null; + + try { + // Choose which output we will do. + if (proto) { + reporter = new ProtoStatusReporter(); + } else if (wait) { + reporter = new TextStatusReporter(rawMode); + } + + // Choose whether we have to wait for the results. + InstrumentationWatcher watcher = null; + UiAutomationConnection connection = null; + if (reporter != null) { + watcher = new InstrumentationWatcher(reporter); + connection = new UiAutomationConnection(); + } + + // Set the window animation if necessary + if (noWindowAnimation) { + oldAnims = mWm.getAnimationScales(); + mWm.setAnimationScale(0, 0.0f); + mWm.setAnimationScale(1, 0.0f); + } + + // Figure out which component we are tring to do. + final ComponentName cn = parseComponentName(componentNameArg); + + // Choose an ABI if necessary + if (abi != null) { + final String[] supportedAbis = Build.SUPPORTED_ABIS; + boolean matched = false; + for (String supportedAbi : supportedAbis) { + if (supportedAbi.equals(abi)) { + matched = true; + break; + } + } + if (!matched) { + throw new AndroidException( + "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi); + } + } + + // Start the instrumentation + if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, + abi)) { + throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString()); + } + + // If we have been requested to wait, do so until the instrumentation is finished. + if (watcher != null) { + if (!watcher.waitForFinish()) { + reporter.onError("INSTRUMENTATION_ABORTED: System has crashed.", false); + return; + } + } + } catch (Exception ex) { + // Report failures + if (reporter != null) { + reporter.onError(ex.getMessage(), true); + } + + // And re-throw the exception + throw ex; + } finally { + // Clean up + if (oldAnims != null) { + mWm.setAnimationScales(oldAnims); + } + } + } +} + diff --git a/cmds/app_process/Android.mk b/cmds/app_process/Android.mk index eaad3a7ec529..72fe0516f480 100644 --- a/cmds/app_process/Android.mk +++ b/cmds/app_process/Android.mk @@ -5,6 +5,7 @@ app_process_common_shared_libs := \ libbinder \ libcutils \ libdl \ + libhwbinder \ liblog \ libnativeloader \ libutils \ diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp index 0ea141c292dd..e56417b82fc0 100644 --- a/cmds/app_process/app_main.cpp +++ b/cmds/app_process/app_main.cpp @@ -14,7 +14,7 @@ #include <unistd.h> #include <binder/IPCThreadState.h> -#include <binder/ProcessState.h> +#include <hwbinder/IPCThreadState.h> #include <utils/Log.h> #include <cutils/memory.h> #include <cutils/properties.h> @@ -85,6 +85,7 @@ public: ar->callMain(mClassName, mClass, mArgs); IPCThreadState::self()->stopProcess(); + hardware::IPCThreadState::self()->stopProcess(); } virtual void onZygoteInit() @@ -99,6 +100,7 @@ public: if (mClassName.isEmpty()) { // if zygote IPCThreadState::self()->stopProcess(); + hardware::IPCThreadState::self()->stopProcess(); } AndroidRuntime::onExit(code); diff --git a/cmds/appops/appops b/cmds/appops/appops index 25d20311aae2..5dc85aa0979c 100755 --- a/cmds/appops/appops +++ b/cmds/appops/appops @@ -1 +1,2 @@ -cmd appops $@ +#!/system/bin/sh +cmd appops "$@" diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 5bf80765a7de..1bcfb22fe2f2 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -17,21 +17,26 @@ package com.android.commands.bmgr; import android.app.backup.BackupManager; +import android.app.backup.BackupManagerMonitor; import android.app.backup.BackupProgress; -import android.app.backup.RestoreSet; import android.app.backup.IBackupManager; import android.app.backup.IBackupObserver; import android.app.backup.IRestoreObserver; import android.app.backup.IRestoreSession; +import android.app.backup.RestoreSet; +import android.app.backup.ISelectBackupTransportCallback; +import android.content.ComponentName; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.util.Log; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.concurrent.CountDownLatch; public final class Bmgr { IBackupManager mBmgr; @@ -122,6 +127,11 @@ public final class Bmgr { return; } + if ("cancel".equals(op)) { + doCancel(); + return; + } + if ("whitelist".equals(op)) { doPrintWhitelist(); return; @@ -265,12 +275,14 @@ public final class Bmgr { return "Agent error"; case BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED: return "Size quota exceeded"; + case BackupManager.ERROR_BACKUP_CANCELLED: + return "Backup Cancelled"; default: return "Unknown error"; } } - private void backupNowAllPackages() { + private void backupNowAllPackages(boolean nonIncrementalBackup) { int userId = UserHandle.USER_SYSTEM; IPackageManager mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); @@ -297,14 +309,20 @@ public final class Bmgr { System.err.println(BMGR_NOT_RUNNING_ERR); } } - backupNowPackages(packages); + backupNowPackages(packages, nonIncrementalBackup); } } - private void backupNowPackages(List<String> packages) { + private void backupNowPackages(List<String> packages, boolean nonIncrementalBackup) { + int flags = 0; + if (nonIncrementalBackup) { + flags |= BackupManager.FLAG_NON_INCREMENTAL_BACKUP; + } try { BackupObserver observer = new BackupObserver(); - int err = mBmgr.requestBackup(packages.toArray(new String[packages.size()]), observer); + // TODO: implement monitor here? + int err = mBmgr.requestBackup(packages.toArray(new String[packages.size()]), observer, + null, flags); if (err == 0) { // Off and running -- wait for the backup to complete observer.waitForCompletion(); @@ -320,29 +338,51 @@ public final class Bmgr { private void doBackupNow() { String pkg; boolean backupAll = false; + boolean nonIncrementalBackup = false; ArrayList<String> allPkgs = new ArrayList<String>(); while ((pkg = nextArg()) != null) { if (pkg.equals("--all")) { backupAll = true; + } else if (pkg.equals("--non-incremental")) { + nonIncrementalBackup = true; + } else if (pkg.equals("--incremental")) { + nonIncrementalBackup = false; } else { allPkgs.add(pkg); } } if (backupAll) { if (allPkgs.size() == 0) { - System.out.println("Running backup for all packages."); - backupNowAllPackages(); + System.out.println("Running " + (nonIncrementalBackup ? "non-" : "") + + "incremental backup for all packages."); + backupNowAllPackages(nonIncrementalBackup); } else { System.err.println("Provide only '--all' flag or list of packages."); } } else if (allPkgs.size() > 0) { - System.out.println("Running backup for " + allPkgs.size() +" requested packages."); - backupNowPackages(allPkgs); + System.out.println("Running " + (nonIncrementalBackup ? "non-" : "") + + "incremental backup for " + allPkgs.size() +" requested packages."); + backupNowPackages(allPkgs, nonIncrementalBackup); } else { System.err.println("Provide '--all' flag or list of packages."); } } + private void doCancel() { + String arg = nextArg(); + if ("backups".equals(arg)) { + try { + mBmgr.cancelBackups(); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(BMGR_NOT_RUNNING_ERR); + } + return; + } + + System.err.println("Unknown command."); + } + private void doTransport() { try { String which = nextArg(); @@ -351,6 +391,11 @@ public final class Bmgr { return; } + if ("-c".equals(which)) { + doTransportByComponent(); + return; + } + String old = mBmgr.selectBackupTransport(which); if (old == null) { System.out.println("Unknown transport '" + which @@ -358,9 +403,47 @@ public final class Bmgr { } else { System.out.println("Selected transport " + which + " (formerly " + old + ")"); } + + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(BMGR_NOT_RUNNING_ERR); + } + } + + private void doTransportByComponent() { + String which = nextArg(); + if (which == null) { + showUsage(); + return; + } + + final CountDownLatch latch = new CountDownLatch(1); + + try { + mBmgr.selectBackupTransportAsync(ComponentName.unflattenFromString(which), + new ISelectBackupTransportCallback.Stub() { + @Override + public void onSuccess(String transportName) { + System.out.println("Success. Selected transport: " + transportName); + latch.countDown(); + } + + @Override + public void onFailure(int reason) { + System.err.println("Failure. error=" + reason); + latch.countDown(); + } + }); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); + return; + } + + try { + latch.await(); + } catch (InterruptedException e) { + System.err.println("Operation interrupted."); } } @@ -415,7 +498,16 @@ public final class Bmgr { } private void doListTransports() { + String arg = nextArg(); + try { + if ("-c".equals(arg)) { + for (ComponentName transport : mBmgr.listAllTransportComponents()) { + System.out.println(transport.flattenToShortString()); + } + return; + } + String current = mBmgr.getCurrentTransport(); String[] transports = mBmgr.listAllTransports(); if (transports == null || transports.length == 0) { @@ -436,7 +528,8 @@ public final class Bmgr { private void doListRestoreSets() { try { RestoreObserver observer = new RestoreObserver(); - int err = mRestore.getAvailableRestoreSets(observer); + // TODO implement monitor here + int err = mRestore.getAvailableRestoreSets(observer, null); if (err != 0) { System.out.println("Unable to request restore sets"); } else { @@ -487,6 +580,11 @@ public final class Bmgr { } } + /** + * Wait until either {@link #restoreFinished} or {@link #restoreStarting} is called. + * Once one is called, it clears the internal flag again, so that the same observer intance + * can be reused for a next operation. + */ public void waitForCompletion() { // The restoreFinished() callback will throw the 'done' flag; we // just sit and wait on that notification. @@ -497,6 +595,7 @@ public final class Bmgr { } catch (InterruptedException ex) { } } + done = false; } } } @@ -539,7 +638,8 @@ public final class Bmgr { } RestoreObserver observer = new RestoreObserver(); - int err = mRestore.restorePackage(pkg, observer); + // TODO implement monitor here + int err = mRestore.restorePackage(pkg, observer, null ); if (err == 0) { // Off and running -- wait for the restore to complete observer.waitForCompletion(); @@ -566,7 +666,8 @@ public final class Bmgr { return; } RestoreSet[] sets = null; - int err = mRestore.getAvailableRestoreSets(observer); + // TODO implement monitor here + int err = mRestore.getAvailableRestoreSets(observer, null); if (err == 0) { observer.waitForCompletion(); sets = observer.sets; @@ -575,11 +676,12 @@ public final class Bmgr { if (s.token == token) { System.out.println("Scheduling restore: " + s.name); if (filter == null) { - didRestore = (mRestore.restoreAll(token, observer) == 0); + didRestore = (mRestore.restoreAll(token, observer, null) == 0); } else { String[] names = new String[filter.size()]; filter.toArray(names); - didRestore = (mRestore.restoreSome(token, observer, names) == 0); + didRestore = (mRestore.restoreSome(token, observer, + null, names) == 0); } break; } @@ -637,9 +739,9 @@ public final class Bmgr { System.err.println(" bmgr backup PACKAGE"); System.err.println(" bmgr enable BOOL"); System.err.println(" bmgr enabled"); - System.err.println(" bmgr list transports"); + System.err.println(" bmgr list transports [-c]"); System.err.println(" bmgr list sets"); - System.err.println(" bmgr transport WHICH"); + System.err.println(" bmgr transport WHICH|-c WHICH_COMPONENT"); System.err.println(" bmgr restore TOKEN"); System.err.println(" bmgr restore TOKEN PACKAGE..."); System.err.println(" bmgr restore PACKAGE"); @@ -647,6 +749,7 @@ public final class Bmgr { System.err.println(" bmgr wipe TRANSPORT PACKAGE"); System.err.println(" bmgr fullbackup PACKAGE..."); System.err.println(" bmgr backupnow --all|PACKAGE..."); + System.err.println(" bmgr cancel backups"); System.err.println(""); System.err.println("The 'backup' command schedules a backup pass for the named package."); System.err.println("Note that the backup pass will effectively be a no-op if the package"); @@ -661,15 +764,18 @@ public final class Bmgr { System.err.println("the backup mechanism."); System.err.println(""); System.err.println("The 'list transports' command reports the names of the backup transports"); - System.err.println("currently available on the device. These names can be passed as arguments"); + System.err.println("BackupManager is currently bound to. These names can be passed as arguments"); System.err.println("to the 'transport' and 'wipe' commands. The currently active transport"); - System.err.println("is indicated with a '*' character."); + System.err.println("is indicated with a '*' character. If -c flag is used, all available"); + System.err.println("transport components on the device are listed. These can be used with"); + System.err.println("the component variant of 'transport' command."); System.err.println(""); System.err.println("The 'list sets' command reports the token and name of each restore set"); System.err.println("available to the device via the currently active transport."); System.err.println(""); System.err.println("The 'transport' command designates the named transport as the currently"); - System.err.println("active one. This setting is persistent across reboots."); + System.err.println("active one. This setting is persistent across reboots. If -c flag is"); + System.err.println("specified, the following string is treated as a component name."); System.err.println(""); System.err.println("The 'restore' command when given just a restore token initiates a full-system"); System.err.println("restore operation from the currently active transport. It will deliver"); @@ -703,5 +809,6 @@ public final class Bmgr { System.err.println("For each package it will run key/value or full data backup "); System.err.println("depending on the package's manifest declarations."); System.err.println("The data is sent via the currently active transport."); + System.err.println("The 'cancel backups' command cancels all running backups."); } } diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk index 3a92b9e74144..0e2c13ee1719 100644 --- a/cmds/bootanimation/Android.mk +++ b/cmds/bootanimation/Android.mk @@ -26,7 +26,8 @@ LOCAL_SHARED_LIBRARIES := \ libGLESv1_CM \ libgui \ libOpenSLES \ - libtinyalsa + libtinyalsa \ + libbase LOCAL_MODULE:= bootanimation diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 9cfef47b2ea1..7394490fc8d4 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -18,6 +18,7 @@ #define LOG_TAG "BootAnimation" #include <stdint.h> +#include <inttypes.h> #include <sys/inotify.h> #include <sys/poll.h> #include <sys/stat.h> @@ -35,6 +36,9 @@ #include <utils/Atomic.h> #include <utils/Errors.h> #include <utils/Log.h> +#include <utils/SystemClock.h> + +#include <android-base/properties.h> #include <ui/PixelFormat.h> #include <ui/Rect.h> @@ -49,8 +53,8 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #include <SkBitmap.h> +#include <SkImage.h> #include <SkStream.h> -#include <SkImageDecoder.h> #pragma GCC diagnostic pop #include <GLES/gl.h> @@ -65,6 +69,9 @@ namespace android { static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip"; static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip"; static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip"; +static const char OEM_SHUTDOWNANIMATION_FILE[] = "/oem/media/shutdownanimation.zip"; +static const char SYSTEM_SHUTDOWNANIMATION_FILE[] = "/system/media/shutdownanimation.zip"; + static const char SYSTEM_DATA_DIR_PATH[] = "/data/system"; static const char SYSTEM_TIME_DIR_NAME[] = "time"; static const char SYSTEM_TIME_DIR_PATH[] = "/data/system/time"; @@ -104,11 +111,15 @@ BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true), mTimeIsAccu mSession = new SurfaceComposerClient(); // If the system has already booted, the animation is not being used for a boot. - mSystemBoot = !property_get_bool(BOOT_COMPLETED_PROP_NAME, 0); + mSystemBoot = !android::base::GetBoolProperty(BOOT_COMPLETED_PROP_NAME, false); + std::string powerCtl = android::base::GetProperty("sys.powerctl", ""); + if (powerCtl.empty()) { + mShuttingDown = false; + } else { + mShuttingDown = true; + } } -BootAnimation::~BootAnimation() {} - void BootAnimation::onFirstRef() { status_t err = mSession->linkToComposerDeath(this); ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err)); @@ -140,8 +151,10 @@ status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, if (asset == NULL) return NO_INIT; SkBitmap bitmap; - SkImageDecoder::DecodeMemory(asset->getBuffer(false), asset->getLength(), - &bitmap, kUnknown_SkColorType, SkImageDecoder::kDecodePixels_Mode); + sk_sp<SkData> data = SkData::MakeWithoutCopy(asset->getBuffer(false), + asset->getLength()); + sk_sp<SkImage> image = SkImage::MakeFromEncoded(data); + image->asLegacyBitmap(&bitmap, SkImage::kRO_LegacyBitmapMode); asset->close(); delete asset; @@ -193,15 +206,10 @@ status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) { SkBitmap bitmap; - SkMemoryStream stream(map->getDataPtr(), map->getDataLength()); - SkImageDecoder* codec = SkImageDecoder::Factory(&stream); - if (codec != NULL) { - codec->setDitherImage(false); - codec->decode(&stream, &bitmap, - kN32_SkColorType, - SkImageDecoder::kDecodePixels_Mode); - delete codec; - } + sk_sp<SkData> data = SkData::MakeWithoutCopy(map->getDataPtr(), + map->getDataLength()); + sk_sp<SkImage> image = SkImage::MakeFromEncoded(data); + image->asLegacyBitmap(&bitmap, SkImage::kRO_LegacyBitmapMode); // FileMap memory is never released until application exit. // Release it now as the texture is already loaded and the memory used for @@ -317,16 +325,23 @@ status_t BootAnimation::readyToRun() { char decrypt[PROPERTY_VALUE_MAX]; property_get("vold.decrypt", decrypt, ""); - bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt); + bool encryptedAnimation = atoi(decrypt) != 0 || + !strcmp("trigger_restart_min_framework", decrypt); - if (encryptedAnimation && (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0)) { + if (!mShuttingDown && encryptedAnimation && + (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0)) { mZipFileName = SYSTEM_ENCRYPTED_BOOTANIMATION_FILE; + return NO_ERROR; } - else if (access(OEM_BOOTANIMATION_FILE, R_OK) == 0) { - mZipFileName = OEM_BOOTANIMATION_FILE; - } - else if (access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) { - mZipFileName = SYSTEM_BOOTANIMATION_FILE; + static const char* bootFiles[] = {OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE}; + static const char* shutdownFiles[] = + {OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE}; + + for (const char* f : (!mShuttingDown ? bootFiles : shutdownFiles)) { + if (access(f, R_OK) == 0) { + mZipFileName = f; + return NO_ERROR; + } } return NO_ERROR; } @@ -355,6 +370,8 @@ bool BootAnimation::threadLoop() bool BootAnimation::android() { + ALOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot", + elapsedRealtime()); initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png"); initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png"); @@ -781,11 +798,12 @@ bool BootAnimation::preloadZip(Animation& animation) } // Create and initialize audioplay if there is a wav file in any of the animations. + // Do it on a separate thread so we don't hold up the animation intro. if (partWithAudio != NULL) { ALOGD("found audio.wav, creating playback engine"); - if (!audioplay::create(partWithAudio->audioData, partWithAudio->audioLength)) { - return false; - } + mInitAudioThread = new InitAudioThread(partWithAudio->audioData, + partWithAudio->audioLength); + mInitAudioThread->run("BootAnimation::InitAudioThread", PRIORITY_NORMAL); } zip->endIteration(cookie); @@ -853,9 +871,14 @@ bool BootAnimation::movie() playAnimation(*animation); - if (mTimeCheckThread != NULL) { + if (mTimeCheckThread != nullptr) { mTimeCheckThread->requestExit(); - mTimeCheckThread = NULL; + mTimeCheckThread = nullptr; + } + + // We should have joined mInitAudioThread thread in playAnimation + if (mInitAudioThread != nullptr) { + mInitAudioThread = nullptr; } releaseAnimation(animation); @@ -874,6 +897,8 @@ bool BootAnimation::playAnimation(const Animation& animation) const int animationX = (mWidth - animation.width) / 2; const int animationY = (mHeight - animation.height) / 2; + ALOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot", + elapsedRealtime()); for (size_t i=0 ; i<pcount ; i++) { const Animation::Part& part(animation.parts[i]); const size_t fcount = part.frames.size(); @@ -895,6 +920,10 @@ bool BootAnimation::playAnimation(const Animation& animation) // only play audio file the first time we animate the part if (r == 0 && part.audioData && playSoundsAllowed()) { ALOGD("playing clip for part%d, size=%d", (int) i, part.audioLength); + // Block until the audio engine is finished initializing. + if (mInitAudioThread != nullptr) { + mInitAudioThread->join(); + } audioplay::playClip(part.audioData, part.audioLength); } @@ -1038,7 +1067,9 @@ bool BootAnimation::playSoundsAllowed() const { if (!mSystemBoot) { return false; } - + if (mShuttingDown) { // no audio while shutting down + return false; + } // Read the system property to see if we should play the sound. // If it's not present, default to allowed. if (!property_get_bool(PLAY_SOUND_PROP_NAME, 1)) { @@ -1064,7 +1095,7 @@ bool BootAnimation::updateIsTimeAccurate() { if (mTimeIsAccurate) { return true; } - + if (mShuttingDown) return true; struct stat statResult; if(stat(TIME_FORMAT_12_HOUR_FLAG_FILE_PATH, &statResult) == 0) { @@ -1188,6 +1219,17 @@ status_t BootAnimation::TimeCheckThread::readyToRun() { return NO_ERROR; } +BootAnimation::InitAudioThread::InitAudioThread(uint8_t* exampleAudioData, int exampleAudioLength) + : Thread(false), + mExampleAudioData(exampleAudioData), + mExampleAudioLength(exampleAudioLength) {} + +bool BootAnimation::InitAudioThread::threadLoop() { + audioplay::create(mExampleAudioData, mExampleAudioLength); + // Exit immediately + return false; +} + // --------------------------------------------------------------------------- } diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index 7a2e4c28f767..181ef1c596d1 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -39,8 +39,7 @@ class SurfaceControl; class BootAnimation : public Thread, public IBinder::DeathRecipient { public: - BootAnimation(); - virtual ~BootAnimation(); + BootAnimation(); sp<SurfaceComposerClient> session() const; @@ -68,6 +67,16 @@ private: BootAnimation* mBootAnimation; }; + class InitAudioThread : public Thread { + public: + InitAudioThread(uint8_t* exampleAudioData, int mExampleAudioLength); + private: + virtual bool threadLoop(); + + uint8_t* mExampleAudioData; + int mExampleAudioLength; + }; + struct Texture { GLint w; GLint h; @@ -154,9 +163,11 @@ private: bool mTimeIsAccurate; bool mTimeFormat12Hour; bool mSystemBoot; + bool mShuttingDown; String8 mZipFileName; SortedVector<String8> mLoadedFiles; - sp<TimeCheckThread> mTimeCheckThread; + sp<TimeCheckThread> mTimeCheckThread = nullptr; + sp<InitAudioThread> mInitAudioThread = nullptr; }; // --------------------------------------------------------------------------- diff --git a/cmds/bootanimation/bootanim.rc b/cmds/bootanimation/bootanim.rc index 7344ba74f70b..469c9646a4aa 100644 --- a/cmds/bootanimation/bootanim.rc +++ b/cmds/bootanimation/bootanim.rc @@ -1,7 +1,7 @@ service bootanim /system/bin/bootanimation - class core + class core animation user graphics group graphics audio disabled oneshot - writepid /dev/stune/top-app/tasks
\ No newline at end of file + writepid /dev/stune/top-app/tasks diff --git a/cmds/bootanimation/bootanimation_main.cpp b/cmds/bootanimation/bootanimation_main.cpp index 48a34e7dbf62..3689d5ed937e 100644 --- a/cmds/bootanimation/bootanimation_main.cpp +++ b/cmds/bootanimation/bootanimation_main.cpp @@ -16,12 +16,16 @@ #define LOG_TAG "BootAnimation" +#include <stdint.h> +#include <inttypes.h> + #include <binder/IPCThreadState.h> #include <binder/ProcessState.h> #include <binder/IServiceManager.h> #include <cutils/properties.h> #include <sys/resource.h> #include <utils/Log.h> +#include <utils/SystemClock.h> #include <utils/threads.h> #include "BootAnimation.h" @@ -37,17 +41,40 @@ int main() char value[PROPERTY_VALUE_MAX]; property_get("debug.sf.nobootanimation", value, "0"); int noBootAnimation = atoi(value); + if (!noBootAnimation) { + property_get("ro.boot.quiescent", value, "0"); + noBootAnimation = atoi(value); + } ALOGI_IF(noBootAnimation, "boot animation disabled"); if (!noBootAnimation) { sp<ProcessState> proc(ProcessState::self()); ProcessState::self()->startThreadPool(); + // TODO: replace this with better waiting logic in future, b/35253872 + int64_t waitStartTime = elapsedRealtime(); + sp<IServiceManager> sm = defaultServiceManager(); + const String16 name("SurfaceFlinger"); + const int SERVICE_WAIT_SLEEP_MS = 100; + const int LOG_PER_RETRIES = 10; + int retry = 0; + while (sm->checkService(name) == nullptr) { + retry++; + if ((retry % LOG_PER_RETRIES) == 0) { + ALOGW("Waiting for SurfaceFlinger, waited for %" PRId64 " ms", + elapsedRealtime() - waitStartTime); + } + usleep(SERVICE_WAIT_SLEEP_MS * 1000); + }; + int64_t totalWaited = elapsedRealtime() - waitStartTime; + if (totalWaited > SERVICE_WAIT_SLEEP_MS) { + ALOGI("Waiting for SurfaceFlinger took %" PRId64 " ms", totalWaited); + } + // create the boot animation object sp<BootAnimation> boot = new BootAnimation(); IPCThreadState::self()->joinThreadPool(); - } return 0; } diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java index ffc0f875c79c..345895b794a3 100644 --- a/cmds/bu/src/com/android/commands/bu/Backup.java +++ b/cmds/bu/src/com/android/commands/bu/Backup.java @@ -53,15 +53,15 @@ public final class Backup { String arg = nextArg(); if (arg.equals("backup")) { - doFullBackup(OsConstants.STDOUT_FILENO); + doBackup(OsConstants.STDOUT_FILENO); } else if (arg.equals("restore")) { - doFullRestore(OsConstants.STDIN_FILENO); + doRestore(OsConstants.STDIN_FILENO); } else { - Log.e(TAG, "Invalid operation '" + arg + "'"); + showUsage(); } } - private void doFullBackup(int socketFd) { + private void doBackup(int socketFd) { ArrayList<String> packages = new ArrayList<String>(); boolean saveApks = false; boolean saveObbs = false; @@ -70,6 +70,7 @@ public final class Backup { boolean doWidgets = false; boolean allIncludesSystem = true; boolean doCompress = true; + boolean doKeyValue = false; String arg; while ((arg = nextArg()) != null) { @@ -100,6 +101,10 @@ public final class Backup { doCompress = true; } else if ("-nocompress".equals(arg)) { doCompress = false; + } else if ("-keyvalue".equals(arg)) { + doKeyValue = true; + } else if ("-nokeyvalue".equals(arg)) { + doKeyValue = false; } else { Log.w(TAG, "Unknown backup flag " + arg); continue; @@ -123,8 +128,8 @@ public final class Backup { try { fd = ParcelFileDescriptor.adoptFd(socketFd); String[] packArray = new String[packages.size()]; - mBackupManager.fullBackup(fd, saveApks, saveObbs, saveShared, doWidgets, - doEverything, allIncludesSystem, doCompress, packages.toArray(packArray)); + mBackupManager.adbBackup(fd, saveApks, saveObbs, saveShared, doWidgets, doEverything, + allIncludesSystem, doCompress, doKeyValue, packages.toArray(packArray)); } catch (RemoteException e) { Log.e(TAG, "Unable to invoke backup manager for backup"); } finally { @@ -136,12 +141,12 @@ public final class Backup { } } - private void doFullRestore(int socketFd) { + private void doRestore(int socketFd) { // No arguments to restore ParcelFileDescriptor fd = null; try { fd = ParcelFileDescriptor.adoptFd(socketFd); - mBackupManager.fullRestore(fd); + mBackupManager.adbRestore(fd); } catch (RemoteException e) { Log.e(TAG, "Unable to invoke backup manager for restore"); } finally { @@ -153,6 +158,21 @@ public final class Backup { } } + private static void showUsage() { + System.err.println(" backup [-f FILE] [-apk|-noapk] [-obb|-noobb] [-shared|-noshared] [-all]"); + System.err.println(" [-system|-nosystem] [-keyvalue|-nokeyvalue] [PACKAGE...]"); + System.err.println(" write an archive of the device's data to FILE [default=backup.adb]"); + System.err.println(" package list optional if -all/-shared are supplied"); + System.err.println(" -apk/-noapk: do/don't back up .apk files (default -noapk)"); + System.err.println(" -obb/-noobb: do/don't back up .obb files (default -noobb)"); + System.err.println(" -shared|-noshared: do/don't back up shared storage (default -noshared)"); + System.err.println(" -all: back up all installed applications"); + System.err.println(" -system|-nosystem: include system apps in -all (default -system)"); + System.err.println(" -keyvalue|-nokeyvalue: include apps that perform key/value backups."); + System.err.println(" (default -nokeyvalue)"); + System.err.println(" restore FILE restore device contents from FILE"); + } + private String nextArg() { if (mNextArg >= mArgs.length) { return null; diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java index d43b8c564e96..3687f10f9974 100644 --- a/cmds/content/src/com/android/commands/content/Content.java +++ b/cmds/content/src/com/android/commands/content/Content.java @@ -16,9 +16,10 @@ package com.android.commands.content; -import android.app.ActivityManagerNative; +import android.app.ActivityManager; +import android.app.ContentProviderHolder; import android.app.IActivityManager; -import android.app.IActivityManager.ContentProviderHolder; +import android.content.ContentResolver; import android.content.ContentValues; import android.content.IContentProvider; import android.database.Cursor; @@ -72,59 +73,64 @@ import libcore.io.IoUtils; public class Content { private static final String USAGE = - "usage: adb shell content [subcommand] [options]\n" - + "\n" - + "usage: adb shell content insert --uri <URI> [--user <USER_ID>]" - + " --bind <BINDING> [--bind <BINDING>...]\n" - + " <URI> a content provider URI.\n" - + " <BINDING> binds a typed value to a column and is formatted:\n" - + " <COLUMN_NAME>:<TYPE>:<COLUMN_VALUE> where:\n" - + " <TYPE> specifies data type such as:\n" - + " b - boolean, s - string, i - integer, l - long, f - float, d - double\n" - + " Note: Omit the value for passing an empty string, e.g column:s:\n" - + " Example:\n" - + " # Add \"new_setting\" secure setting with value \"new_value\".\n" - + " adb shell content insert --uri content://settings/secure --bind name:s:new_setting" - + " --bind value:s:new_value\n" - + "\n" - + "usage: adb shell content update --uri <URI> [--user <USER_ID>] [--where <WHERE>]\n" - + " <WHERE> is a SQL style where clause in quotes (You have to escape single quotes" - + " - see example below).\n" - + " Example:\n" - + " # Change \"new_setting\" secure setting to \"newer_value\".\n" - + " adb shell content update --uri content://settings/secure --bind" - + " value:s:newer_value --where \"name=\'new_setting\'\"\n" - + "\n" - + "usage: adb shell content delete --uri <URI> [--user <USER_ID>] --bind <BINDING>" - + " [--bind <BINDING>...] [--where <WHERE>]\n" - + " Example:\n" - + " # Remove \"new_setting\" secure setting.\n" - + " adb shell content delete --uri content://settings/secure " - + "--where \"name=\'new_setting\'\"\n" - + "\n" - + "usage: adb shell content query --uri <URI> [--user <USER_ID>]" - + " [--projection <PROJECTION>] [--where <WHERE>] [--sort <SORT_ORDER>]\n" - + " <PROJECTION> is a list of colon separated column names and is formatted:\n" - + " <COLUMN_NAME>[:<COLUMN_NAME>...]\n" - + " <SORT_ORDER> is the order in which rows in the result should be sorted.\n" - + " Example:\n" - + " # Select \"name\" and \"value\" columns from secure settings where \"name\" is " - + "equal to \"new_setting\" and sort the result by name in ascending order.\n" - + " adb shell content query --uri content://settings/secure --projection name:value" - + " --where \"name=\'new_setting\'\" --sort \"name ASC\"\n" - + "\n" - + "usage: adb shell content call --uri <URI> --method <METHOD> [--arg <ARG>]\n" - + " [--extra <BINDING> ...]\n" - + " <METHOD> is the name of a provider-defined method\n" - + " <ARG> is an optional string argument\n" - + " <BINDING> is like --bind above, typed data of the form <KEY>:{b,s,i,l,f,d}:<VAL>\n" - + "\n" - + "usage: adb shell content read --uri <URI> [--user <USER_ID>]\n" - + " Example:\n" - + " # cat default ringtone to a file, then pull to host\n" - + " adb shell 'content read --uri content://settings/system/ringtone >" - + " /mnt/sdcard/tmp.ogg' && adb pull /mnt/sdcard/tmp.ogg\n" - + "\n"; + "usage: adb shell content [subcommand] [options]\n" + + "\n" + + "usage: adb shell content insert --uri <URI> [--user <USER_ID>]" + + " --bind <BINDING> [--bind <BINDING>...]\n" + + " <URI> a content provider URI.\n" + + " <BINDING> binds a typed value to a column and is formatted:\n" + + " <COLUMN_NAME>:<TYPE>:<COLUMN_VALUE> where:\n" + + " <TYPE> specifies data type such as:\n" + + " b - boolean, s - string, i - integer, l - long, f - float, d - double\n" + + " Note: Omit the value for passing an empty string, e.g column:s:\n" + + " Example:\n" + + " # Add \"new_setting\" secure setting with value \"new_value\".\n" + + " adb shell content insert --uri content://settings/secure --bind name:s:new_setting" + + " --bind value:s:new_value\n" + + "\n" + + "usage: adb shell content update --uri <URI> [--user <USER_ID>] [--where <WHERE>]\n" + + " <WHERE> is a SQL style where clause in quotes (You have to escape single quotes" + + " - see example below).\n" + + " Example:\n" + + " # Change \"new_setting\" secure setting to \"newer_value\".\n" + + " adb shell content update --uri content://settings/secure --bind" + + " value:s:newer_value --where \"name=\'new_setting\'\"\n" + + "\n" + + "usage: adb shell content delete --uri <URI> [--user <USER_ID>] --bind <BINDING>" + + " [--bind <BINDING>...] [--where <WHERE>]\n" + + " Example:\n" + + " # Remove \"new_setting\" secure setting.\n" + + " adb shell content delete --uri content://settings/secure " + + "--where \"name=\'new_setting\'\"\n" + + "\n" + + "usage: adb shell content query --uri <URI> [--user <USER_ID>]" + + " [--projection <PROJECTION>] [--where <WHERE>] [--sort <SORT_ORDER>]\n" + + " <PROJECTION> is a list of colon separated column names and is formatted:\n" + + " <COLUMN_NAME>[:<COLUMN_NAME>...]\n" + + " <SORT_ORDER> is the order in which rows in the result should be sorted.\n" + + " Example:\n" + + " # Select \"name\" and \"value\" columns from secure settings where \"name\" is " + + "equal to \"new_setting\" and sort the result by name in ascending order.\n" + + " adb shell content query --uri content://settings/secure --projection name:value" + + " --where \"name=\'new_setting\'\" --sort \"name ASC\"\n" + + "\n" + + "usage: adb shell content call --uri <URI> --method <METHOD> [--arg <ARG>]\n" + + " [--extra <BINDING> ...]\n" + + " <METHOD> is the name of a provider-defined method\n" + + " <ARG> is an optional string argument\n" + + " <BINDING> is like --bind above, typed data of the form <KEY>:{b,s,i,l,f,d}:<VAL>\n" + + "\n" + + "usage: adb shell content read --uri <URI> [--user <USER_ID>]\n" + + " Example:\n" + + " # cat default ringtone to a file, then pull to host\n" + + " adb shell 'content read --uri content://settings/system/ringtone >" + + " /mnt/sdcard/tmp.ogg' && adb pull /mnt/sdcard/tmp.ogg\n" + + "\n" + + "usage: adb shell content gettype --uri <URI> [--user <USER_ID>]\n" + + " Example:\n" + + " # Show the mime-type of the URI\n" + + " adb shell content gettype --uri content://media/internal/audio/media/\n" + + "\n"; private static class Parser { private static final String ARGUMENT_INSERT = "insert"; @@ -133,6 +139,7 @@ public class Content { private static final String ARGUMENT_QUERY = "query"; private static final String ARGUMENT_CALL = "call"; private static final String ARGUMENT_READ = "read"; + private static final String ARGUMENT_GET_TYPE = "gettype"; private static final String ARGUMENT_WHERE = "--where"; private static final String ARGUMENT_BIND = "--bind"; private static final String ARGUMENT_URI = "--uri"; @@ -172,6 +179,8 @@ public class Content { return parseCallCommand(); } else if (ARGUMENT_READ.equals(operation)) { return parseReadCommand(); + } else if (ARGUMENT_GET_TYPE.equals(operation)) { + return parseGetTypeCommand(); } else { throw new IllegalArgumentException("Unsupported operation: " + operation); } @@ -291,6 +300,26 @@ public class Content { return new CallCommand(uri, userId, method, arg, values); } + private GetTypeCommand parseGetTypeCommand() { + Uri uri = null; + int userId = UserHandle.USER_SYSTEM; + + for (String argument; (argument = mTokenizer.nextArg()) != null;) { + if (ARGUMENT_URI.equals(argument)) { + uri = Uri.parse(argumentValueRequired(argument)); + } else if (ARGUMENT_USER.equals(argument)) { + userId = Integer.parseInt(argumentValueRequired(argument)); + } else { + throw new IllegalArgumentException("Unsupported argument: " + argument); + } + } + if (uri == null) { + throw new IllegalArgumentException("Content provider URI not specified." + + " Did you specify --uri argument?"); + } + return new GetTypeCommand(uri, userId); + } + private ReadCommand parseReadCommand() { Uri uri = null; int userId = UserHandle.USER_SYSTEM; @@ -405,7 +434,7 @@ public class Content { public final void execute() { String providerName = mUri.getAuthority(); try { - IActivityManager activityManager = ActivityManagerNative.getDefault(); + IActivityManager activityManager = ActivityManager.getService(); IContentProvider provider = null; IBinder token = new Binder(); try { @@ -511,6 +540,18 @@ public class Content { } } + private static class GetTypeCommand extends Command { + public GetTypeCommand(Uri uri, int userId) { + super(uri, userId); + } + + @Override + public void onExecute(IContentProvider provider) throws Exception { + String type = provider.getType(mUri); + System.out.println("Result: " + type); + } + } + private static class ReadCommand extends Command { public ReadCommand(Uri uri, int userId) { super(uri, userId); @@ -549,8 +590,8 @@ public class Content { @Override public void onExecute(IContentProvider provider) throws Exception { - Cursor cursor = provider.query(resolveCallingPackage(), mUri, mProjection, mWhere, - null, mSortOrder, null); + Cursor cursor = provider.query(resolveCallingPackage(), mUri, mProjection, + ContentResolver.createSqlQueryBundle(mWhere, null, mSortOrder), null); if (cursor == null) { System.out.println("No result found."); return; diff --git a/cmds/dpm/src/com/android/commands/dpm/Dpm.java b/cmds/dpm/src/com/android/commands/dpm/Dpm.java index 31c742153f24..3ac70d668198 100644 --- a/cmds/dpm/src/com/android/commands/dpm/Dpm.java +++ b/cmds/dpm/src/com/android/commands/dpm/Dpm.java @@ -16,7 +16,7 @@ package com.android.commands.dpm; -import android.app.ActivityManagerNative; +import android.app.ActivityManager; import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; @@ -117,7 +117,7 @@ public final class Dpm extends BaseCommand { mUserId = parseInt(arg); } if (mUserId == UserHandle.USER_CURRENT) { - IActivityManager activityManager = ActivityManagerNative.getDefault(); + IActivityManager activityManager = ActivityManager.getService(); try { mUserId = activityManager.getCurrentUser().id; } catch (RemoteException e) { diff --git a/cmds/idmap/Android.mk b/cmds/idmap/Android.mk index 50ccb07a3826..aeb8a0c001ec 100644 --- a/cmds/idmap/Android.mk +++ b/cmds/idmap/Android.mk @@ -17,7 +17,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := idmap.cpp create.cpp scan.cpp inspect.cpp -LOCAL_SHARED_LIBRARIES := liblog libutils libandroidfw +LOCAL_SHARED_LIBRARIES := liblog libutils libandroidfw libcutils LOCAL_MODULE := idmap diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp index c13d318f7449..524db14f7aab 100644 --- a/cmds/idmap/create.cpp +++ b/cmds/idmap/create.cpp @@ -221,3 +221,9 @@ int idmap_create_fd(const char *target_apk_path, const char *overlay_apk_path, i return create_and_write_idmap(target_apk_path, overlay_apk_path, fd, true) == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } + +int idmap_verify_fd(const char *target_apk_path, const char *overlay_apk_path, int fd) +{ + return !is_idmap_stale_fd(target_apk_path, overlay_apk_path, fd) ? + EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/cmds/idmap/idmap.cpp b/cmds/idmap/idmap.cpp index 3ab191553625..8f86ed8f7d32 100644 --- a/cmds/idmap/idmap.cpp +++ b/cmds/idmap/idmap.cpp @@ -16,6 +16,7 @@ SYNOPSIS \n\ idmap --scan target-package-name-to-look-for path-to-target-apk dir-to-hold-idmaps \\\ dir-to-scan [additional-dir-to-scan [additional-dir-to-scan [...]]]\n\ idmap --inspect idmap \n\ + idmap --verify target overlay fd \n\ \n\ DESCRIPTION \n\ Idmap files play an integral part in the runtime resource overlay framework. An idmap \n\ @@ -49,14 +50,17 @@ OPTIONS \n\ --path: create idmap for target package 'target' (path to apk) and overlay package \n\ 'overlay' (path to apk); write results to 'idmap' (path). \n\ \n\ - --scan: non-recursively search directory 'dir-to-scan' (path) for overlay packages with \n\ - target package 'target-package-name-to-look-for' (package name) present at\n\ + --scan: non-recursively search directory 'dir-to-scan' (path) for static overlay packages \n\ + with target package 'target-package-name-to-look-for' (package name) present at\n\ 'path-to-target-apk' (path to apk). For each overlay package found, create an\n\ idmap file in 'dir-to-hold-idmaps' (path). \n\ \n\ --inspect: decode the binary format of 'idmap' (path) and display the contents in a \n\ debug-friendly format. \n\ \n\ + --verify: verify if idmap corresponding to file descriptor 'fd' (integer) is made from \n\ + target package 'target' (path to apk) and overlay package 'overlay'. \n\ +\n\ EXAMPLES \n\ Create an idmap file: \n\ \n\ @@ -167,6 +171,29 @@ NOTES \n\ return idmap_create_path(target_apk_path, overlay_apk_path, idmap_path); } + int maybe_verify_fd(const char *target_apk_path, const char *overlay_apk_path, + const char *idmap_str) + { + char *endptr; + int idmap_fd = strtol(idmap_str, &endptr, 10); + if (*endptr != '\0') { + fprintf(stderr, "error: failed to parse file descriptor argument %s\n", idmap_str); + return -1; + } + + if (!verify_file_readable(target_apk_path)) { + ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno)); + return -1; + } + + if (!verify_file_readable(overlay_apk_path)) { + ALOGD("error: failed to read apk %s: %s\n", overlay_apk_path, strerror(errno)); + return -1; + } + + return idmap_verify_fd(target_apk_path, overlay_apk_path, idmap_fd); + } + int maybe_scan(const char *target_package_name, const char *target_apk_path, const char *idmap_dir, const android::Vector<const char *> *overlay_dirs) { @@ -235,6 +262,10 @@ int main(int argc, char **argv) return maybe_create_path(argv[2], argv[3], argv[4]); } + if (argc == 5 && !strcmp(argv[1], "--verify")) { + return maybe_verify_fd(argv[2], argv[3], argv[4]); + } + if (argc >= 6 && !strcmp(argv[1], "--scan")) { android::Vector<const char *> v; for (int i = 5; i < argc; i++) { diff --git a/cmds/idmap/idmap.h b/cmds/idmap/idmap.h index 8d4210bcb443..5962108c9f7e 100644 --- a/cmds/idmap/idmap.h +++ b/cmds/idmap/idmap.h @@ -25,6 +25,8 @@ int idmap_create_path(const char *target_apk_path, const char *overlay_apk_path, int idmap_create_fd(const char *target_apk_path, const char *overlay_apk_path, int fd); +int idmap_verify_fd(const char *target_apk_path, const char *overlay_apk_path, int fd); + // Regarding target_package_name: the idmap_scan implementation should // be able to extract this from the manifest in target_apk_path, // simplifying the external API. diff --git a/cmds/idmap/inspect.cpp b/cmds/idmap/inspect.cpp index f6afc8594309..154cb25a02a1 100644 --- a/cmds/idmap/inspect.cpp +++ b/cmds/idmap/inspect.cpp @@ -2,6 +2,7 @@ #include <androidfw/AssetManager.h> #include <androidfw/ResourceTypes.h> +#include <utils/ByteOrder.h> #include <utils/String8.h> #include <fcntl.h> diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp index ab6adfb9475f..d69dd79555a1 100644 --- a/cmds/idmap/scan.cpp +++ b/cmds/idmap/scan.cpp @@ -9,6 +9,8 @@ #include <androidfw/ResourceTypes.h> #include <androidfw/StreamingZipInflater.h> #include <androidfw/ZipFileRO.h> +#include <cutils/jstring.h> +#include <cutils/properties.h> #include <private/android_filesystem_config.h> // for AID_SYSTEM #include <utils/SortedVector.h> #include <utils/String16.h> @@ -81,11 +83,26 @@ namespace { return String8(tmp); } - int parse_overlay_tag(const ResXMLTree& parser, const char *target_package_name) + bool check_property(String16 property, String16 value) { + const char *prop; + const char *val; + + prop = strndup16to8(property.string(), property.size()); + char propBuf[PROPERTY_VALUE_MAX]; + property_get(prop, propBuf, NULL); + val = strndup16to8(value.string(), value.size()); + + return (strcmp(propBuf, val) == 0); + } + + int parse_overlay_tag(const ResXMLTree& parser, const char *target_package_name, + bool* is_static_overlay) { const size_t N = parser.getAttributeCount(); String16 target; int priority = -1; + String16 propName = String16(); + String16 propValue = String16(); for (size_t i = 0; i < N; ++i) { size_t len; String16 key(parser.getAttributeName(i, &len)); @@ -102,8 +119,33 @@ namespace { return -1; } } + } else if (key == String16("isStatic")) { + Res_value v; + if (parser.getAttributeValue(i, &v) == sizeof(Res_value)) { + *is_static_overlay = (v.data != 0); + } + } else if (key == String16("requiredSystemPropertyName")) { + const char16_t *p = parser.getAttributeStringValue(i, &len); + if (p != NULL) { + propName = String16(p, len); + } + } else if (key == String16("requiredSystemPropertyValue")) { + const char16_t *p = parser.getAttributeStringValue(i, &len); + if (p != NULL) { + propValue = String16(p, len); + } + } + } + + // Note that conditional property enablement/exclusion only applies if + // the attribute is present. In its absence, all overlays are presumed enabled. + if (propName.size() > 0 && propValue.size() > 0) { + // if property set & equal to value, then include overlay - otherwise skip + if (!check_property(propName, propValue)) { + return NO_OVERLAY_TAG; } } + if (target == String16(target_package_name)) { return priority; } @@ -120,17 +162,23 @@ namespace { } ResXMLParser::event_code_t type; + bool is_static_overlay = false; + int priority = NO_OVERLAY_TAG; do { type = parser.next(); if (type == ResXMLParser::START_TAG) { size_t len; String16 tag(parser.getElementName(&len)); if (tag == String16("overlay")) { - return parse_overlay_tag(parser, target_package_name); + priority = parse_overlay_tag(parser, target_package_name, &is_static_overlay); + break; } } } while (type != ResXMLParser::BAD_DOCUMENT && type != ResXMLParser::END_DOCUMENT); + if (is_static_overlay) { + return priority; + } return NO_OVERLAY_TAG; } @@ -237,3 +285,4 @@ int idmap_scan(const char *target_package_name, const char *target_apk_path, return EXIT_SUCCESS; } + diff --git a/cmds/incident/Android.mk b/cmds/incident/Android.mk new file mode 100644 index 000000000000..e1c9b93a8e6d --- /dev/null +++ b/cmds/incident/Android.mk @@ -0,0 +1,48 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + main.cpp + +LOCAL_MODULE := incident + +LOCAL_SHARED_LIBRARIES := \ + libbase \ + libbinder \ + libcutils \ + liblog \ + libutils \ + libincident + +LOCAL_CFLAGS += \ + -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter + +LOCAL_MODULE_CLASS := EXECUTABLES +gen_src_dir := $(local-generated-sources-dir) + +gen := $(gen_src_dir)/incident_sections.cpp +$(gen): $(HOST_OUT_EXECUTABLES)/incident-section-gen +$(gen): PRIVATE_CUSTOM_TOOL = \ + $(HOST_OUT_EXECUTABLES)/incident-section-gen > $@ +$(gen): $(HOST_OUT_EXECUTABLES)/incident-section-gen + $(transform-generated-source) +LOCAL_GENERATED_SOURCES += $(gen) + +gen_src_dir:= +gen:= + +include $(BUILD_EXECUTABLE) diff --git a/cmds/incident/incident_sections.h b/cmds/incident/incident_sections.h new file mode 100644 index 000000000000..1972088fc82c --- /dev/null +++ b/cmds/incident/incident_sections.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCIDENT_SECTIONS_H +#define INCIDENT_SECTIONS_H + +struct IncidentSection +{ + int id; + char const* name; +}; + +extern IncidentSection const INCIDENT_SECTIONS[]; +extern const int INCIDENT_SECTION_COUNT; + +#endif // INCIDENT_SECTIONS_H diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp new file mode 100644 index 000000000000..91b7c22b2038 --- /dev/null +++ b/cmds/incident/main.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "incident" + +#include "incident_sections.h" + +#include <android/os/BnIncidentReportStatusListener.h> +#include <android/os/IIncidentManager.h> +#include <android/os/IncidentReportArgs.h> +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <utils/Looper.h> + +#include <fcntl.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +using namespace android; +using namespace android::base; +using namespace android::binder; +using namespace android::os; + +// ================================================================================ +class StatusListener : public BnIncidentReportStatusListener { +public: + StatusListener(); + virtual ~StatusListener(); + + virtual Status onReportStarted(); + virtual Status onReportSectionStatus(int32_t section, int32_t status); + virtual Status onReportServiceStatus(const String16& service, int32_t status); + virtual Status onReportFinished(); + virtual Status onReportFailed(); +}; + +StatusListener::StatusListener() +{ +} + +StatusListener::~StatusListener() +{ +} + +Status +StatusListener::onReportStarted() +{ + return Status::ok(); +} + +Status +StatusListener::onReportSectionStatus(int32_t section, int32_t status) +{ + fprintf(stderr, "section %d status %d\n", section, status); + return Status::ok(); +} + +Status +StatusListener::onReportServiceStatus(const String16& service, int32_t status) +{ + fprintf(stderr, "service '%s' status %d\n", String8(service).string(), status); + return Status::ok(); +} + +Status +StatusListener::onReportFinished() +{ + fprintf(stderr, "done\n"); + exit(0); + return Status::ok(); +} + +Status +StatusListener::onReportFailed() +{ + fprintf(stderr, "failed\n"); + exit(1); + return Status::ok(); +} + +// ================================================================================ +static IncidentSection const* +find_section(const char* name) +{ + size_t low = 0; + size_t high = INCIDENT_SECTION_COUNT - 1; + + while (low <= high) { + size_t mid = (low + high) >> 1; + IncidentSection const* section = INCIDENT_SECTIONS + mid; + + int cmp = strcmp(section->name, name); + if (cmp < 0) { + low = mid + 1; + } else if (cmp > 0) { + high = mid - 1; + } else { + return section; + } + } + return NULL; +} + +// ================================================================================ +static void +usage(FILE* out) +{ + fprintf(out, "usage: incident OPTIONS [SECTION...]\n"); + fprintf(out, "\n"); + fprintf(out, "Takes an incident report.\n"); + fprintf(out, "\n"); + fprintf(out, "OPTIONS\n"); + fprintf(out, " -b (default) print the report to stdout (in proto format)\n"); + fprintf(out, " -d send the report into dropbox\n"); + fprintf(out, "\n"); + fprintf(out, " SECTION the field numbers of the incident report fields to include\n"); + fprintf(out, "\n"); +} + +int +main(int argc, char** argv) +{ + Status status; + IncidentReportArgs args; + enum { DEST_DROPBOX, DEST_STDOUT } destination = DEST_STDOUT; + + // Parse the args + int opt; + while ((opt = getopt(argc, argv, "bhd")) != -1) { + switch (opt) { + case 'b': + destination = DEST_STDOUT; + break; + case 'h': + usage(stdout); + return 0; + case 'd': + destination = DEST_DROPBOX; + break; + default: + usage(stderr); + return 1; + } + } + + if (optind == argc) { + args.setAll(true); + } else { + for (int i=optind; i<argc; i++) { + const char* arg = argv[i]; + char* end; + if (arg[0] != '\0') { + int section = strtol(arg, &end, 0); + if (*end == '\0') { + args.addSection(section); + } else { + IncidentSection const* ic = find_section(arg); + if (ic == NULL) { + fprintf(stderr, "Invalid section: %s\n", arg); + return 1; + } + args.addSection(ic->id); + } + } + } + } + + + + // Start the thread pool. + sp<ProcessState> ps(ProcessState::self()); + ps->startThreadPool(); + ps->giveThreadPoolName(); + + // Look up the service + sp<IIncidentManager> service = interface_cast<IIncidentManager>( + defaultServiceManager()->getService(android::String16("incident"))); + if (service == NULL) { + fprintf(stderr, "Couldn't look up the incident service\n"); + return 1; + } + + // Construct the stream + int fds[2]; + pipe(fds); + + unique_fd readEnd(fds[0]); + unique_fd writeEnd(fds[1]); + + if (destination == DEST_STDOUT) { + // Call into the service + sp<StatusListener> listener(new StatusListener()); + status = service->reportIncidentToStream(args, listener, writeEnd); + + if (!status.isOk()) { + fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string()); + } + + // Wait for the result and print out the data they send. + //IPCThreadState::self()->joinThreadPool(); + + while (true) { + int amt = splice(fds[0], NULL, STDOUT_FILENO, NULL, 4096, 0); + fprintf(stderr, "spliced %d bytes\n", amt); + if (amt < 0) { + return errno; + } else if (amt == 0) { + return 0; + } + } + } else { + status = service->reportIncident(args); + if (!status.isOk()) { + fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string()); + return 1; + } else { + return 0; + } + } + +} diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk new file mode 100644 index 000000000000..bacf672e81b5 --- /dev/null +++ b/cmds/incidentd/Android.mk @@ -0,0 +1,56 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := incidentd + +LOCAL_SRC_FILES := \ + src/FdBuffer.cpp \ + src/IncidentService.cpp \ + src/Reporter.cpp \ + src/Section.cpp \ + src/main.cpp \ + src/protobuf.cpp \ + src/report_directory.cpp \ + src/section_list.cpp + +LOCAL_CFLAGS += \ + -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter + +ifeq (debug,) + LOCAL_CFLAGS += \ + -g -O0 +else + # optimize for size (protobuf glop can get big) + LOCAL_CFLAGS += \ + -Os +endif + +LOCAL_SHARED_LIBRARIES := \ + libbase \ + libbinder \ + libcutils \ + libincident \ + liblog \ + libselinux \ + libservices \ + libutils + +ifeq (BUILD_WITH_INCIDENTD_RC,true) +LOCAL_INIT_RC := incidentd.rc +endif + +include $(BUILD_EXECUTABLE) diff --git a/cmds/incidentd/incidentd.rc b/cmds/incidentd/incidentd.rc new file mode 100644 index 000000000000..d11e3cf70567 --- /dev/null +++ b/cmds/incidentd/incidentd.rc @@ -0,0 +1,16 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#service incidentd /system/bin/incidentd +# class main diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp new file mode 100644 index 000000000000..527d7eef3a96 --- /dev/null +++ b/cmds/incidentd/src/FdBuffer.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "incidentd" + +#include "FdBuffer.h" + +#include <cutils/log.h> +#include <utils/SystemClock.h> + +#include <fcntl.h> +#include <poll.h> +#include <unistd.h> + +const ssize_t BUFFER_SIZE = 16 * 1024; +const ssize_t MAX_BUFFER_COUNT = 256; // 4 MB max + + +FdBuffer::FdBuffer() + :mBuffers(), + mStartTime(-1), + mFinishTime(-1), + mCurrentWritten(-1), + mTimedOut(false), + mTruncated(false) +{ +} + +FdBuffer::~FdBuffer() +{ + const int N = mBuffers.size(); + for (int i=0; i<N; i++) { + uint8_t* buf = mBuffers[i]; + free(buf); + } +} + +status_t +FdBuffer::read(int fd, int64_t timeout) +{ + struct pollfd pfds = { + .fd = fd, + .events = POLLIN + }; + mStartTime = uptimeMillis(); + + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + + uint8_t* buf = NULL; + while (true) { + if (mCurrentWritten >= BUFFER_SIZE || mCurrentWritten < 0) { + if (mBuffers.size() == MAX_BUFFER_COUNT) { + mTruncated = true; + break; + } + buf = (uint8_t*)malloc(BUFFER_SIZE); + if (buf == NULL) { + return NO_MEMORY; + } + mBuffers.push_back(buf); + mCurrentWritten = 0; + } + + int64_t remainingTime = (mStartTime + timeout) - uptimeMillis(); + if (remainingTime <= 0) { + mTimedOut = true; + break; + } + + int count = poll(&pfds, 1, remainingTime); + if (count == 0) { + mTimedOut = true; + break; + } else if (count < 0) { + return -errno; + } else { + if ((pfds.revents & POLLERR) != 0) { + return errno != 0 ? -errno : UNKNOWN_ERROR; + } else { + ssize_t amt = ::read(fd, buf + mCurrentWritten, BUFFER_SIZE - mCurrentWritten); + if (amt < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + continue; + } else { + return -errno; + } + } else if (amt == 0) { + break; + } + mCurrentWritten += amt; + } + } + } + + mFinishTime = uptimeMillis(); + return NO_ERROR; +} + +size_t +FdBuffer::size() +{ + return ((mBuffers.size() - 1) * BUFFER_SIZE) + mCurrentWritten; +} + +status_t +FdBuffer::write(ReportRequestSet* reporter) +{ + const int N = mBuffers.size() - 1; + for (int i=0; i<N; i++) { + reporter->write(mBuffers[i], BUFFER_SIZE); + } + reporter->write(mBuffers[N], mCurrentWritten); + return NO_ERROR; +} + + diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h new file mode 100644 index 000000000000..e12374f21558 --- /dev/null +++ b/cmds/incidentd/src/FdBuffer.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FD_BUFFER_H +#define FD_BUFFER_H + +#include "Reporter.h" + +#include <utils/Errors.h> + +#include <set> +#include <vector> + +using namespace android; +using namespace std; + +/** + * Reads a file into a buffer, and then writes that data to an FdSet. + */ +class FdBuffer +{ +public: + FdBuffer(); + ~FdBuffer(); + + /** + * Read the data until the timeout is hit or we hit eof. + * Returns NO_ERROR if there were no errors or if we timed out. + * Will mark the file O_NONBLOCK. + */ + status_t read(int fd, int64_t timeoutMs); + + /** + * Whether we timed out. + */ + bool timedOut() { return mTimedOut; } + + /** + * If more than 4 MB is read, we truncate the data and return success. + * Downstream tools must handle truncated incident reports as best as possible + * anyway because they could be cut off for a lot of reasons and it's best + * to get as much useful information out of the system as possible. If this + * happens, truncated() will return true so it can be marked. If the data is + * exactly 4 MB, truncated is still set. Sorry. + */ + bool truncated() { return mTruncated; } + + /** + * How much data was read. + */ + size_t size(); + + /** + * Write the data that we recorded to the fd given. + */ + status_t write(ReportRequestSet* requests); + + /** + * How long the read took in milliseconds. + */ + int64_t durationMs() { return mFinishTime - mStartTime; } + +private: + vector<uint8_t*> mBuffers; + int64_t mStartTime; + int64_t mFinishTime; + ssize_t mCurrentWritten; + bool mTimedOut; + bool mTruncated; +}; + + +#endif // FD_BUFFER_H diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp new file mode 100644 index 000000000000..7c6789e6e5ba --- /dev/null +++ b/cmds/incidentd/src/IncidentService.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "incidentd" + +#include "IncidentService.h" + +#include "Reporter.h" + +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <cutils/log.h> +#include <private/android_filesystem_config.h> +#include <utils/Looper.h> + +#include <unistd.h> + +using namespace android; + +enum { + WHAT_RUN_REPORT = 1, + WHAT_SEND_BACKLOG_TO_DROPBOX = 2 +}; + +//#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL * 60 * 5) +#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL) + +// ================================================================================ +String16 const DUMP_PERMISSION("android.permission.DUMP"); +String16 const USAGE_STATS_PERMISSION("android.permission.PACKAGE_USAGE_STATS"); + +static Status +checkIncidentPermissions() +{ + if (!checkCallingPermission(DUMP_PERMISSION)) { + ALOGW("Calling pid %d and uid %d does not have permission: android.permission.DUMP", + IPCThreadState::self()->getCallingPid(), IPCThreadState::self()->getCallingUid()); + return Status::fromExceptionCode(Status::EX_SECURITY, + "Calling process does not have permission: android.permission.DUMP"); + } + if (!checkCallingPermission(USAGE_STATS_PERMISSION)) { + ALOGW("Calling pid %d and uid %d does not have permission: android.permission.USAGE_STATS", + IPCThreadState::self()->getCallingPid(), IPCThreadState::self()->getCallingUid()); + return Status::fromExceptionCode(Status::EX_SECURITY, + "Calling process does not have permission: android.permission.USAGE_STATS"); + } + return Status::ok(); +} + + +// ================================================================================ +ReportRequestQueue::ReportRequestQueue() +{ +} + +ReportRequestQueue::~ReportRequestQueue() +{ +} + +void +ReportRequestQueue::addRequest(const sp<ReportRequest>& request) +{ + unique_lock<mutex> lock(mLock); + mQueue.push_back(request); +} + +sp<ReportRequest> +ReportRequestQueue::getNextRequest() +{ + unique_lock<mutex> lock(mLock); + if (mQueue.empty()) { + return NULL; + } else { + sp<ReportRequest> front(mQueue.front()); + mQueue.pop_front(); + return front; + } +} + + +// ================================================================================ +ReportHandler::ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue) + :mBacklogDelay(DEFAULT_BACKLOG_DELAY_NS), + mHandlerLooper(handlerLooper), + mQueue(queue) +{ +} + +ReportHandler::~ReportHandler() +{ +} + +void +ReportHandler::handleMessage(const Message& message) +{ + switch (message.what) { + case WHAT_RUN_REPORT: + run_report(); + break; + case WHAT_SEND_BACKLOG_TO_DROPBOX: + send_backlog_to_dropbox(); + break; + } +} + +void +ReportHandler::scheduleRunReport(const sp<ReportRequest>& request) +{ + mQueue->addRequest(request); + mHandlerLooper->removeMessages(this, WHAT_RUN_REPORT); + mHandlerLooper->sendMessage(this, Message(WHAT_RUN_REPORT)); +} + +void +ReportHandler::scheduleSendBacklogToDropbox() +{ + unique_lock<mutex> lock(mLock); + mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS; + schedule_send_backlog_to_dropbox_locked(); +} + +void +ReportHandler::schedule_send_backlog_to_dropbox_locked() +{ + mHandlerLooper->removeMessages(this, WHAT_SEND_BACKLOG_TO_DROPBOX); + mHandlerLooper->sendMessageDelayed(mBacklogDelay, this, + Message(WHAT_SEND_BACKLOG_TO_DROPBOX)); +} + +void +ReportHandler::run_report() +{ + sp<Reporter> reporter = new Reporter(); + + // Merge all of the requests into one that has all of the + // requested fields. + while (true) { + sp<ReportRequest> request = mQueue->getNextRequest(); + if (request == NULL) { + break; + } + reporter->batch.add(request); + reporter->args.merge(request->args); + } + + // Take the report, which might take a while. More requests might queue + // up while we're doing this, and we'll handle them in their next batch. + // TODO: We should further rate-limit the reports to no more than N per time-period. + Reporter::run_report_status_t reportStatus = reporter->runReport(); + if (reportStatus == Reporter::REPORT_NEEDS_DROPBOX) { + unique_lock<mutex> lock(mLock); + schedule_send_backlog_to_dropbox_locked(); + } +} + +void +ReportHandler::send_backlog_to_dropbox() +{ + if (Reporter::upload_backlog() == Reporter::REPORT_NEEDS_DROPBOX) { + // There was a failure. Exponential backoff. + unique_lock<mutex> lock(mLock); + mBacklogDelay *= 2; + ALOGI("Error sending to dropbox. Trying again in %lld minutes", + (mBacklogDelay / (1000000000LL * 60))); + schedule_send_backlog_to_dropbox_locked(); + } else { + mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS; + } +} + +// ================================================================================ +IncidentService::IncidentService(const sp<Looper>& handlerLooper) + :mQueue(new ReportRequestQueue()) +{ + mHandler = new ReportHandler(handlerLooper, mQueue); +} + +IncidentService::~IncidentService() +{ +} + +Status +IncidentService::reportIncident(const IncidentReportArgs& args) +{ + ALOGI("reportIncident"); + + Status status = checkIncidentPermissions(); + if (!status.isOk()) { + return status; + } + + mHandler->scheduleRunReport(new ReportRequest(args, NULL, -1)); + + return Status::ok(); +} + +Status +IncidentService::reportIncidentToStream(const IncidentReportArgs& args, + const sp<IIncidentReportStatusListener>& listener, const unique_fd& stream) +{ + ALOGI("reportIncidentToStream"); + + Status status = checkIncidentPermissions(); + if (!status.isOk()) { + return status; + } + + int fd = dup(stream.get()); + if (fd < 0) { + return Status::fromStatusT(-errno); + } + + mHandler->scheduleRunReport(new ReportRequest(args, listener, fd)); + + return Status::ok(); +} + +Status +IncidentService::systemRunning() +{ + if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) { + return Status::fromExceptionCode(Status::EX_SECURITY, + "Only system uid can call systemRunning"); + } + + // When system_server is up and running, schedule the dropbox task to run. + mHandler->scheduleSendBacklogToDropbox(); + + return Status::ok(); +} + diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h new file mode 100644 index 000000000000..d6f33dfb1a86 --- /dev/null +++ b/cmds/incidentd/src/IncidentService.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCIDENT_SERVICE_H +#define INCIDENT_SERVICE_H + +#include "Reporter.h" + +#include <android/os/BnIncidentManager.h> +#include <utils/Looper.h> + +#include <deque> +#include <mutex> + +using namespace android; +using namespace android::base; +using namespace android::binder; +using namespace android::os; +using namespace std; + +// ================================================================================ +class ReportRequestQueue : public virtual RefBase +{ +public: + ReportRequestQueue(); + virtual ~ReportRequestQueue(); + + void addRequest(const sp<ReportRequest>& request); + sp<ReportRequest> getNextRequest(); + +private: + mutex mLock; + deque<sp<ReportRequest> > mQueue; +}; + + +// ================================================================================ +class ReportHandler : public MessageHandler +{ +public: + ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue); + virtual ~ReportHandler(); + + virtual void handleMessage(const Message& message); + + /** + * Adds a ReportRequest to the queue. + */ + void scheduleRunReport(const sp<ReportRequest>& request); + + /** + * Resets mBacklogDelay to the default and schedules sending + * the messages to dropbox. + */ + void scheduleSendBacklogToDropbox(); + +private: + mutex mLock; + nsecs_t mBacklogDelay; + sp<Looper> mHandlerLooper; + sp<ReportRequestQueue> mQueue; + + /** + * Runs all of the reports that have been queued. + */ + void run_report(); + + /** + * Schedules a dropbox task mBacklogDelay nanoseconds from now. + */ + void schedule_send_backlog_to_dropbox_locked(); + + /** + * Sends the backlog to the dropbox service. + */ + void send_backlog_to_dropbox(); +}; + + +// ================================================================================ +class IncidentService : public BnIncidentManager { +public: + IncidentService(const sp<Looper>& handlerLooper); + virtual ~IncidentService(); + + virtual Status reportIncident(const IncidentReportArgs& args); + + virtual Status reportIncidentToStream(const IncidentReportArgs& args, + const sp<IIncidentReportStatusListener>& listener, const unique_fd& stream); + + virtual Status systemRunning(); + +private: + sp<ReportRequestQueue> mQueue; + sp<ReportHandler> mHandler; +}; + + +#endif // INCIDENT_SERVICE_H diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp new file mode 100644 index 000000000000..1ecb291c84a1 --- /dev/null +++ b/cmds/incidentd/src/Reporter.cpp @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "incidentd" + +#include "Reporter.h" +#include "protobuf.h" + +#include "report_directory.h" +#include "section_list.h" + +#include <private/android_filesystem_config.h> +#include <android/os/DropBoxManager.h> +#include <utils/SystemClock.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <fcntl.h> +#include <errno.h> + +/** + * The directory where the incident reports are stored. + */ +static const String8 INCIDENT_DIRECTORY("/data/incidents"); + +static status_t +write_all(int fd, uint8_t const* buf, size_t size) +{ + while (size > 0) { + ssize_t amt = ::write(fd, buf, size); + if (amt < 0) { + return -errno; + } + size -= amt; + buf += amt; + } + return NO_ERROR; +} + +// ================================================================================ +ReportRequest::ReportRequest(const IncidentReportArgs& a, + const sp<IIncidentReportStatusListener> &l, int f) + :args(a), + listener(l), + fd(f), + err(NO_ERROR) +{ +} + +ReportRequest::~ReportRequest() +{ +} + +// ================================================================================ +ReportRequestSet::ReportRequestSet() + :mRequests(), + mWritableCount(0), + mMainFd(-1) +{ +} + +ReportRequestSet::~ReportRequestSet() +{ +} + +void +ReportRequestSet::add(const sp<ReportRequest>& request) +{ + mRequests.push_back(request); + mWritableCount++; +} + +void +ReportRequestSet::setMainFd(int fd) +{ + mMainFd = fd; + mWritableCount++; +} + +status_t +ReportRequestSet::write(uint8_t const* buf, size_t size) +{ + status_t err = EBADF; + + // The streaming ones + int const N = mRequests.size(); + for (int i=N-1; i>=0; i--) { + sp<ReportRequest> request = mRequests[i]; + if (request->fd >= 0 && request->err == NO_ERROR) { + err = write_all(request->fd, buf, size); + if (err != NO_ERROR) { + request->err = err; + mWritableCount--; + } + } + } + + // The dropbox file + if (mMainFd >= 0) { + err = write_all(mMainFd, buf, size); + if (err != NO_ERROR) { + mMainFd = -1; + mWritableCount--; + } + } + + // Return an error only when there are no FDs to write. + return mWritableCount > 0 ? NO_ERROR : err; +} + + +// ================================================================================ +Reporter::Reporter() + :args(), + batch() +{ + char buf[100]; + + // TODO: Make the max size smaller for user builds. + mMaxSize = 100 * 1024 * 1024; + mMaxCount = 100; + + // There can't be two at the same time because it's on one thread. + mStartTime = time(NULL); + strftime(buf, sizeof(buf), "/incident-%Y%m%d-%H%M%S", localtime(&mStartTime)); + mFilename = INCIDENT_DIRECTORY + buf; +} + +Reporter::~Reporter() +{ +} + +Reporter::run_report_status_t +Reporter::runReport() +{ + + status_t err = NO_ERROR; + bool needMainFd = false; + int mainFd = -1; + + // See if we need the main file + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if ((*it)->fd < 0 && mainFd < 0) { + needMainFd = true; + break; + } + } + if (needMainFd) { + // Create the directory + err = create_directory(INCIDENT_DIRECTORY); + if (err != NO_ERROR) { + goto done; + } + + // If there are too many files in the directory (for whatever reason), + // delete the oldest ones until it's under the limit. Doing this first + // does mean that we can go over, so the max size is not a hard limit. + clean_directory(INCIDENT_DIRECTORY, mMaxSize, mMaxCount); + + // Open the file. + err = create_file(&mainFd); + if (err != NO_ERROR) { + goto done; + } + + // Add to the set + batch.setMainFd(mainFd); + } + + // Tell everyone that we're starting. + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if ((*it)->listener != NULL) { + (*it)->listener->onReportStarted(); + } + } + + // Write the incident headers + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + const sp<ReportRequest> request = (*it); + const vector<vector<int8_t>>& headers = request->args.headers(); + + for (vector<vector<int8_t>>::const_iterator buf=headers.begin(); buf!=headers.end(); + buf++) { + int fd = request->fd >= 0 ? request->fd : mainFd; + + uint8_t buffer[20]; + uint8_t* p = write_length_delimited_tag_header(buffer, FIELD_ID_INCIDENT_HEADER, + buf->size()); + write_all(fd, buffer, p-buffer); + + write_all(fd, (uint8_t const*)buf->data(), buf->size()); + // If there was an error now, there will be an error later and we will remove + // it from the list then. + } + } + + // For each of the report fields, see if we need it, and if so, execute the command + // and report to those that care that we're doing it. + for (const Section** section=SECTION_LIST; *section; section++) { + const int id = (*section)->id; + ALOGD("Taking incident report section %d '%s'", id, (*section)->name.string()); + + if (this->args.containsSection(id)) { + // Notify listener of starting + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if ((*it)->listener != NULL && (*it)->args.containsSection(id)) { + (*it)->listener->onReportSectionStatus(id, + IIncidentReportStatusListener::STATUS_STARTING); + } + } + + // Execute - go get the data and write it into the file descriptors. + err = (*section)->Execute(&batch); + if (err != NO_ERROR) { + ALOGW("Incident section %s (%d) failed. Stopping report.", + (*section)->name.string(), id); + goto done; + } + + // Notify listener of starting + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if ((*it)->listener != NULL && (*it)->args.containsSection(id)) { + (*it)->listener->onReportSectionStatus(id, + IIncidentReportStatusListener::STATUS_FINISHED); + } + } + } + } + +done: + // Close the file. + if (mainFd >= 0) { + close(mainFd); + } + + // Tell everyone that we're done. + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if ((*it)->listener != NULL) { + if (err == NO_ERROR) { + (*it)->listener->onReportFinished(); + } else { + (*it)->listener->onReportFailed(); + } + } + } + + // Put the report into dropbox. + if (needMainFd && err == NO_ERROR) { + sp<DropBoxManager> dropbox = new DropBoxManager(); + Status status = dropbox->addFile(String16("incident"), mFilename, 0); + ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string()); + if (!status.isOk()) { + return REPORT_NEEDS_DROPBOX; + } + + // If the status was ok, delete the file. If not, leave it around until the next + // boot or the next checkin. If the directory gets too big older files will + // be rotated out. + unlink(mFilename.c_str()); + } + + return REPORT_FINISHED; +} + +/** + * Create our output file and set the access permissions to -rw-rw---- + */ +status_t +Reporter::create_file(int* fd) +{ + const char* filename = mFilename.c_str(); + + *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0660); + if (*fd < 0) { + ALOGE("Couldn't open incident file: %s (%s)", filename, strerror(errno)); + return -errno; + } + + // Override umask. Not super critical. If it fails go on with life. + chmod(filename, 0660); + + if (chown(filename, AID_SYSTEM, AID_SYSTEM)) { + ALOGE("Unable to change ownership of incident file %s: %s\n", filename, strerror(errno)); + status_t err = -errno; + unlink(mFilename.c_str()); + return err; + } + + return NO_ERROR; +} + +// ================================================================================ +Reporter::run_report_status_t +Reporter::upload_backlog() +{ + DIR* dir; + struct dirent* entry; + struct stat st; + + if ((dir = opendir(INCIDENT_DIRECTORY.string())) == NULL) { + ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY.string()); + return REPORT_NEEDS_DROPBOX; + } + + String8 dirbase(INCIDENT_DIRECTORY + "/"); + sp<DropBoxManager> dropbox = new DropBoxManager(); + + // Enumerate, count and add up size + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + String8 filename = dirbase + entry->d_name; + if (stat(filename.string(), &st) != 0) { + ALOGE("Unable to stat file %s", filename.string()); + continue; + } + if (!S_ISREG(st.st_mode)) { + continue; + } + + Status status = dropbox->addFile(String16("incident"), filename.string(), 0); + ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string()); + if (!status.isOk()) { + return REPORT_NEEDS_DROPBOX; + } + + // If the status was ok, delete the file. If not, leave it around until the next + // boot or the next checkin. If the directory gets too big older files will + // be rotated out. + unlink(filename.string()); + } + + closedir(dir); + + return REPORT_FINISHED; +} + diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h new file mode 100644 index 000000000000..5b86561520f8 --- /dev/null +++ b/cmds/incidentd/src/Reporter.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef REPORTER_H +#define REPORTER_H + +#include <android/os/IIncidentReportStatusListener.h> +#include <android/os/IncidentReportArgs.h> + +#include <string> +#include <vector> + +#include <time.h> + +using namespace android; +using namespace android::os; +using namespace std; + +// ================================================================================ +struct ReportRequest : public virtual RefBase +{ + IncidentReportArgs args; + sp<IIncidentReportStatusListener> listener; + int fd; + status_t err; + + ReportRequest(const IncidentReportArgs& args, + const sp<IIncidentReportStatusListener> &listener, int fd); + virtual ~ReportRequest(); +}; + +// ================================================================================ +class ReportRequestSet +{ +public: + ReportRequestSet(); + ~ReportRequestSet(); + + void add(const sp<ReportRequest>& request); + void setMainFd(int fd); + + // Write to all of the fds for the requests. If a write fails, it stops + // writing to that fd and returns NO_ERROR. When we are out of fds to write + // to it returns an error. + status_t write(uint8_t const* buf, size_t size); + + typedef vector<sp<ReportRequest>>::iterator iterator; + + iterator begin() { return mRequests.begin(); } + iterator end() { return mRequests.end(); } + +private: + vector<sp<ReportRequest>> mRequests; + int mWritableCount; + int mMainFd; +}; + +// ================================================================================ +class Reporter : public virtual RefBase +{ +public: + enum run_report_status_t { + REPORT_FINISHED = 0, + REPORT_NEEDS_DROPBOX = 1 + }; + + IncidentReportArgs args; + ReportRequestSet batch; + + Reporter(); + virtual ~Reporter(); + + // Run the report as described in the batch and args parameters. + run_report_status_t runReport(); + + static run_report_status_t upload_backlog(); + +private: + string mFilename; + off_t mMaxSize; + size_t mMaxCount; + time_t mStartTime; + + status_t create_file(int* fd); +}; + + +#endif // REPORTER_H diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp new file mode 100644 index 000000000000..fac299ed0dcd --- /dev/null +++ b/cmds/incidentd/src/Section.cpp @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "incidentd" + +#include "Section.h" +#include "protobuf.h" + +#include <binder/IServiceManager.h> +#include <mutex> + +using namespace std; + +const int64_t REMOTE_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds + +// ================================================================================ +Section::Section(int i) + :id(i) +{ +} + +Section::~Section() +{ +} + +status_t +Section::WriteHeader(ReportRequestSet* requests, size_t size) const +{ + ssize_t amt; + uint8_t buf[20]; + uint8_t* p = write_length_delimited_tag_header(buf, this->id, size); + return requests->write(buf, p-buf); +} + +// ================================================================================ +struct WorkerThreadData : public virtual RefBase +{ + const WorkerThreadSection* section; + int fds[2]; + + // Lock protects these fields + mutex lock; + bool workerDone; + status_t workerError; + + WorkerThreadData(const WorkerThreadSection* section); + virtual ~WorkerThreadData(); + + int readFd() { return fds[0]; } + int writeFd() { return fds[1]; } +}; + +WorkerThreadData::WorkerThreadData(const WorkerThreadSection* sec) + :section(sec), + workerDone(false), + workerError(NO_ERROR) +{ + fds[0] = -1; + fds[1] = -1; +} + +WorkerThreadData::~WorkerThreadData() +{ +} + +// ================================================================================ +WorkerThreadSection::WorkerThreadSection(int id) + :Section(id) +{ +} + +WorkerThreadSection::~WorkerThreadSection() +{ +} + +static void* +worker_thread_func(void* cookie) +{ + WorkerThreadData* data = (WorkerThreadData*)cookie; + status_t err = data->section->BlockingCall(data->writeFd()); + + { + unique_lock<mutex> lock(data->lock); + data->workerDone = true; + data->workerError = err; + } + + close(data->writeFd()); + data->decStrong(data->section); + // data might be gone now. don't use it after this point in this thread. + return NULL; +} + +status_t +WorkerThreadSection::Execute(ReportRequestSet* requests) const +{ + status_t err = NO_ERROR; + pthread_t thread; + pthread_attr_t attr; + bool timedOut = false; + FdBuffer buffer; + + // Data shared between this thread and the worker thread. + sp<WorkerThreadData> data = new WorkerThreadData(this); + + // Create the pipe + err = pipe(data->fds); + if (err != 0) { + return -errno; + } + + // The worker thread needs a reference and we can't let the count go to zero + // if that thread is slow to start. + data->incStrong(this); + + // Create the thread + err = pthread_attr_init(&attr); + if (err != 0) { + return -err; + } + // TODO: Do we need to tweak thread priority? + err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (err != 0) { + pthread_attr_destroy(&attr); + return -err; + } + err = pthread_create(&thread, &attr, worker_thread_func, (void*)data.get()); + if (err != 0) { + pthread_attr_destroy(&attr); + return -err; + } + pthread_attr_destroy(&attr); + + // Loop reading until either the timeout or the worker side is done (i.e. eof). + err = buffer.read(data->readFd(), REMOTE_CALL_TIMEOUT_MS); + if (err != NO_ERROR) { + // TODO: Log this error into the incident report. + ALOGW("WorkerThreadSection '%s' reader failed with error '%s'", this->name.string(), + strerror(-err)); + } + + // Done with the read fd. The worker thread closes the write one so + // we never race and get here first. + close(data->readFd()); + + // If the worker side is finished, then return its error (which may overwrite + // our possible error -- but it's more interesting anyway). If not, then we timed out. + { + unique_lock<mutex> lock(data->lock); + if (!data->workerDone) { + // We timed out + timedOut = true; + } else { + if (data->workerError != NO_ERROR) { + err = data->workerError; + // TODO: Log this error into the incident report. + ALOGW("WorkerThreadSection '%s' worker failed with error '%s'", this->name.string(), + strerror(-err)); + } + } + } + + if (timedOut || buffer.timedOut()) { + ALOGW("WorkerThreadSection '%s' timed out", this->name.string()); + return NO_ERROR; + } + + if (buffer.truncated()) { + // TODO: Log this into the incident report. + } + + // TODO: There was an error with the command or buffering. Report that. For now + // just exit with a log messasge. + if (err != NO_ERROR) { + ALOGW("WorkerThreadSection '%s' failed with error '%s'", this->name.string(), + strerror(-err)); + return NO_ERROR; + } + + // Write the data that was collected + ALOGD("section '%s' wrote %zd bytes in %d ms", name.string(), buffer.size(), + (int)buffer.durationMs()); + WriteHeader(requests, buffer.size()); + err = buffer.write(requests); + if (err != NO_ERROR) { + ALOGW("WorkerThreadSection '%s' failed writing: '%s'", this->name.string(), strerror(-err)); + return err; + } + + return NO_ERROR; +} + +// ================================================================================ +CommandSection::CommandSection(int id, const char* first, ...) + :Section(id) +{ + va_list args; + int count = 0; + + va_start(args, first); + while (va_arg(args, const char*) != NULL) { + count++; + } + va_end(args); + + mCommand = (const char**)malloc(sizeof(const char*) * count); + + mCommand[0] = first; + name = first; + name += " "; + va_start(args, first); + for (int i=0; i<count; i++) { + const char* arg = va_arg(args, const char*); + mCommand[i+1] = arg; + if (arg != NULL) { + name += va_arg(args, const char*); + name += " "; + } + } + va_end(args); +} + +CommandSection::~CommandSection() +{ +} + +status_t +CommandSection::Execute(ReportRequestSet* /*requests*/) const +{ + return NO_ERROR; +} + +// ================================================================================ +DumpsysSection::DumpsysSection(int id, const char* service, ...) + :WorkerThreadSection(id), + mService(service) +{ + name = "dumpsys "; + name += service; + + va_list args; + va_start(args, service); + while (true) { + const char* arg = va_arg(args, const char*); + if (arg == NULL) { + break; + } + mArgs.add(String16(arg)); + name += " "; + name += arg; + } + va_end(args); +} + +DumpsysSection::~DumpsysSection() +{ +} + +status_t +DumpsysSection::BlockingCall(int pipeWriteFd) const +{ + // checkService won't wait for the service to show up like getService will. + sp<IBinder> service = defaultServiceManager()->checkService(mService); + + if (service == NULL) { + // Returning an error interrupts the entire incident report, so just + // log the failure. + // TODO: have a meta record inside the report that would log this + // failure inside the report, because the fact that we can't find + // the service is good data in and of itself. This is running in + // another thread so lock that carefully... + ALOGW("DumpsysSection: Can't lookup service: %s", String8(mService).string()); + return NO_ERROR; + } + + service->dump(pipeWriteFd, mArgs); + + return NO_ERROR; +} diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h new file mode 100644 index 000000000000..35740e9771d5 --- /dev/null +++ b/cmds/incidentd/src/Section.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SECTIONS_H +#define SECTIONS_H + +#include "FdBuffer.h" + +#include <utils/String8.h> +#include <utils/String16.h> +#include <utils/Vector.h> + +using namespace android; + +/** + * Base class for sections + */ +class Section +{ +public: + int id; + String8 name; + + Section(int id); + virtual ~Section(); + + virtual status_t Execute(ReportRequestSet* requests) const = 0; + + status_t WriteHeader(ReportRequestSet* requests, size_t size) const; +}; + +/** + * Section that reads in a file. + */ +class FileSection : public Section +{ +public: + FileSection(int id, const char* filename); + virtual ~FileSection(); + + virtual status_t Execute(ReportRequestSet* requests) const; + +private: + const char* mFilename; +}; + +/** + * Base class for sections that call a command that might need a timeout. + */ +class WorkerThreadSection : public Section +{ +public: + WorkerThreadSection(int id); + virtual ~WorkerThreadSection(); + + virtual status_t Execute(ReportRequestSet* requests) const; + + virtual status_t BlockingCall(int pipeWriteFd) const = 0; +}; + +/** + * Section that forks and execs a command, and puts stdout as the section. + */ +class CommandSection : public Section +{ +public: + CommandSection(int id, const char* first, ...); + virtual ~CommandSection(); + + virtual status_t Execute(ReportRequestSet* requests) const; + +private: + const char** mCommand; +}; + +/** + * Section that calls dumpsys on a system service. + */ +class DumpsysSection : public WorkerThreadSection +{ +public: + DumpsysSection(int id, const char* service, ...); + virtual ~DumpsysSection(); + + virtual status_t BlockingCall(int pipeWriteFd) const; + +private: + String16 mService; + Vector<String16> mArgs; +}; + +#endif // SECTIONS_H + diff --git a/cmds/incidentd/src/main.cpp b/cmds/incidentd/src/main.cpp new file mode 100644 index 000000000000..3a7511d43048 --- /dev/null +++ b/cmds/incidentd/src/main.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "incidentd" + +#include "IncidentService.h" + +#include <binder/IInterface.h> +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <binder/ProcessState.h> +#include <binder/Status.h> +#include <cutils/log.h> +#include <utils/Looper.h> +#include <utils/StrongPointer.h> + +#include <sys/types.h> +#include <sys/stat.h> + +using namespace android; + +// ================================================================================ +int +main(int /*argc*/, char** /*argv*/) +{ + // Set up the looper + sp<Looper> looper(Looper::prepare(0 /* opts */)); + + // Set up the binder + sp<ProcessState> ps(ProcessState::self()); + ps->setThreadPoolMaxThreadCount(1); // everything is oneway, let it queue and save ram + ps->startThreadPool(); + ps->giveThreadPoolName(); + IPCThreadState::self()->disableBackgroundScheduling(true); + + // Create the service + android::sp<IncidentService> service = new IncidentService(looper); + if (defaultServiceManager()->addService(String16("incident"), service) != 0) { + ALOGE("Failed to add service"); + return -1; + } + + // Loop forever -- the reports run on this thread in a handler, and the + // binder calls remain responsive in their pool of one thread. + while (true) { + looper->pollAll(-1 /* timeoutMillis */); + } + ALOGW("incidentd escaped from its loop."); + + return 1; +} diff --git a/cmds/incidentd/src/protobuf.cpp b/cmds/incidentd/src/protobuf.cpp new file mode 100644 index 000000000000..cb864fd3b619 --- /dev/null +++ b/cmds/incidentd/src/protobuf.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "protobuf.h" + +uint8_t* +write_raw_varint(uint8_t* buf, uint32_t val) +{ + uint8_t* p = buf; + while (true) { + if ((val & ~0x7F) == 0) { + *p++ = (uint8_t)val; + return p; + } else { + *p++ = (uint8_t)((val & 0x7F) | 0x80); + val >>= 7; + } + } +} + +uint8_t* +write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size) +{ + buf = write_raw_varint(buf, (fieldId << 3) | 2); + buf = write_raw_varint(buf, size); + return buf; +} + diff --git a/cmds/incidentd/src/protobuf.h b/cmds/incidentd/src/protobuf.h new file mode 100644 index 000000000000..a24399832b00 --- /dev/null +++ b/cmds/incidentd/src/protobuf.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PROTOBUF_H +#define PROTOBUF_H + +#include <stdint.h> + +/** + * Write a varint into the buffer. Return the next position to write at. + * There must be 10 bytes in the buffer. The same as EncodedBuffer.writeRawVarint32 + */ +uint8_t* write_raw_varint(uint8_t* buf, uint32_t val); + +/** + * Write a protobuf WIRE_TYPE_LENGTH_DELIMITED header. Return the next position to write at. + * There must be 20 bytes in the buffer. + */ +uint8_t* write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size); + +enum { + // IncidentProto.header + FIELD_ID_INCIDENT_HEADER = 1 +}; + +#endif // PROTOBUF_H + diff --git a/cmds/incidentd/src/report_directory.cpp b/cmds/incidentd/src/report_directory.cpp new file mode 100644 index 000000000000..f60b8ac46cca --- /dev/null +++ b/cmds/incidentd/src/report_directory.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "incidentd" + +#include "report_directory.h" + +#include <cutils/log.h> +#include <private/android_filesystem_config.h> +#include <utils/String8.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <libgen.h> +#include <unistd.h> + +#include <vector> + +using namespace android; +using namespace std; + +status_t +create_directory(const char* directory) +{ + struct stat st; + status_t err = NO_ERROR; + char* dir = strdup(directory); + + // Skip first slash + char* d = dir + 1; + + // Create directories, assigning them to the system user + bool last = false; + while (!last) { + d = strchr(d, '/'); + if (d != NULL) { + *d = '\0'; + } else { + last = true; + } + if (stat(dir, &st) == 0) { + if (!S_ISDIR(st.st_mode)) { + err = ALREADY_EXISTS; + goto done; + } + } else { + if (mkdir(dir, 0770)) { + ALOGE("No incident reports today. " + "Unable to create incident report dir %s: %s", dir, + strerror(errno)); + err = -errno; + goto done; + } + if (chmod(dir, 0770)) { + ALOGE("No incident reports today. " + "Unable to set permissions for incident report dir %s: %s", dir, + strerror(errno)); + err = -errno; + goto done; + } + if (chown(dir, AID_SYSTEM, AID_SYSTEM)) { + ALOGE("No incident reports today. Unable to change ownership of dir %s: %s\n", + dir, strerror(errno)); + err = -errno; + goto done; + } + } + if (!last) { + *d++ = '/'; + } + } + + // Ensure that the final directory is owned by the system with 0770. If it isn't + // we won't write into it. + if (stat(directory, &st) != 0) { + ALOGE("No incident reports today. Can't stat: %s", directory); + err = -errno; + goto done; + } + if ((st.st_mode & 0777) != 0770) { + ALOGE("No incident reports today. Mode is %0o on report directory %s", + st.st_mode, directory); + err = BAD_VALUE; + goto done; + } + if (st.st_uid != AID_SYSTEM || st.st_gid != AID_SYSTEM) { + ALOGE("No incident reports today. Owner is %d and group is %d on report directory %s", + st.st_uid, st.st_gid, directory); + err = BAD_VALUE; + goto done; + } + +done: + free(dir); + return err; +} + +static bool +stat_mtime_cmp(const pair<String8,struct stat>& a, const pair<String8,struct stat>& b) +{ + return a.second.st_mtime < b.second.st_mtime; +} + +void +clean_directory(const char* directory, off_t maxSize, size_t maxCount) +{ + DIR* dir; + struct dirent* entry; + struct stat st; + + vector<pair<String8,struct stat>> files; + + if ((dir = opendir(directory)) == NULL) { + ALOGE("Couldn't open incident directory: %s", directory); + return; + } + + String8 dirbase(String8(directory) + "/"); + + off_t totalSize = 0; + size_t totalCount = 0; + + // Enumerate, count and add up size + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + String8 filename = dirbase + entry->d_name; + if (stat(filename.string(), &st) != 0) { + ALOGE("Unable to stat file %s", filename.string()); + continue; + } + if (!S_ISREG(st.st_mode)) { + continue; + } + files.push_back(pair<String8,struct stat>(filename, st)); + + totalSize += st.st_size; + totalCount++; + } + + closedir(dir); + + // Count or size is less than max, then we're done. + if (totalSize < maxSize && totalCount < maxCount) { + return; + } + + // Oldest files first. + sort(files.begin(), files.end(), stat_mtime_cmp); + + // Remove files until we're under our limits. + for (vector<pair<String8,struct stat>>::iterator it = files.begin(); + it != files.end() && totalSize >= maxSize && totalCount >= maxCount; it++) { + remove(it->first.string()); + totalSize -= it->second.st_size; + totalCount--; + } +} diff --git a/cmds/incidentd/src/report_directory.h b/cmds/incidentd/src/report_directory.h new file mode 100644 index 000000000000..bed4f869cfe4 --- /dev/null +++ b/cmds/incidentd/src/report_directory.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DIRECTORY_CLEANER_H +#define DIRECTORY_CLEANER_H + +#include <utils/Errors.h> + +#include <sys/types.h> + +using namespace android; + +status_t create_directory(const char* directory); +void clean_directory(const char* directory, off_t maxSize, size_t maxCount); + +#endif // DIRECTORY_CLEANER_H diff --git a/cmds/incidentd/src/section_list.cpp b/cmds/incidentd/src/section_list.cpp new file mode 100644 index 000000000000..b6112ed0f7df --- /dev/null +++ b/cmds/incidentd/src/section_list.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "section_list.h" + +//using namespace android::util; + +/** + * This is the mapping of section IDs to the commands that are run to get those commands. + */ +const Section* SECTION_LIST[] = { + new DumpsysSection(3000, + "fingerprint", "--proto", "--incident", NULL), + NULL +}; + diff --git a/cmds/incidentd/src/section_list.h b/cmds/incidentd/src/section_list.h new file mode 100644 index 000000000000..c97751937d6d --- /dev/null +++ b/cmds/incidentd/src/section_list.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SECTION_LIST_H +#define SECTION_LIST_H + +#include "Section.h" + +/** + * This is the mapping of section IDs to the commands that are run to get those commands. + */ +extern const Section* SECTION_LIST[]; + +#endif // SECTION_LIST_H + diff --git a/cmds/input/src/com/android/commands/input/Input.java b/cmds/input/src/com/android/commands/input/Input.java index 754d3f510bbd..9ee11f8571e2 100644 --- a/cmds/input/src/com/android/commands/input/Input.java +++ b/cmds/input/src/com/android/commands/input/Input.java @@ -23,6 +23,7 @@ import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.ViewConfiguration; import java.util.HashMap; import java.util.Map; @@ -118,6 +119,19 @@ public class Input { duration); return; } + } else if (command.equals("draganddrop")) { + int duration = -1; + inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN); + switch (length) { + case 6: + duration = Integer.parseInt(args[index+5]); + case 5: + sendDragAndDrop(inputSource, + Float.parseFloat(args[index+1]), Float.parseFloat(args[index+2]), + Float.parseFloat(args[index+3]), Float.parseFloat(args[index+4]), + duration); + return; + } } else if (command.equals("press")) { inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL); if (length == 1) { @@ -216,6 +230,31 @@ public class Input { injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, x2, y2, 0.0f); } + private void sendDragAndDrop(int inputSource, float x1, float y1, float x2, float y2, + int dragDuration) { + if (dragDuration < 0) { + dragDuration = 300; + } + long now = SystemClock.uptimeMillis(); + injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, now, x1, y1, 1.0f); + try { + Thread.sleep(ViewConfiguration.getLongPressTimeout()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + now = SystemClock.uptimeMillis(); + long startTime = now; + long endTime = startTime + dragDuration; + while (now < endTime) { + long elapsedTime = now - startTime; + float alpha = (float) elapsedTime / dragDuration; + injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, lerp(x1, x2, alpha), + lerp(y1, y2, alpha), 1.0f); + now = SystemClock.uptimeMillis(); + } + injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, x2, y2, 0.0f); + } + /** * Sends a simple zero-pressure move event. * @@ -294,6 +333,8 @@ public class Input { System.err.println(" tap <x> <y> (Default: touchscreen)"); System.err.println(" swipe <x1> <y1> <x2> <y2> [duration(ms)]" + " (Default: touchscreen)"); + System.err.println(" draganddrop <x1> <y1> <x2> <y2> [duration(ms)]" + + " (Default: touchscreen)"); System.err.println(" press (Default: trackball)"); System.err.println(" roll <dx> <dy> (Default: trackball)"); } diff --git a/cmds/locksettings/Android.mk b/cmds/locksettings/Android.mk new file mode 100644 index 000000000000..76766c7c6955 --- /dev/null +++ b/cmds/locksettings/Android.mk @@ -0,0 +1,30 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_MODULE := locksettings +LOCAL_MODULE_TAGS := optional +include $(BUILD_JAVA_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := locksettings +LOCAL_SRC_FILES := locksettings +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MODULE_TAGS := optional +include $(BUILD_PREBUILT) + + diff --git a/cmds/locksettings/locksettings b/cmds/locksettings/locksettings new file mode 100755 index 000000000000..c963b238726b --- /dev/null +++ b/cmds/locksettings/locksettings @@ -0,0 +1,5 @@ +# Script to start "locksettings" on the device +# +base=/system +export CLASSPATH=$base/framework/locksettings.jar +exec app_process $base/bin com.android.commands.locksettings.LockSettingsCmd "$@" diff --git a/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java b/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java new file mode 100644 index 000000000000..1e426d62e4e0 --- /dev/null +++ b/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.commands.locksettings; + +import android.os.ResultReceiver; +import android.os.ServiceManager; +import android.os.ShellCallback; + +import com.android.internal.os.BaseCommand; +import com.android.internal.widget.ILockSettings; + +import java.io.FileDescriptor; +import java.io.PrintStream; + +public final class LockSettingsCmd extends BaseCommand { + + private static final String USAGE = + "usage: locksettings set-pattern [--old OLD_CREDENTIAL] NEW_PATTERN\n" + + " locksettings set-pin [--old OLD_CREDENTIAL] NEW_PIN\n" + + " locksettings set-password [--old OLD_CREDENTIAL] NEW_PASSWORD\n" + + " locksettings clear [--old OLD_CREDENTIAL]\n" + + "\n" + + "locksettings set-pattern: sets a pattern\n" + + " A pattern is specified by a non-separated list of numbers that index the cell\n" + + " on the pattern in a 1-based manner in left to right and top to bottom order,\n" + + " i.e. the top-left cell is indexed with 1, whereas the bottom-right cell\n" + + " is indexed with 9. Example: 1234\n" + + "\n" + + "locksettings set-pin: sets a PIN\n" + + "\n" + + "locksettings set-password: sets a password\n" + + "\n" + + "locksettings clear: clears the unlock credential\n"; + + public static void main(String[] args) { + (new LockSettingsCmd()).run(args); + } + + @Override + public void onShowUsage(PrintStream out) { + out.println(USAGE); + } + + @Override + public void onRun() throws Exception { + ILockSettings lockSettings = ILockSettings.Stub.asInterface( + ServiceManager.getService("lock_settings")); + lockSettings.asBinder().shellCommand(FileDescriptor.in, FileDescriptor.out, + FileDescriptor.err, getRawArgs(), new ShellCallback(), new ResultReceiver(null) {}); + } +} diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java index d7f23cb44098..9df229cbf490 100644 --- a/cmds/media/src/com/android/commands/media/Media.java +++ b/cmds/media/src/com/android/commands/media/Media.java @@ -57,22 +57,26 @@ public class Media extends BaseCommand { (new Media()).run(args); } + @Override public void onShowUsage(PrintStream out) { out.println( "usage: media [subcommand] [options]\n" + " media dispatch KEY\n" + " media list-sessions\n" + " media monitor <tag>\n" + + " media volume [options]\n" + "\n" + "media dispatch: dispatch a media key to the system.\n" + " KEY may be: play, pause, play-pause, mute, headsethook,\n" + " stop, next, previous, rewind, record, fast-forword.\n" + "media list-sessions: print a list of the current sessions.\n" + "media monitor: monitor updates to the specified session.\n" + - " Use the tag from list-sessions.\n" + " Use the tag from list-sessions.\n" + + "media volume: " + VolumeCtrl.USAGE ); } + @Override public void onRun() throws Exception { mSessionService = ISessionManager.Stub.asInterface(ServiceManager.checkService( Context.MEDIA_SESSION_SERVICE)); @@ -90,6 +94,8 @@ public class Media extends BaseCommand { runListSessions(); } else if (op.equals("monitor")) { runMonitor(); + } else if (op.equals("volume")) { + runVolume(); } else { showError("Error: unknown command '" + op + "'"); return; @@ -310,4 +316,10 @@ public class Media extends BaseCommand { System.out.println("***Error listing sessions***"); } } + + //================================= + // "volume" command for stream volume control + private void runVolume() throws Exception { + VolumeCtrl.run(this); + } } diff --git a/cmds/media/src/com/android/commands/media/VolumeCtrl.java b/cmds/media/src/com/android/commands/media/VolumeCtrl.java new file mode 100755 index 000000000000..1629c6f178f0 --- /dev/null +++ b/cmds/media/src/com/android/commands/media/VolumeCtrl.java @@ -0,0 +1,185 @@ +/* +** +** Copyright 2016, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package com.android.commands.media; + +import android.content.Context; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.media.IAudioService; +import android.os.ServiceManager; +import android.util.AndroidException; + +import com.android.internal.os.BaseCommand; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.lang.ArrayIndexOutOfBoundsException; + +/** + * Command line tool to exercise AudioService.setStreamVolume() + * and AudioService.adjustStreamVolume() + */ +public class VolumeCtrl { + + private final static String TAG = "VolumeCtrl"; + + // --stream affects --set, --adj or --get options. + // --show affects --set and --adj options. + // --get can be used with --set, --adj or by itself. + public final static String USAGE = new String( + "the options are as follows: \n" + + "\t\t--stream STREAM selects the stream to control, see AudioManager.STREAM_*\n" + + "\t\t controls AudioManager.STREAM_MUSIC if no stream is specified\n"+ + "\t\t--set INDEX sets the volume index value\n" + + "\t\t--adj DIRECTION adjusts the volume, use raise|same|lower for the direction\n" + + "\t\t--get outputs the current volume\n" + + "\t\t--show shows the UI during the volume change\n" + + "\texamples:\n" + + "\t\tadb shell media volume --show --stream 3 --set 11\n" + + "\t\tadb shell media volume --stream 0 --adj lower\n" + + "\t\tadb shell media volume --stream 3 --get\n" + ); + + private final static int VOLUME_CONTROL_MODE_SET = 1; + private final static int VOLUME_CONTROL_MODE_ADJUST = 2; + + private final static String ADJUST_LOWER = "lower"; + private final static String ADJUST_SAME = "same"; + private final static String ADJUST_RAISE = "raise"; + + public static void run(BaseCommand cmd) throws Exception { + //---------------------------------------- + // Default parameters + int stream = AudioManager.STREAM_MUSIC; + int volIndex = 5; + int mode = 0; + int adjDir = AudioManager.ADJUST_RAISE; + boolean showUi = false; + boolean doGet = false; + + //---------------------------------------- + // read options + String option; + String adjustment = null; + while ((option = cmd.nextOption()) != null) { + switch (option) { + case "--show": + showUi = true; + break; + case "--get": + doGet = true; + log(LOG_V, "will get volume"); + break; + case "--stream": + stream = Integer.decode(cmd.nextArgRequired()).intValue(); + log(LOG_V, "will control stream=" + stream + " (" + streamName(stream) + ")"); + break; + case "--set": + volIndex = Integer.decode(cmd.nextArgRequired()).intValue(); + mode = VOLUME_CONTROL_MODE_SET; + log(LOG_V, "will set volume to index=" + volIndex); + break; + case "--adj": + mode = VOLUME_CONTROL_MODE_ADJUST; + adjustment = cmd.nextArgRequired(); + log(LOG_V, "will adjust volume"); + break; + default: + throw new IllegalArgumentException("Unknown argument " + option); + } + } + + //------------------------------ + // Read options: validation + if (mode == VOLUME_CONTROL_MODE_ADJUST) { + if (adjustment == null) { + cmd.showError("Error: no valid volume adjustment (null)"); + return; + } + switch (adjustment) { + case ADJUST_RAISE: adjDir = AudioManager.ADJUST_RAISE; break; + case ADJUST_SAME: adjDir = AudioManager.ADJUST_SAME; break; + case ADJUST_LOWER: adjDir = AudioManager.ADJUST_LOWER; break; + default: + cmd.showError("Error: no valid volume adjustment, was " + adjustment + + ", expected " + ADJUST_LOWER + "|" + ADJUST_SAME + "|" + + ADJUST_RAISE); + return; + } + } + + //---------------------------------------- + // Test initialization + log(LOG_V, "Connecting to AudioService"); + IAudioService audioService = IAudioService.Stub.asInterface(ServiceManager.checkService( + Context.AUDIO_SERVICE)); + if (audioService == null) { + System.err.println(BaseCommand.NO_SYSTEM_ERROR_CODE); + throw new AndroidException( + "Can't connect to audio service; is the system running?"); + } + + if (mode == VOLUME_CONTROL_MODE_SET) { + if ((volIndex > audioService.getStreamMaxVolume(stream)) + || (volIndex < audioService.getStreamMinVolume(stream))) { + cmd.showError(String.format("Error: invalid volume index %d for stream %d " + + "(should be in [%d..%d])", volIndex, stream, + audioService.getStreamMinVolume(stream), + audioService.getStreamMaxVolume(stream))); + return; + } + } + + //---------------------------------------- + // Non-interactive test + final int flag = showUi? AudioManager.FLAG_SHOW_UI : 0; + final String pack = cmd.getClass().getPackage().getName(); + if (mode == VOLUME_CONTROL_MODE_SET) { + audioService.setStreamVolume(stream, volIndex, flag, pack/*callingPackage*/); + } else if (mode == VOLUME_CONTROL_MODE_ADJUST) { + audioService.adjustStreamVolume(stream, adjDir, flag, pack); + } + if (doGet) { + log(LOG_V, "volume is " + audioService.getStreamVolume(stream) + + " in range [" + audioService.getStreamMinVolume(stream) + + ".." + audioService.getStreamMaxVolume(stream) + "]"); + } + } + + //-------------------------------------------- + // Utilities + + static final String LOG_V = "[v]"; + static final String LOG_W = "[w]"; + static final String LOG_OK = "[ok]"; + + static void log(String code, String msg) { + System.out.println(code + " " + msg); + } + + static String streamName(int stream) { + try { + return AudioSystem.STREAM_NAMES[stream]; + } catch (ArrayIndexOutOfBoundsException e) { + return "invalid stream"; + } + } + +} diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index 1b4eda804312..d71573f7ca50 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -24,7 +24,6 @@ import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATIO import android.accounts.IAccountManager; import android.app.ActivityManager; -import android.app.ActivityManagerNative; import android.app.PackageInstallObserver; import android.content.ComponentName; import android.content.Context; @@ -52,16 +51,23 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; import android.os.IUserManager; +import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; +import android.os.SELinux; import android.os.ServiceManager; +import android.os.ShellCallback; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; +import android.util.Pair; import com.android.internal.content.PackageHelper; import com.android.internal.util.ArrayUtils; @@ -72,6 +78,7 @@ import libcore.io.IoUtils; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -80,6 +87,7 @@ import java.util.concurrent.TimeUnit; public final class Pm { private static final String TAG = "Pm"; + private static final String STDIN_PATH = "-"; IPackageManager mPm; IPackageInstaller mInstaller; @@ -288,13 +296,45 @@ public final class Pm { } } + static final class MyShellCallback extends ShellCallback { + @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) { + File file = new File(path); + final ParcelFileDescriptor fd; + try { + fd = ParcelFileDescriptor.open(file, + ParcelFileDescriptor.MODE_CREATE | + ParcelFileDescriptor.MODE_TRUNCATE | + ParcelFileDescriptor.MODE_WRITE_ONLY); + } catch (FileNotFoundException e) { + String msg = "Unable to open file " + path + ": " + e; + System.err.println(msg); + throw new IllegalArgumentException(msg); + } + if (seLinuxContext != null) { + final String tcon = SELinux.getFileContext(file.getAbsolutePath()); + if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) { + try { + fd.close(); + } catch (IOException e) { + } + String msg = "System server has no access to file context " + tcon; + System.err.println(msg + " (from path " + file.getAbsolutePath() + + ", context " + seLinuxContext + ")"); + throw new IllegalArgumentException(msg); + } + } + return fd; + } + } + private int runShellCommand(String serviceName, String[] args) { final HandlerThread handlerThread = new HandlerThread("results"); handlerThread.start(); try { ServiceManager.getService(serviceName).shellCommand( FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, - args, new ResultReceiver(new Handler(handlerThread.getLooper()))); + args, new MyShellCallback(), + new ResultReceiver(new Handler(handlerThread.getLooper()))); return 0; } catch (RemoteException e) { e.printStackTrace(); @@ -309,7 +349,7 @@ public final class Pm { private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { @Override - public void send(int code, Intent intent, String resolvedType, + public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { try { mResult.offer(intent, 5, TimeUnit.SECONDS); @@ -365,27 +405,26 @@ public final class Pm { * The use of "adb install" or "cmd package install" over "pm install" is highly encouraged. */ private int runInstall() throws RemoteException { + long startedTime = SystemClock.elapsedRealtime(); final InstallParams params = makeInstallParams(); final String inPath = nextArg(); - boolean installExternal = - (params.sessionParams.installFlags & PackageManager.INSTALL_EXTERNAL) != 0; - if (params.sessionParams.sizeBytes < 0 && inPath != null) { + if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) { File file = new File(inPath); if (file.isFile()) { - if (installExternal) { - try { - ApkLite baseApk = PackageParser.parseApkLite(file, 0); - PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null); - params.sessionParams.setSize( - PackageHelper.calculateInstalledSize(pkgLite, false, - params.sessionParams.abiOverride)); - } catch (PackageParserException | IOException e) { - System.err.println("Error: Failed to parse APK file : " + e); - return 1; - } - } else { - params.sessionParams.setSize(file.length()); + try { + ApkLite baseApk = PackageParser.parseApkLite(file, 0); + PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null, + null, null); + params.sessionParams.setSize( + PackageHelper.calculateInstalledSize(pkgLite, false, + params.sessionParams.abiOverride)); + } catch (PackageParserException | IOException e) { + System.err.println("Error: Failed to parse APK file: " + e); + return 1; } + } else { + System.err.println("Error: Can't open non-file: " + inPath); + return 1; } } @@ -393,7 +432,7 @@ public final class Pm { params.installerPackageName, params.userId); try { - if (inPath == null && params.sessionParams.sizeBytes == 0) { + if (inPath == null && params.sessionParams.sizeBytes == -1) { System.err.println("Error: must either specify a package size or an APK file"); return 1; } @@ -401,10 +440,12 @@ public final class Pm { false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) { return 1; } - if (doCommitSession(sessionId, false /*logSuccess*/) - != PackageInstaller.STATUS_SUCCESS) { + Pair<String, Integer> status = doCommitSession(sessionId, false /*logSuccess*/); + if (status.second != PackageInstaller.STATUS_SUCCESS) { return 1; } + Log.i(TAG, "Package " + status.first + " installed in " + (SystemClock.elapsedRealtime() + - startedTime) + " ms"); System.out.println("Success"); return 0; } finally { @@ -422,7 +463,7 @@ public final class Pm { private int runInstallCommit() throws RemoteException { final int sessionId = Integer.parseInt(nextArg()); - return doCommitSession(sessionId, true /*logSuccess*/); + return doCommitSession(sessionId, true /*logSuccess*/).second; } private int runInstallCreate() throws RemoteException { @@ -509,14 +550,28 @@ public final class Pm { throw new IllegalArgumentException("Missing inherit package name"); } break; + case "--pkg": + sessionParams.appPackageName = nextOptionData(); + if (sessionParams.appPackageName == null) { + throw new IllegalArgumentException("Missing package name"); + } + break; case "-S": - sessionParams.setSize(Long.parseLong(nextOptionData())); + final long sizeBytes = Long.parseLong(nextOptionData()); + if (sizeBytes <= 0) { + throw new IllegalArgumentException("Size must be positive"); + } + sessionParams.setSize(sizeBytes); break; case "--abi": sessionParams.abiOverride = checkAbiArgument(nextOptionData()); break; case "--ephemeral": - sessionParams.installFlags |= PackageManager.INSTALL_EPHEMERAL; + case "--instant": + sessionParams.setInstallAsInstantApp(true /*isInstantApp*/); + break; + case "--full": + sessionParams.setInstallAsInstantApp(false /*isInstantApp*/); break; case "--user": params.userId = UserHandle.parseUserArg(nextOptionData()); @@ -555,7 +610,7 @@ public final class Pm { private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName, boolean logSuccess) throws RemoteException { - if ("-".equals(inPath)) { + if (STDIN_PATH.equals(inPath)) { inPath = null; } else if (inPath != null) { final File file = new File(inPath); @@ -608,7 +663,8 @@ public final class Pm { } } - private int doCommitSession(int sessionId, boolean logSuccess) throws RemoteException { + private Pair<String, Integer> doCommitSession(int sessionId, boolean logSuccess) + throws RemoteException { PackageInstaller.Session session = null; try { session = new PackageInstaller.Session( @@ -628,7 +684,7 @@ public final class Pm { System.err.println("Failure [" + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]"); } - return status; + return new Pair<>(result.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME), status); } finally { IoUtils.closeQuietly(session); } @@ -967,11 +1023,12 @@ public final class Pm { // In non-split user mode, userId can only be SYSTEM int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM; info = mUm.createRestrictedProfile(name, parentUserId); - mAm.addSharedAccountsFromParentUser(parentUserId, userId); + mAm.addSharedAccountsFromParentUser(parentUserId, userId, + (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell"); } else if (userId < 0) { info = mUm.createUser(name, flags); } else { - info = mUm.createProfileForUser(name, flags, userId); + info = mUm.createProfileForUser(name, flags, userId, null); } if (info != null) { @@ -1153,7 +1210,7 @@ public final class Pm { ClearDataObserver obs = new ClearDataObserver(); try { - ActivityManagerNative.getDefault().clearApplicationUserData(pkg, obs, userId); + ActivityManager.getService().clearApplicationUserData(pkg, obs, userId); synchronized (obs) { while (!obs.finished) { try { @@ -1386,10 +1443,10 @@ public final class Pm { System.err.println("Error: no size specified"); return showUsage(); } - int len = size.length(); long multiplier = 1; - if (len > 1) { - char c = size.charAt(len-1); + int len = size.length(); + char c = size.charAt(len - 1); + if (c < '0' || c > '9') { if (c == 'K' || c == 'k') { multiplier = 1024L; } else if (c == 'M' || c == 'm') { @@ -1415,7 +1472,8 @@ public final class Pm { } ClearDataObserver obs = new ClearDataObserver(); try { - mPm.freeStorageAndNotify(volumeUuid, sizeVal, obs); + mPm.freeStorageAndNotify(volumeUuid, sizeVal, + StorageManager.FLAG_ALLOCATE_DEFY_RESERVED, obs); synchronized (obs) { while (!obs.finished) { try { @@ -1519,7 +1577,7 @@ public final class Pm { System.err.println(" pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH]"); System.err.println(" pm install-commit SESSION_ID"); System.err.println(" pm install-abandon SESSION_ID"); - System.err.println(" pm uninstall [-k] [--user USER_ID] PACKAGE"); + System.err.println(" pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE"); System.err.println(" pm set-installer PACKAGE INSTALLER"); System.err.println(" pm move-package PACKAGE [internal|UUID]"); System.err.println(" pm move-primary-storage [internal|UUID]"); @@ -1529,6 +1587,7 @@ public final class Pm { System.err.println(" pm disable-user [--user USER_ID] PACKAGE_OR_COMPONENT"); System.err.println(" pm disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT"); System.err.println(" pm default-state [--user USER_ID] PACKAGE_OR_COMPONENT"); + System.err.println(" pm set-user-restriction [--user USER_ID] RESTRICTION VALUE"); System.err.println(" pm hide [--user USER_ID] PACKAGE_OR_COMPONENT"); System.err.println(" pm unhide [--user USER_ID] PACKAGE_OR_COMPONENT"); System.err.println(" pm grant [--user USER_ID] PACKAGE PERMISSION"); diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp index 7bf073b4b1a1..5fedc9e74087 100644 --- a/cmds/screencap/screencap.cpp +++ b/cmds/screencap/screencap.cpp @@ -84,6 +84,11 @@ static status_t notifyMediaScanner(const char* fileName) { int main(int argc, char** argv) { + // setThreadPoolMaxThreadCount(0) actually tells the kernel it's + // not allowed to spawn any additional threads, but we still spawn + // a binder thread from userspace when we call startThreadPool(). + // See b/36066697 for rationale + ProcessState::self()->setThreadPoolMaxThreadCount(0); ProcessState::self()->startThreadPool(); const char* pname = argv[0]; @@ -162,7 +167,9 @@ int main(int argc, char** argv) uint8_t displayOrientation = configs[activeConfig].orientation; uint32_t captureOrientation = ORIENTATION_MAP[displayOrientation]; - status_t result = screenshot.update(display, Rect(), 0, 0, 0, -1U, + status_t result = screenshot.update(display, Rect(), + 0 /* reqWidth */, 0 /* reqHeight */, + INT32_MIN, INT32_MAX, /* all layers */ false, captureOrientation); if (result == NO_ERROR) { base = screenshot.getPixels(); @@ -175,13 +182,20 @@ int main(int argc, char** argv) if (base != NULL) { if (png) { - const SkImageInfo info = SkImageInfo::Make(w, h, flinger2skia(f), - kPremul_SkAlphaType); - SkAutoTUnref<SkData> data(SkImageEncoder::EncodeData(info, base, s*bytesPerPixel(f), - SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality)); - if (data.get()) { - write(fd, data->data(), data->size()); - } + const SkImageInfo info = + SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType); + SkPixmap pixmap(info, base, s * bytesPerPixel(f)); + struct FDWStream final : public SkWStream { + size_t fBytesWritten = 0; + int fFd; + FDWStream(int f) : fFd(f) {} + size_t bytesWritten() const override { return fBytesWritten; } + bool write(const void* buffer, size_t size) override { + fBytesWritten += size; + return size == 0 || ::write(fFd, buffer, size) > 0; + } + } fdStream(fd); + (void)SkEncodeImage(&fdStream, pixmap, SkEncodedImageFormat::kPNG, 100); if (fn != NULL) { notifyMediaScanner(fn); } diff --git a/cmds/settings/Android.mk b/cmds/settings/Android.mk index 05deb99f7228..8a8d1bb95c66 100644 --- a/cmds/settings/Android.mk +++ b/cmds/settings/Android.mk @@ -3,11 +3,6 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_MODULE := settings -LOCAL_MODULE_TAGS := optional -include $(BUILD_JAVA_LIBRARY) - include $(CLEAR_VARS) LOCAL_MODULE := settings LOCAL_SRC_FILES := settings diff --git a/cmds/settings/settings b/cmds/settings/settings index ef459ca7d4c4..d41ccc62811a 100755 --- a/cmds/settings/settings +++ b/cmds/settings/settings @@ -1,5 +1,2 @@ -# Script to start "settings" on the device -# -base=/system -export CLASSPATH=$base/framework/settings.jar -exec app_process $base/bin com.android.commands.settings.SettingsCmd "$@" +#!/system/bin/sh +cmd settings "$@" diff --git a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java b/cmds/settings/src/com/android/commands/settings/SettingsCmd.java deleted file mode 100644 index e63a1f58f8fc..000000000000 --- a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.commands.settings; - -import android.app.ActivityManagerNative; -import android.app.IActivityManager; -import android.app.IActivityManager.ContentProviderHolder; -import android.content.IContentProvider; -import android.database.Cursor; -import android.net.Uri; -import android.os.Binder; -import android.os.Bundle; -import android.os.IBinder; -import android.os.Process; -import android.os.RemoteException; -import android.os.UserHandle; -import android.provider.Settings; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public final class SettingsCmd { - - enum CommandVerb { - UNSPECIFIED, - GET, - PUT, - DELETE, - LIST, - } - - static String[] mArgs; - int mNextArg; - int mUser = -1; // unspecified - CommandVerb mVerb = CommandVerb.UNSPECIFIED; - String mTable = null; - String mKey = null; - String mValue = null; - - public static void main(String[] args) { - if (args == null || args.length < 2) { - printUsage(); - return; - } - - mArgs = args; - try { - new SettingsCmd().run(); - } catch (Exception e) { - System.err.println("Unable to run settings command"); - } - } - - public void run() { - boolean valid = false; - String arg; - try { - while ((arg = nextArg()) != null) { - if ("--user".equals(arg)) { - if (mUser != -1) { - // --user specified more than once; invalid - break; - } - arg = nextArg(); - if ("current".equals(arg) || "cur".equals(arg)) { - mUser = UserHandle.USER_CURRENT; - } else { - mUser = Integer.parseInt(arg); - } - } else if (mVerb == CommandVerb.UNSPECIFIED) { - if ("get".equalsIgnoreCase(arg)) { - mVerb = CommandVerb.GET; - } else if ("put".equalsIgnoreCase(arg)) { - mVerb = CommandVerb.PUT; - } else if ("delete".equalsIgnoreCase(arg)) { - mVerb = CommandVerb.DELETE; - } else if ("list".equalsIgnoreCase(arg)) { - mVerb = CommandVerb.LIST; - } else { - // invalid - System.err.println("Invalid command: " + arg); - break; - } - } else if (mTable == null) { - if (!"system".equalsIgnoreCase(arg) - && !"secure".equalsIgnoreCase(arg) - && !"global".equalsIgnoreCase(arg)) { - System.err.println("Invalid namespace '" + arg + "'"); - break; // invalid - } - mTable = arg.toLowerCase(); - if (mVerb == CommandVerb.LIST) { - valid = true; - break; - } - } else if (mVerb == CommandVerb.GET || mVerb == CommandVerb.DELETE) { - mKey = arg; - if (mNextArg >= mArgs.length) { - valid = true; - } else { - System.err.println("Too many arguments"); - } - break; - } else if (mKey == null) { - mKey = arg; - // keep going; there's another PUT arg - } else { // PUT, final arg - mValue = arg; - if (mNextArg >= mArgs.length) { - valid = true; - } else { - System.err.println("Too many arguments"); - } - break; - } - } - } catch (Exception e) { - valid = false; - } - - if (valid) { - try { - IActivityManager activityManager = ActivityManagerNative.getDefault(); - if (mUser == UserHandle.USER_CURRENT) { - mUser = activityManager.getCurrentUser().id; - } - if (mUser < 0) { - mUser = UserHandle.USER_SYSTEM; - } - IContentProvider provider = null; - IBinder token = new Binder(); - try { - ContentProviderHolder holder = activityManager.getContentProviderExternal( - "settings", UserHandle.USER_SYSTEM, token); - if (holder == null) { - throw new IllegalStateException("Could not find settings provider"); - } - provider = holder.provider; - - switch (mVerb) { - case GET: - System.out.println(getForUser(provider, mUser, mTable, mKey)); - break; - case PUT: - putForUser(provider, mUser, mTable, mKey, mValue); - break; - case DELETE: - System.out.println("Deleted " - + deleteForUser(provider, mUser, mTable, mKey) + " rows"); - break; - case LIST: - for (String line : listForUser(provider, mUser, mTable)) { - System.out.println(line); - } - break; - default: - System.err.println("Unspecified command"); - break; - } - - } finally { - if (provider != null) { - activityManager.removeContentProviderExternal("settings", token); - } - } - } catch (Exception e) { - System.err.println("Error while accessing settings provider"); - e.printStackTrace(); - } - - } else { - printUsage(); - } - } - - private List<String> listForUser(IContentProvider provider, int userHandle, String table) { - final Uri uri = "system".equals(table) ? Settings.System.CONTENT_URI - : "secure".equals(table) ? Settings.Secure.CONTENT_URI - : "global".equals(table) ? Settings.Global.CONTENT_URI - : null; - final ArrayList<String> lines = new ArrayList<String>(); - if (uri == null) { - return lines; - } - try { - final Cursor cursor = provider.query(resolveCallingPackage(), uri, null, null, null, - null, null); - try { - while (cursor != null && cursor.moveToNext()) { - lines.add(cursor.getString(1) + "=" + cursor.getString(2)); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - Collections.sort(lines); - } catch (RemoteException e) { - System.err.println("List failed in " + table + " for user " + userHandle); - } - return lines; - } - - private String nextArg() { - if (mNextArg >= mArgs.length) { - return null; - } - String arg = mArgs[mNextArg]; - mNextArg++; - return arg; - } - - String getForUser(IContentProvider provider, int userHandle, - final String table, final String key) { - final String callGetCommand; - if ("system".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_SYSTEM; - else if ("secure".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_SECURE; - else if ("global".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_GLOBAL; - else { - System.err.println("Invalid table; no put performed"); - throw new IllegalArgumentException("Invalid table " + table); - } - - String result = null; - try { - Bundle arg = new Bundle(); - arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle); - Bundle b = provider.call(resolveCallingPackage(), callGetCommand, key, arg); - if (b != null) { - result = b.getPairValue(); - } - } catch (RemoteException e) { - System.err.println("Can't read key " + key + " in " + table + " for user " + userHandle); - } - return result; - } - - void putForUser(IContentProvider provider, int userHandle, - final String table, final String key, final String value) { - final String callPutCommand; - if ("system".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_SYSTEM; - else if ("secure".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_SECURE; - else if ("global".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_GLOBAL; - else { - System.err.println("Invalid table; no put performed"); - return; - } - - try { - Bundle arg = new Bundle(); - arg.putString(Settings.NameValueTable.VALUE, value); - arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle); - provider.call(resolveCallingPackage(), callPutCommand, key, arg); - } catch (RemoteException e) { - System.err.println("Can't set key " + key + " in " + table + " for user " + userHandle); - } - } - - int deleteForUser(IContentProvider provider, int userHandle, - final String table, final String key) { - Uri targetUri; - if ("system".equals(table)) targetUri = Settings.System.getUriFor(key); - else if ("secure".equals(table)) targetUri = Settings.Secure.getUriFor(key); - else if ("global".equals(table)) targetUri = Settings.Global.getUriFor(key); - else { - System.err.println("Invalid table; no delete performed"); - throw new IllegalArgumentException("Invalid table " + table); - } - - int num = 0; - try { - num = provider.delete(resolveCallingPackage(), targetUri, null, null); - } catch (RemoteException e) { - System.err.println("Can't clear key " + key + " in " + table + " for user " - + userHandle); - } - return num; - } - - private static void printUsage() { - System.err.println("usage: settings [--user <USER_ID> | current] get namespace key"); - System.err.println(" settings [--user <USER_ID> | current] put namespace key value"); - System.err.println(" settings [--user <USER_ID> | current] delete namespace key"); - System.err.println(" settings [--user <USER_ID> | current] list namespace"); - System.err.println("\n'namespace' is one of {system, secure, global}, case-insensitive"); - System.err.println("If '--user <USER_ID> | current' is not given, the operations are " - + "performed on the system user."); - } - - public static String resolveCallingPackage() { - switch (android.os.Process.myUid()) { - case Process.ROOT_UID: { - return "root"; - } - - case Process.SHELL_UID: { - return "com.android.shell"; - } - - default: { - return null; - } - } - } -} diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java index d527ad73b787..658d662de5e1 100644 --- a/cmds/sm/src/com/android/commands/sm/Sm.java +++ b/cmds/sm/src/com/android/commands/sm/Sm.java @@ -20,7 +20,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.storage.DiskInfo; -import android.os.storage.IMountService; +import android.os.storage.IStorageManager; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.util.Log; @@ -28,7 +28,7 @@ import android.util.Log; public final class Sm { private static final String TAG = "Sm"; - IMountService mSm; + IStorageManager mSm; private String[] mArgs; private int mNextArg; @@ -55,7 +55,7 @@ public final class Sm { throw new IllegalArgumentException(); } - mSm = IMountService.Stub.asInterface(ServiceManager.getService("mount")); + mSm = IStorageManager.Stub.asInterface(ServiceManager.getService("mount")); if (mSm == null) { throw new RemoteException("Failed to find running mount service"); } @@ -92,6 +92,10 @@ public final class Sm { runSetEmulateFbe(); } else if ("get-fbe-mode".equals(op)) { runGetFbeMode(); + } else if ("fstrim".equals(op)) { + runFstrim(); + } else if ("set-virtual-disk".equals(op)) { + runSetVirtualDisk(); } else { throw new IllegalArgumentException(); } @@ -210,7 +214,7 @@ public final class Sm { mSm.benchmark(volId); } - public void runForget() throws RemoteException{ + public void runForget() throws RemoteException { final String fsUuid = nextArg(); if ("all".equals(fsUuid)) { mSm.forgetAllVolumes(); @@ -219,6 +223,16 @@ public final class Sm { } } + public void runFstrim() throws RemoteException { + mSm.fstrim(0); + } + + public void runSetVirtualDisk() throws RemoteException { + final boolean virtualDisk = Boolean.parseBoolean(nextArg()); + mSm.setDebugFlags(virtualDisk ? StorageManager.DEBUG_VIRTUAL_DISK : 0, + StorageManager.DEBUG_VIRTUAL_DISK); + } + private String nextArg() { if (mNextArg >= mArgs.length) { return null; @@ -234,12 +248,14 @@ public final class Sm { System.err.println(" sm has-adoptable"); System.err.println(" sm get-primary-storage-uuid"); System.err.println(" sm set-force-adoptable [true|false]"); + System.err.println(" sm set-virtual-disk [true|false]"); System.err.println(""); System.err.println(" sm partition DISK [public|private|mixed] [ratio]"); System.err.println(" sm mount VOLUME"); System.err.println(" sm unmount VOLUME"); System.err.println(" sm format VOLUME"); System.err.println(" sm benchmark VOLUME"); + System.err.println(" sm fstrim"); System.err.println(""); System.err.println(" sm forget [UUID|all]"); System.err.println(""); diff --git a/cmds/svc/src/com/android/commands/svc/PowerCommand.java b/cmds/svc/src/com/android/commands/svc/PowerCommand.java index 6ce29cb24bc6..920a52dad641 100644 --- a/cmds/svc/src/com/android/commands/svc/PowerCommand.java +++ b/cmds/svc/src/com/android/commands/svc/PowerCommand.java @@ -22,6 +22,7 @@ import android.os.IPowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.SystemProperties; public class PowerCommand extends Svc.Command { public PowerCommand() { @@ -87,7 +88,7 @@ public class PowerCommand extends Svc.Command { // no confirm, wait till device is rebooted pm.reboot(false, mode, true); } catch (RemoteException e) { - System.err.println("Failed to reboot."); + maybeLogRemoteException("Failed to reboot."); } return; } else if ("shutdown".equals(args[1])) { @@ -95,7 +96,7 @@ public class PowerCommand extends Svc.Command { // no confirm, wait till device is off pm.shutdown(false, null, true); } catch (RemoteException e) { - System.err.println("Failed to shutdown."); + maybeLogRemoteException("Failed to shutdown."); } return; } @@ -103,4 +104,14 @@ public class PowerCommand extends Svc.Command { } System.err.println(longHelp()); } + + // Check if remote exception is benign during shutdown. Pm can be killed + // before system server during shutdown, so remote exception can be ignored + // if it is already in shutdown flow. + private void maybeLogRemoteException(String msg) { + String powerProp = SystemProperties.get("sys.powerctl"); + if (powerProp.isEmpty()) { + System.err.println(msg); + } + } } diff --git a/cmds/uiautomator/library/Android.mk b/cmds/uiautomator/library/Android.mk index 536cbef41feb..4bf856f8cbfa 100644 --- a/cmds/uiautomator/library/Android.mk +++ b/cmds/uiautomator/library/Android.mk @@ -49,7 +49,7 @@ LOCAL_DROIDDOC_OPTIONS:= \ -api $(uiautomator_internal_api_file) \ -removedApi $(uiautomator_internal_removed_api_file) -LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR := build/tools/droiddoc/templates-sdk +LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR := external/doclava/res/assets/templates-sdk LOCAL_UNINSTALLABLE_MODULE := true LOCAL_MODULE := uiautomator-stubs diff --git a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java index cb39e375d307..653851546d01 100644 --- a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java +++ b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java @@ -16,10 +16,11 @@ package com.android.uiautomator.core; -import android.app.ActivityManagerNative; +import android.app.ActivityManager; +import android.app.ContentProviderHolder; import android.app.IActivityManager; -import android.app.IActivityManager.ContentProviderHolder; import android.app.UiAutomation; +import android.content.ContentResolver; import android.content.Context; import android.content.IContentProvider; import android.database.Cursor; @@ -56,7 +57,7 @@ public class ShellUiAutomatorBridge extends UiAutomatorBridge { try { IContentProvider provider = null; Cursor cursor = null; - IActivityManager activityManager = ActivityManagerNative.getDefault(); + IActivityManager activityManager = ActivityManager.getService(); String providerName = Settings.Secure.CONTENT_URI.getAuthority(); IBinder token = new Binder(); try { @@ -69,10 +70,12 @@ public class ShellUiAutomatorBridge extends UiAutomatorBridge { cursor = provider.query(null, Settings.Secure.CONTENT_URI, new String[] { Settings.Secure.VALUE - }, "name=?", - new String[] { - Settings.Secure.LONG_PRESS_TIMEOUT - }, null, null); + }, + ContentResolver.createSqlQueryBundle( + "name=?", + new String[] { Settings.Secure.LONG_PRESS_TIMEOUT }, + null), + null); if (cursor.moveToFirst()) { longPressTimeout = cursor.getInt(0); } @@ -98,7 +101,7 @@ public class ShellUiAutomatorBridge extends UiAutomatorBridge { IWindowManager.Stub.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE)); int ret = -1; try { - ret = wm.getRotation(); + ret = wm.getDefaultDisplayRotation(); } catch (RemoteException e) { Log.e(LOG_TAG, "Error getting screen rotation", e); throw new RuntimeException(e); diff --git a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java index 19aa11a3b1aa..71561c3c7023 100644 --- a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java +++ b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java @@ -2,7 +2,6 @@ package com.android.uiautomator.core; import android.accessibilityservice.AccessibilityServiceInfo; import android.app.ActivityManager; -import android.app.ActivityManagerNative; import android.app.IActivityController; import android.app.IActivityManager; import android.app.UiAutomation; @@ -44,7 +43,7 @@ public class UiAutomationShellWrapper { * @see ActivityManager#isUserAMonkey() */ public void setRunAsMonkey(boolean isSet) { - IActivityManager am = ActivityManagerNative.getDefault(); + IActivityManager am = ActivityManager.getService(); if (am == null) { throw new RuntimeException("Can't manage monkey status; is the system running?"); } diff --git a/cmds/vr/Android.mk b/cmds/vr/Android.mk new file mode 100644 index 000000000000..d0dc25a36dc8 --- /dev/null +++ b/cmds/vr/Android.mk @@ -0,0 +1,15 @@ +# Copyright 2017 The Android Open Source Project +# +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_MODULE := vr +include $(BUILD_JAVA_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := vr +LOCAL_SRC_FILES := vr +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MODULE_TAGS := optional +include $(BUILD_PREBUILT) diff --git a/cmds/vr/MODULE_LICENSE_APACHE2 b/cmds/vr/MODULE_LICENSE_APACHE2 new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/cmds/vr/MODULE_LICENSE_APACHE2 diff --git a/cmds/vr/NOTICE b/cmds/vr/NOTICE new file mode 100644 index 000000000000..25f8ab95ce61 --- /dev/null +++ b/cmds/vr/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2017, The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/cmds/vr/src/com/android/commands/vr/Vr.java b/cmds/vr/src/com/android/commands/vr/Vr.java new file mode 100644 index 000000000000..b765866faef9 --- /dev/null +++ b/cmds/vr/src/com/android/commands/vr/Vr.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.commands.vr; + +import android.app.Vr2dDisplayProperties; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; + +import android.service.vr.IVrManager; +import com.android.internal.os.BaseCommand; + +import java.io.PrintStream; + +public final class Vr extends BaseCommand { + + /** + * Command-line entry point. + * + * @param args The command-line arguments + */ + public static void main(String[] args) { + (new Vr()).run(args); + } + + private static final String COMMAND_SET_PERSISTENT_VR_MODE_ENABLED = + "set-persistent-vr-mode-enabled"; + private static final String COMMAND_SET_VR2D_DISPLAY_PROPERTIES = + "set-display-props"; + + private IVrManager mVrService; + + @Override + public void onShowUsage(PrintStream out) { + out.println( + "usage: vr [subcommand]\n" + + "usage: vr set-persistent-vr-mode-enabled [true|false]\n" + + "usage: vr set-display-props [width] [height] [dpi]\n" + ); + } + + @Override + public void onRun() throws Exception { + mVrService = IVrManager.Stub.asInterface(ServiceManager.getService(Context.VR_SERVICE)); + if (mVrService == null) { + showError("Error: Could not access the Vr Manager. Is the system running?"); + return; + } + + String command = nextArgRequired(); + switch (command) { + case COMMAND_SET_VR2D_DISPLAY_PROPERTIES: + runSetVr2dDisplayProperties(); + break; + case COMMAND_SET_PERSISTENT_VR_MODE_ENABLED: + runSetPersistentVrModeEnabled(); + break; + default: + throw new IllegalArgumentException ("unknown command '" + command + "'"); + } + } + + private void runSetVr2dDisplayProperties() throws RemoteException { + String widthStr = nextArgRequired(); + int width = Integer.parseInt(widthStr); + + String heightStr = nextArgRequired(); + int height = Integer.parseInt(heightStr); + + String dpiStr = nextArgRequired(); + int dpi = Integer.parseInt(dpiStr); + + Vr2dDisplayProperties vr2dDisplayProperties = + new Vr2dDisplayProperties(width, height, dpi); + + try { + mVrService.setVr2dDisplayProperties(vr2dDisplayProperties); + } catch (RemoteException re) { + System.err.println("Error: Can't set persistent mode " + re); + } + } + + private void runSetPersistentVrModeEnabled() throws RemoteException { + String enableStr = nextArg(); + boolean enabled = Boolean.parseBoolean(enableStr); + try { + mVrService.setPersistentVrModeEnabled(enabled); + } catch (RemoteException re) { + System.err.println("Error: Can't set persistent mode " + re); + } + } +} diff --git a/cmds/vr/vr b/cmds/vr/vr new file mode 100755 index 000000000000..a279007caed4 --- /dev/null +++ b/cmds/vr/vr @@ -0,0 +1,6 @@ +# Script to start "vr" on the device +# +base=/system +export CLASSPATH=$base/framework/vr.jar +exec app_process $base/bin com.android.commands.vr.Vr "$@" + diff --git a/cmds/wm/src/com/android/commands/wm/Wm.java b/cmds/wm/src/com/android/commands/wm/Wm.java index 383cd01ddcd6..8defb331e289 100644 --- a/cmds/wm/src/com/android/commands/wm/Wm.java +++ b/cmds/wm/src/com/android/commands/wm/Wm.java @@ -21,16 +21,22 @@ package com.android.commands.wm; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.util.AndroidException; import android.util.DisplayMetrics; +import android.system.Os; import android.view.Display; import android.view.IWindowManager; import com.android.internal.os.BaseCommand; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.DataInputStream; import java.io.PrintStream; +import java.lang.Runtime; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -69,7 +75,9 @@ public class Wm extends BaseCommand { "wm screen-capture: enable/disable screen capture.\n" + "\n" + "wm dismiss-keyguard: dismiss the keyguard, prompting the user for auth if " + - "necessary.\n" + "necessary.\n" + + "\n" + + "wm surface-trace: log surface commands to stdout in a binary format.\n" ); } @@ -96,12 +104,29 @@ public class Wm extends BaseCommand { runSetScreenCapture(); } else if (op.equals("dismiss-keyguard")) { runDismissKeyguard(); + } else if (op.equals("surface-trace")) { + runSurfaceTrace(); } else { showError("Error: unknown command '" + op + "'"); return; } } + private void runSurfaceTrace() throws Exception { + ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(FileDescriptor.out); + mWm.enableSurfaceTrace(pfd); + + try { + // No one is going to wake us up, we are just waiting on SIGINT. Otherwise + // the WM can happily continue writing to our stdout. + synchronized (this) { + this.wait(); + } + } finally { + mWm.disableSurfaceTrace(); + } + } + private void runSetScreenCapture() throws Exception { String userIdStr = nextArg(); String enableStr = nextArg(); @@ -249,7 +274,7 @@ public class Wm extends BaseCommand { } private void runDismissKeyguard() throws Exception { - mWm.dismissKeyguard(); + mWm.dismissKeyguard(null /* callback */); } private int parseDimension(String s) throws NumberFormatException { |