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