1 /* 2 * Copyright (C) 2022 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.media.bettertogether.cts; 18 19 import static org.junit.Assert.assertTrue; 20 21 import static java.util.concurrent.TimeUnit.MILLISECONDS; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.app.Service; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.ServiceConnection; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.HandlerThread; 33 import android.os.IBinder; 34 import android.os.RemoteException; 35 import android.util.Log; 36 37 import java.io.Closeable; 38 import java.util.concurrent.CountDownLatch; 39 import java.util.concurrent.atomic.AtomicReference; 40 41 /** 42 * Base class for a service that runs on a remote process. The service that extends this class must 43 * be added to AndroidManifest.xml with "android:process" attribute to be run on a separate process. 44 */ 45 public abstract class RemoteService extends Service { 46 private static final String TAG = "RemoteService"; 47 public static final long TIMEOUT_MS = 10_000; 48 49 private RemoteServiceStub mBinder; 50 private HandlerThread mHandlerThread; 51 private volatile Handler mHandler; 52 53 @Override onCreate()54 public void onCreate() { 55 mBinder = new RemoteServiceStub(); 56 mHandlerThread = new HandlerThread(TAG); 57 mHandlerThread.start(); 58 mHandler = new Handler(mHandlerThread.getLooper()); 59 } 60 61 @Override onDestroy()62 public void onDestroy() { 63 mHandlerThread.quitSafely(); 64 } 65 66 @Override onBind(Intent intent)67 public IBinder onBind(Intent intent) { 68 return mBinder; 69 } 70 71 /** 72 * Called by {@link Invoker#run}. It will be run on a dedicated {@link HandlerThread}. 73 * 74 * @param testId id of the test case 75 * @param step the step of a command to run 76 * @param args optional arguments 77 * @throws Exception if any 78 */ onRun(int testId, int step, @Nullable Bundle args)79 public abstract void onRun(int testId, int step, @Nullable Bundle args) throws Exception; 80 runOnHandlerSync(TestRunnable runnable)81 private boolean runOnHandlerSync(TestRunnable runnable) { 82 CountDownLatch latch = new CountDownLatch(1); 83 AtomicReference<Throwable> throwable = new AtomicReference<>(); 84 mHandler.post(() -> { 85 try { 86 runnable.run(); 87 } catch (Throwable th) { 88 throwable.set(th); 89 Log.e(TAG, "Error while running TestRunnable", th); 90 } 91 latch.countDown(); 92 }); 93 try { 94 boolean done = latch.await(TIMEOUT_MS, MILLISECONDS); 95 return done && throwable.get() == null; 96 } catch (InterruptedException ex) { 97 Log.w(TAG, ex); 98 return false; 99 } 100 } 101 102 private interface TestRunnable { run()103 void run() throws Exception; 104 } 105 106 private class RemoteServiceStub extends IRemoteService.Stub { 107 @Override run(int testId, int step, Bundle args)108 public boolean run(int testId, int step, Bundle args) throws RemoteException { 109 return runOnHandlerSync(() -> onRun(testId, step, args)); 110 } 111 } 112 113 /** 114 * A class to run commands on a {@link RemoteService} for a test case. 115 */ 116 public static class Invoker implements Closeable { 117 private static final String ASSERTION_MESSAGE = 118 "Failed on remote service. See logcat TAG=" + TAG + " for detail."; 119 120 private final Context mContext; 121 private final int mTestId; 122 private final CountDownLatch mConnectionLatch; 123 private final ServiceConnection mServiceConnection; 124 private IRemoteService mBinder; 125 126 /** 127 * Creates an instance and connects to the remote service. 128 * 129 * @param context the context 130 * @param serviceClass the class of remote service 131 * @param testId id of the test case 132 * @throws InterruptedException if the thread is interrupted while waiting for connection 133 */ Invoker(@onNull Context context, @NonNull Class<? extends RemoteService> serviceClass, int testId)134 public Invoker(@NonNull Context context, 135 @NonNull Class<? extends RemoteService> serviceClass, int testId) 136 throws InterruptedException { 137 mContext = context; 138 mTestId = testId; 139 mConnectionLatch = new CountDownLatch(1); 140 mServiceConnection = new ServiceConnection() { 141 @Override 142 public void onServiceConnected(ComponentName name, IBinder service) { 143 mBinder = IRemoteService.Stub.asInterface(service); 144 mConnectionLatch.countDown(); 145 } 146 147 @Override 148 public void onServiceDisconnected(ComponentName name) { 149 mBinder = null; 150 } 151 }; 152 153 Intent intent = new Intent(mContext, serviceClass); 154 mContext.bindService(intent, mServiceConnection, BIND_AUTO_CREATE); 155 assertTrue("Failed to bind to service " + serviceClass, 156 mConnectionLatch.await(TIMEOUT_MS, MILLISECONDS)); 157 } 158 159 /** 160 * Disconnects from the remote service. 161 */ 162 @Override close()163 public void close() { 164 mContext.unbindService(mServiceConnection); 165 } 166 167 /** 168 * Invokes {@link #onRun} on the remote service without optional arguments. 169 * 170 * @param step the step of a command to run 171 * @throws RemoteException if binder throws exception 172 */ run(int step)173 public void run(int step) throws RemoteException { 174 run(step, null); 175 } 176 177 /** 178 * Invokes {@link #onRun} on the remote service. 179 * 180 * @param step the step of a command to run 181 * @param args optional arguments 182 * @throws RemoteException if binder throws exception 183 */ run(int step, @Nullable Bundle args)184 public void run(int step, @Nullable Bundle args) throws RemoteException { 185 assertTrue(ASSERTION_MESSAGE, mBinder.run(mTestId, step, args)); 186 } 187 } 188 } 189