1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.inputmethodservice.cts.devicetest; 18 19 import android.os.Bundle; 20 import android.os.Handler; 21 import android.os.HandlerThread; 22 import android.os.IBinder; 23 import android.os.Looper; 24 import android.os.ParcelFileDescriptor; 25 import android.os.ResultReceiver; 26 27 import java.io.BufferedReader; 28 import java.io.FileDescriptor; 29 import java.io.IOException; 30 import java.io.InputStreamReader; 31 import java.lang.reflect.Method; 32 import java.util.StringJoiner; 33 34 /** 35 * A utility class to invoke a hidden API {@code IBinder#shellCommand(FileDescriptor in, 36 * FileDescriptor out, FileDescriptor err, String[] args, ShellCallback shellCallback, 37 * ResultReceiver resultReceiver)} via reflection for any the system server. 38 */ 39 class DirectShellCommand { 40 /** 41 * A {@link java.util.concurrent.Future} style result object. 42 */ 43 @FunctionalInterface 44 public interface FutureResult { 45 /** 46 * @param mills timeout in milliseconds. 47 * @return Non-{@code null} object is the {@link Result} becomes available within timeout. 48 * Otherwise {@code null}. 49 */ 50 Result get(long mills); 51 } 52 53 /** 54 * Represents general information about invocation result. 55 */ 56 public static final class Result { 57 private final int mCode; 58 private final Bundle mData; 59 private final String mString; 60 private final Exception mException; 61 62 private Result(int code, Bundle data, String string, Exception exception) { 63 mCode = code; 64 mData = data; 65 mString = string; 66 mException = exception; 67 } 68 69 /** 70 * @return {@code 0} when the command is completed successfully. 71 */ 72 public int getCode() { 73 return mCode; 74 } 75 76 /** 77 * @return {@link Bundle} returned from the system server as a response. 78 */ 79 public Bundle getData() { 80 return mData; 81 } 82 83 /** 84 * @return Console output from the system server. 85 */ 86 public String getString() { 87 return mString; 88 } 89 90 /** 91 * @return Any {@link Exception} thrown during the invocation. 92 */ 93 public Exception getException() { 94 return mException; 95 } 96 } 97 98 private static final class MyResultReceiver extends ResultReceiver { 99 private final ParcelFileDescriptor mInputFileDescriptor; 100 private final Object mLock = new Object(); 101 102 private Result mResult; 103 104 private static Looper createBackgroundLooper() { 105 final HandlerThread handlerThread = new HandlerThread("ShellCommandResultReceiver"); 106 handlerThread.start(); 107 return handlerThread.getLooper(); 108 } 109 110 MyResultReceiver(ParcelFileDescriptor inputFileDescriptor) { 111 super(new Handler(createBackgroundLooper())); 112 mInputFileDescriptor = inputFileDescriptor; 113 } 114 115 @Override 116 protected void onReceiveResult(int resultCode, Bundle resultData) { 117 synchronized (mLock) { 118 final StringJoiner joiner = new StringJoiner("\n"); 119 Exception resultException = null; 120 // Note that system server is expected to be using Charset.defaultCharset() hence 121 // we do not set the encoding here. 122 try (BufferedReader reader = new BufferedReader(new InputStreamReader( 123 new ParcelFileDescriptor.AutoCloseInputStream(mInputFileDescriptor)))) { 124 while (true) { 125 final String line = reader.readLine(); 126 if (line == null) { 127 break; 128 } 129 joiner.add(line); 130 } 131 } catch (IOException e) { 132 resultException = e; 133 } 134 135 mResult = new Result(resultCode, resultData, joiner.toString(), resultException); 136 mLock.notifyAll(); 137 Looper.myLooper().quitSafely(); 138 } 139 } 140 141 Result getResult(long millis) { 142 synchronized (mLock) { 143 if (mResult == null) { 144 try { 145 mLock.wait(millis); 146 } catch (InterruptedException e) { 147 } 148 } 149 return mResult; 150 } 151 } 152 } 153 154 private static ParcelFileDescriptor createEmptyInput() throws IOException { 155 final ParcelFileDescriptor[] pipeFds = ParcelFileDescriptor.createReliablePipe(); 156 pipeFds[1].close(); 157 return pipeFds[0]; 158 } 159 160 private static FutureResult run(String serviceName, String[] args) throws Exception { 161 final Class<?> serviceManagerClass = Class.forName("android.os.ServiceManager"); 162 final Method getService = serviceManagerClass.getMethod("getService", String.class); 163 final IBinder service = (IBinder) getService.invoke(null, serviceName); 164 165 final Class<?> shellCallbackClass = Class.forName("android.os.ShellCallback"); 166 final Method shellCommand = IBinder.class.getMethod("shellCommand", 167 FileDescriptor.class, FileDescriptor.class, FileDescriptor.class, 168 String[].class, shellCallbackClass, ResultReceiver.class); 169 shellCommand.setAccessible(true); 170 final ParcelFileDescriptor[] pipeFds = ParcelFileDescriptor.createReliablePipe(); 171 final MyResultReceiver resultReceiver = new MyResultReceiver(pipeFds[0]); 172 try (ParcelFileDescriptor emptyInput = createEmptyInput(); 173 ParcelFileDescriptor out = pipeFds[1]; 174 ParcelFileDescriptor err = out.dup()) { 175 shellCommand.invoke(service, emptyInput.getFileDescriptor(), 176 out.getFileDescriptor(), err.getFileDescriptor(), args, null, resultReceiver); 177 } 178 return resultReceiver::getResult; 179 } 180 181 /** 182 * Synchronously invoke {@code IBinder#shellCommand()} then return the result. 183 * 184 * @param serviceName internal system service name, e.g. 185 * {@link android.content.Context#INPUT_METHOD_SERVICE}. 186 * @param args commands to be passedto the system service. 187 * @param millis timeout in milliseconds. 188 * @return Non-{@code null} object if the command is not timed out. Otherwise {@code null}. 189 */ 190 static Result runSync(String serviceName, String[] args, long millis) { 191 try { 192 return run(serviceName, args).get(millis); 193 } catch (Exception e) { 194 return new Result(-1, null, null, e); 195 } 196 } 197 } 198