1 /*
2  * Copyright 2020 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.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