1 /*
2  * Copyright (C) 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 com.android.nn.crashtest.core;
18 
19 import android.app.Service;
20 import android.content.Intent;
21 import android.os.Bundle;
22 import android.os.Handler;
23 import android.os.IBinder;
24 import android.os.Message;
25 import android.os.Messenger;
26 import android.os.RemoteException;
27 import android.util.Log;
28 
29 import java.util.Objects;
30 import java.util.Optional;
31 import java.util.concurrent.CountDownLatch;
32 import java.util.concurrent.ExecutorService;
33 import java.util.concurrent.Executors;
34 import java.util.concurrent.TimeUnit;
35 
36 public class CrashTestService extends Service {
37 
38     public static final String TAG = "CrashTestService";
39     public static final String DESCRIPTION = "description";
40     public static final String EXTRA_KEY_CRASH_TEST_CLASS = "crash_test_class_name";
41 
42     public static final int SUCCESS = 1;
43     public static final int FAILURE = 2;
44     public static final int PROGRESS = 3;
45     public static final int SET_COMM_CHANNEL = 4;
46     public static final int KILL_PROCESS = 5;
47 
48     // Starting tests only after the crash test coordinator has set the communication
49     // channel to me in order to avoid event notifications
50     private final CountDownLatch startTest = new CountDownLatch(1);
51 
52     Messenger lifecycleListener = null;
53     final Messenger mMessenger = new Messenger(new Handler(message -> {
54         switch (message.what) {
55             case SET_COMM_CHANNEL:
56                 lifecycleListener = message.replyTo;
57                 startTest.countDown();
58                 break;
59 
60             case KILL_PROCESS:
61                 Log.w(TAG, "Shutting down service!");
62                 System.exit(-1);
63         }
64 
65         return true;
66     }));
67 
68     final ExecutorService executor = Executors.newSingleThreadExecutor();
69 
notify(int messageType, String messageBody)70     private void notify(int messageType, String messageBody) {
71         if (lifecycleListener == null) {
72             Log.e(TAG, "No listener configured, skipping message " + messageType);
73             return;
74         }
75         try {
76             final Message message = Message.obtain(null, messageType);
77             if (messageBody != null) {
78                 Bundle data = new Bundle();
79                 data.putString(DESCRIPTION, messageBody);
80                 message.setData(data);
81             }
82             lifecycleListener.send(message);
83         } catch (RemoteException e) {
84             Log.e(TAG, "Exception sending message", e);
85         }
86     }
87 
88     @Override
onBind(Intent intent)89     public IBinder onBind(Intent intent) {
90         Log.d(TAG, "Service is bound");
91 
92         try {
93             String testClassName = Objects
94                     .requireNonNull(intent.getStringExtra(EXTRA_KEY_CRASH_TEST_CLASS));
95             Log.v(TAG, "Instantiating test class name '" + testClassName + "'");
96             final CrashTest crashTest = (CrashTest) Class.forName(
97                     testClassName).newInstance();
98             crashTest.init(getApplicationContext(), intent,
99                     Optional.of(messageMaybe -> notify(PROGRESS, messageMaybe.orElse(null))));
100 
101             Log.i(TAG, "Starting test");
102 
103             executor.submit(() -> {
104                 try {
105                     startTest.await(3, TimeUnit.SECONDS);
106                 } catch (InterruptedException e) {
107                     Thread.interrupted();
108                     Log.e(TAG, "Interrupted before starting test", e);
109                     stopSelf();
110                     return;
111                 }
112 
113                 try {
114                     final Optional<String> testResult = crashTest.call();
115                     Log.d(TAG, String.format("Test '%s' completed with result: %s", testClassName,
116                             testResult.orElse("success")));
117                     notify(testResult.isPresent() ? FAILURE : SUCCESS, testResult.orElse(null));
118                 } catch (Throwable e) {
119                     Log.e(TAG, "Exception in crash test", e);
120                     notify(FAILURE, "Exception in crash test: " + e);
121                     stopSelf();
122                 }
123             });
124         } catch (Exception e) {
125             Log.e(TAG, "Exception starting test ", e);
126             stopSelf();
127         } catch (Error error) {
128             Log.e(TAG, "Error starting test ", error);
129             throw error;
130         }
131 
132         return mMessenger.getBinder();
133     }
134 
135     @Override
onUnbind(Intent intent)136     public boolean onUnbind(Intent intent) {
137         Log.i(TAG, "Unbinding, shutting down thread pool ");
138         executor.shutdown();
139         return false;
140     }
141 }
142