• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.cts.servicekilltestapp;
18 
19 import android.annotation.SuppressLint;
20 import android.app.AlarmManager;
21 import android.app.Notification;
22 import android.app.NotificationChannel;
23 import android.app.NotificationManager;
24 import android.app.PendingIntent;
25 import android.app.Service;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.os.AsyncTask;
31 import android.os.Build;
32 import android.os.Handler;
33 import android.os.IBinder;
34 import android.os.PowerManager;
35 import android.util.Log;
36 import android.os.SystemClock;
37 
38 import java.io.ByteArrayOutputStream;
39 import java.io.FileNotFoundException;
40 import java.io.FileOutputStream;
41 import java.io.IOException;
42 import java.io.ObjectInputStream;
43 import java.io.ObjectOutputStream;
44 import java.io.Serializable;
45 import java.util.ArrayList;
46 import java.util.HashMap;
47 import java.util.Iterator;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.TimeZone;
51 import java.util.concurrent.CancellationException;
52 import java.util.concurrent.ExecutorService;
53 import java.util.concurrent.ScheduledFuture;
54 import java.util.concurrent.Executors;
55 import java.util.concurrent.ScheduledExecutorService;
56 import java.util.concurrent.TimeUnit;
57 
58 public class ServiceKillTestService extends Service {
59 
60     /**
61      * Execution times for each measure
62      */
63     public static final long HOUR_IN_MS = TimeUnit.HOURS.toMillis(1);
64     public static final long ALARM_REPEAT_MS = TimeUnit.MINUTES.toMillis(10);
65     public static final long ALARM_REPEAT_MARGIN_MS = TimeUnit.SECONDS.toMillis(30);
66     public static final long PERSIST_BENCHMARK_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30);
67     public static final long WORK_REPEAT_MS = TimeUnit.SECONDS.toMillis(10);
68     public static final long MAIN_REPEAT_MS = TimeUnit.SECONDS.toMillis(10);
69 
70     /**
71      * Actions and extras
72      */
73     public static final String TEST_CASE_PACKAGE_NAME = "com.android.cts.servicekilltest";
74     public static final String TEST_APP_PACKAGE_NAME = TEST_CASE_PACKAGE_NAME + "app";
75     public static final String ACTION_START = TEST_CASE_PACKAGE_NAME + ".ACTION_START";
76     public static final String ACTION_STOP = TEST_CASE_PACKAGE_NAME + ".ACTION_STOP";
77     public static final String ACTION_RESULT = TEST_CASE_PACKAGE_NAME + ".ACTION_RESULT";
78     private static final String ACTION_ALARM = TEST_CASE_PACKAGE_NAME + ".ACTION_ALARM";
79     public static final String EXTRA_TEST_ID = "test_id";
80 
81 
82     public static final String APP = "CTSServiceKillTest";
83     public static final String TAG = "ServiceKillTest";
84 
85     public static String NOTIFICATION_CHANNEL_FOREGROUND = "foreground";
86 
87     private PowerManager.WakeLock mWakeLock;
88     private Handler mHandler;
89     private ScheduledExecutorService mScheduledExecutor;
90     private ExecutorService mExecutor;
91 
92 
93     private Benchmark mCurrentBenchmark;
94 
95     private boolean mStarted = false;
96 
97     private ScheduledFuture<?> mScheduledFuture;
98 
99     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
100 
101     private BroadcastReceiver mAlarmReceiver = new BroadcastReceiver() {
102 
103         @Override
104         public void onReceive(Context context, Intent intent) {
105             if (intent != null && ACTION_ALARM.equals(intent.getAction())) {
106                 logDebug("Alarm");
107                 mCurrentBenchmark.addEvent(Benchmark.Measure.ALARM, SystemClock.elapsedRealtime());
108                 scheduleAlarm();
109                 saveBenchmarkIfRequired(mCurrentBenchmark);
110             }
111 
112         }
113     };
114 
115     private Runnable mMainRunnable = new Runnable() {
116         @Override
117         public void run() {
118             logDebug("Main");
119             if (mWakeLock.isHeld()) {
120                 mCurrentBenchmark.addEvent(Benchmark.Measure.MAIN, SystemClock.elapsedRealtime());
121                 saveBenchmarkIfRequired(mCurrentBenchmark);
122             } else {
123                 Log.w(TAG, "Wake lock broken");
124             }
125             mHandler.postDelayed(this, MAIN_REPEAT_MS);
126         }
127     };
128 
129     @Override
onBind(Intent intent)130     public IBinder onBind(Intent intent) {
131         return null;
132     }
133 
134 
135     @Override
onCreate()136     public void onCreate() {
137         super.onCreate();
138         logDebug("onCreate()");
139         PowerManager powerManager = getSystemService(PowerManager.class);
140         mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, APP + "::" + TAG);
141         mWakeLock.acquire();
142         mExecutor = Executors.newSingleThreadExecutor();
143         mHandler = new Handler();
144         startForeground();
145         loadBenchmarkAsync(benchmark -> {
146             logDebug("Loading Benchmark " + benchmark);
147 
148             if (benchmark == null || !benchmark.isRunning()) {
149                 mCurrentBenchmark = new Benchmark();
150                 logDebug("New Benchmark " + mCurrentBenchmark);
151             } else {
152                 mCurrentBenchmark = benchmark;
153             }
154             startBenchmark();
155         });
156 
157     }
158 
getTestId(Intent i)159     private String getTestId(Intent i) {
160         return i == null ? null : i.getStringExtra(EXTRA_TEST_ID);
161     }
162 
isAction(Intent i, String action)163     private boolean isAction(Intent i, String action) {
164         if (i != null && action != null) {
165             return action.equals(i.getAction());
166         }
167         return i == null && action == null;
168     }
169 
170     @Override
onStartCommand(Intent intent, int flags, int startId)171     public int onStartCommand(Intent intent, int flags, int startId) {
172         startForeground();
173 
174         final String id = getTestId(intent);
175         if (id != null) {
176             logDebug("onStartCommand TEST " + id + " action " + intent.getAction());
177             mHandler.post(new Runnable() {
178                 @Override
179                 public void run() {
180                     if (mCurrentBenchmark != null) {
181                         if (isAction(intent, ACTION_START)) {
182                             logDebug("Starting TEST " + id);
183                             mCurrentBenchmark.startTest(id);
184                         } else if (isAction(intent, ACTION_STOP)) {
185                             logDebug("Stopping TEST " + id);
186                             sendResult(id, mCurrentBenchmark.finishTest(id));
187 
188                             if (!mCurrentBenchmark.isRunning()) {
189                                 logDebug("No TEST running, stopping benchmark");
190                                 saveBenchmarkAsync(mCurrentBenchmark, () -> {
191                                             logDebug("No TEST running, stopping service");
192                                             stopSelf();
193                                         });
194                             }
195                         } else if (isAction(intent, ACTION_RESULT)) {
196                             logDebug("Getting results for TEST " + id);
197                             sendResult(id, mCurrentBenchmark.getAllResults(id));
198                         }
199                     } else {
200                         mHandler.postDelayed(this, 1000);
201                     }
202                 }
203             });
204         } else {
205             Log.w(TAG, "Ignoring start request without test ID");
206         }
207         return START_STICKY;
208     }
209 
sendResult(String testId, Map<Benchmark.Measure, Float> result)210     private void sendResult(String testId, Map<Benchmark.Measure, Float> result) {
211         logDebug("Sending result");
212         Intent intent = new Intent(ACTION_RESULT);
213         intent.putExtra(EXTRA_TEST_ID, testId);
214         intent.setPackage(TEST_CASE_PACKAGE_NAME);
215         if (result != null) {
216             for (Benchmark.Measure measure : result.keySet()) {
217                 intent.putExtra(measure.name(), result.get(measure));
218                 logDebug("Result " + measure.name() + "=" + result.get(measure));
219             }
220         }
221         sendBroadcast(intent);
222     }
223 
startForeground()224     private void startForeground() {
225         NotificationManager notificationManager = getSystemService(NotificationManager.class);
226 
227         NotificationChannel notificationChannel =
228                 new NotificationChannel(NOTIFICATION_CHANNEL_FOREGROUND, TAG,
229                         NotificationManager.IMPORTANCE_LOW);
230         notificationManager.createNotificationChannel(notificationChannel);
231         startForeground(12, new Notification.Builder(this, NOTIFICATION_CHANNEL_FOREGROUND)
232                 .setSmallIcon(android.R.drawable.ic_media_play)
233                 .setChannelId(NOTIFICATION_CHANNEL_FOREGROUND)
234                 .setContentText("Foreground Service Kill Test Running").build());
235     }
236 
getAlarmIntent()237     private PendingIntent getAlarmIntent() {
238         Intent i = new Intent(ACTION_ALARM);
239         i.setPackage(getPackageName());
240         return PendingIntent.getBroadcast(this, 0, i,
241                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
242     }
243 
scheduleAlarm()244     private void scheduleAlarm() {
245         long alarmTime = SystemClock.elapsedRealtime() + ALARM_REPEAT_MS;
246         logDebug(String.format("Scheduling alarm at %d", alarmTime));
247         AlarmManager alarmManager = getSystemService(AlarmManager.class);
248         if (alarmManager.canScheduleExactAlarms()) {
249             alarmManager.setExactAndAllowWhileIdle(
250                     AlarmManager.ELAPSED_REALTIME_WAKEUP,
251                     alarmTime,
252                     getAlarmIntent()
253             );
254         } else {
255             Log.w(TAG, "Cannot schedule exact alarms");
256         }
257     }
258 
cancelAlarm()259     private void cancelAlarm() {
260         logDebug("Cancel alarm ");
261         AlarmManager alarmManager = getSystemService(AlarmManager.class);
262         alarmManager.cancel(getAlarmIntent());
263     }
264 
265 
startBenchmark()266     private void startBenchmark() {
267         mHandler.post(mMainRunnable);
268         scheduleAlarm();
269         registerReceiver(mAlarmReceiver, new IntentFilter(ACTION_ALARM));
270         mScheduledExecutor = Executors.newScheduledThreadPool(1);
271         mScheduledFuture = mScheduledExecutor.scheduleAtFixedRate(() -> {
272             try {
273                 logDebug("Work");
274                 long now = SystemClock.elapsedRealtime();
275                 mHandler.post(() -> {
276                     if (mWakeLock.isHeld()) {
277                         mCurrentBenchmark.addEvent(Benchmark.Measure.WORK, now);
278                         saveBenchmarkIfRequired(mCurrentBenchmark);
279                     } else {
280                         Log.w(TAG, "Wake lock broken");
281                     }
282                 });
283             } catch (Throwable t) {
284                 Log.e(TAG, "Error in scheduled execution ", t);
285             }
286         }, WORK_REPEAT_MS, WORK_REPEAT_MS, TimeUnit.MILLISECONDS);
287 
288         mStarted = true;
289     }
290 
stopBenchmark()291     private void stopBenchmark() {
292         try {
293             unregisterReceiver(mAlarmReceiver);
294         } catch (Exception e) {
295             Log.w(TAG, "Receiver not registered", e);
296         }
297         cancelAlarm();
298         mHandler.removeCallbacks(mMainRunnable);
299         if (mScheduledExecutor != null) {
300             mScheduledExecutor.shutdown();
301             if (mScheduledFuture.isDone()) {
302                 try {
303                     mScheduledFuture.get();
304                 } catch (CancellationException e) {
305                 } catch (Exception e) {
306                     Log.e(TAG, "Error in scheduled execution ", e);
307                 }
308             }
309         }
310     }
311 
312     @Override
onDestroy()313     public void onDestroy() {
314         super.onDestroy();
315         logDebug("onDestroy()");
316         if (mStarted) {
317             stopBenchmark();
318         }
319         mExecutor.shutdown();
320         mWakeLock.release();
321     }
322 
getServiceIntent(Context context)323     private static Intent getServiceIntent(Context context) {
324         return new Intent(context, ServiceKillTestService.class);
325     }
326 
loadBenchmarkAsync(Consumer<Benchmark> consumer)327     private void loadBenchmarkAsync(Consumer<Benchmark> consumer) {
328         mExecutor.execute(() -> {
329             final Benchmark benchmark = loadBenchmark();
330             mHandler.post(() -> {
331                 consumer.accept(benchmark);
332             });
333         });
334     }
335 
336     public interface Consumer<T> {
accept(T consumable)337         void accept(T consumable);
338     }
339 
loadBenchmark()340     private synchronized Benchmark loadBenchmark() {
341         ObjectInputStream in = null;
342         try {
343             in = new ObjectInputStream(openFileInput(TAG));
344             return (Benchmark) in.readObject();
345         } catch (FileNotFoundException e) {
346             logDebug("File not found");
347         } catch (ClassNotFoundException e) {
348             Log.e(TAG, "Class no found", e);
349         } catch (IOException e) {
350             Log.e(TAG, "I/O error", e);
351         } finally {
352             try {
353                 if (in != null) {
354                     in.close();
355                 }
356             } catch (IOException e) {
357                 Log.e(TAG, "Cannot close benchmark file", e);
358             }
359         }
360         return null;
361     }
362 
363 
clearBenchmark()364     private synchronized void clearBenchmark() {
365         deleteFile(TAG);
366     }
367 
saveBenchmarkIfRequired(Benchmark benchmark)368     private void saveBenchmarkIfRequired(Benchmark benchmark) {
369         if (SystemClock.elapsedRealtime() - benchmark.lastPersisted > PERSIST_BENCHMARK_TIMEOUT_MS) {
370             saveBenchmarkAsync(benchmark, null);
371         }
372     }
373 
374 
saveBenchmarkAsync(Benchmark benchmark, Runnable runnable)375     private void saveBenchmarkAsync(Benchmark benchmark, Runnable runnable) {
376         final byte[] bytes = benchmark.toBytes();
377 
378         mExecutor.execute(() -> {
379             save(bytes);
380             mHandler.post(() -> {
381                 logDebug("SAVED " + benchmark);
382                 benchmark.setPersisted();
383                 if (runnable != null) {
384                     runnable.run();
385                 }
386             });
387         });
388     }
389 
save(byte[] bytes)390     private synchronized void save(byte[] bytes) {
391         FileOutputStream fileOut = null;
392         try {
393             fileOut = openFileOutput(TAG, MODE_PRIVATE);
394             fileOut.write(bytes);
395             fileOut.flush();
396         } catch (FileNotFoundException e) {
397             Log.e(TAG, "File not found", e);
398         } catch (IOException e) {
399             Log.e(TAG, "I/O error", e);
400         } finally {
401             try {
402                 if (fileOut != null) {
403                     fileOut.close();
404                 }
405             } catch (IOException e) {
406                 Log.e(TAG, "Cannot close benchmark file", e);
407             }
408         }
409     }
410 
411     private static class Range {
412         public final long from;
413         public final long to;
414 
Range(long from, long to)415         public Range(long from, long to) {
416             if (to < from || to < 0 || from < 0) {
417                 throw new IllegalArgumentException("FROM: " + from + " before TO: " + to);
418             }
419             this.from = from;
420             this.to = to;
421         }
422 
inRange(long timestamp)423         public boolean inRange(long timestamp) {
424             return timestamp >= from && timestamp <= to;
425         }
426 
getDuration()427         public long getDuration() {
428             return to - from + 1;
429         }
430 
431         @Override
toString()432         public String toString() {
433             return String.format("[%d-%d]", from, to);
434         }
435     }
436 
437     public static class Benchmark implements Serializable {
438 
439         public enum Measure {
440             TOTAL,
441             WORK(WORK_REPEAT_MS),
442             MAIN(MAIN_REPEAT_MS),
443             ALARM(ALARM_REPEAT_MS + ALARM_REPEAT_MARGIN_MS);
444 
445             private final long interval;
446 
Measure()447             Measure() {
448                 interval = -1;
449             }
450 
Measure(long interval)451             Measure(long interval) {
452                 this.interval = interval;
453             }
454         }
455 
456         private static final long serialVersionUID = -2939643983335136263L;
457 
458         private long lastPersisted = -1;
459 
460         private long startTime;
461 
Benchmark()462         public Benchmark() {
463             startTime = SystemClock.elapsedRealtime();
464         }
465 
466         private final Map<Measure, List<Long>> eventMap = new HashMap<>();
467         private final Map<String, Long> tests = new HashMap<>();
468 
isRunning()469         public boolean isRunning() {
470             return tests.size() > 0;
471         }
472 
startTest(String id)473         public void startTest(String id) {
474             if (!tests.containsKey(id)) {
475                 tests.put(id, SystemClock.elapsedRealtime());
476             }
477         }
478 
finishTest(String id)479         public Map<Measure, Float> finishTest(String id) {
480             if (tests.containsKey(id)) {
481                 Long startTime = tests.remove(id);
482                 return getAllResults(new Range(startTime, SystemClock.elapsedRealtime()));
483             }
484             Log.w(TAG, "Missing results for test " + id);
485             return null;
486         }
487 
getAllResults(String id)488         public Map<Measure, Float> getAllResults(String id) {
489             if (tests.containsKey(id)) {
490                 Long startTime = tests.get(id);
491                 return getAllResults(new Range(startTime, SystemClock.elapsedRealtime()));
492             }
493             return null;
494         }
495 
getAllResults(Range range)496         private Map<Measure, Float> getAllResults(Range range) {
497             Map<Measure, Float> results = new HashMap<>();
498             for (Measure measure : Measure.values()) {
499                 results.put(measure, getResult(measure, range));
500             }
501             return results;
502         }
503 
getLastPersisted()504         public long getLastPersisted() {
505             return lastPersisted;
506         }
507 
setPersisted()508         public void setPersisted() {
509             this.lastPersisted = SystemClock.elapsedRealtime();
510         }
511 
filter(List<Long> source, Range range)512         private List<Long> filter(List<Long> source, Range range) {
513             List<Long> result = new ArrayList<>(source);
514 
515             if (range == null) {
516                 return source;
517             }
518 
519             Iterator<Long> i = result.iterator();
520             while (i.hasNext()) {
521                 if (!range.inRange(i.next())) {
522                     i.remove();
523                 }
524             }
525             return result;
526         }
527 
addEvent(Measure measure, long timestamp)528         public void addEvent(Measure measure, long timestamp) {
529             List<Long> events = getEvents(measure);
530             events.add(timestamp);
531             if (!eventMap.containsKey(measure)) {
532                 eventMap.put(measure, events);
533             }
534         }
535 
addEvent(Measure measure)536         public void addEvent(Measure measure) {
537             addEvent(measure, SystemClock.elapsedRealtime());
538         }
539 
getEvents(Measure measure)540         private List<Long> getEvents(Measure measure) {
541             List<Long> events = eventMap.get(measure);
542             return events == null ? new ArrayList<>() : events;
543         }
544 
getResult(Measure measure)545         public float getResult(Measure measure) {
546             return getResult(measure, null);
547         }
548 
getResult(Measure measure, Range range)549         public float getResult(Measure measure, Range range) {
550 
551             if (measure == Measure.TOTAL) {
552                 return (getResult(Measure.WORK, range) + (2 * getResult(Measure.ALARM, range)) +
553                         getResult(Measure.MAIN, range)) / 4f;
554             }
555 
556             List<Long> events = filter(getEvents(measure), range);
557 
558             return Math
559                     .min(1, events.size() / (getDuration(range) / (float) measure.interval));
560         }
561 
getDuration()562         private long getDuration() {
563             return SystemClock.elapsedRealtime() - startTime;
564         }
565 
getDuration(Range range)566         private long getDuration(Range range) {
567             if (range == null) {
568                 return getDuration();
569             }
570             return range.getDuration();
571         }
572 
toBytes()573         private byte[] toBytes() {
574             try {
575                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
576                 ObjectOutputStream out = new ObjectOutputStream(bos);
577                 out.writeObject(this);
578                 out.flush();
579                 out.close();
580                 bos.close();
581                 return bos.toByteArray();
582             } catch (IOException e) {
583                 Log.e(TAG, "Cannot serialize benchmark: " + this, e);
584                 return null;
585             }
586         }
587 
588         @SuppressLint("DefaultLocale")
589         @Override
toString()590         public String toString() {
591             return toReportString().replaceAll("\n", "");
592         }
593 
594         @SuppressLint("DefaultLocale")
toReportString()595         public String toReportString() {
596             return String
597                     .format("Benchmark TIME: %tT TESTS: %d \n\nMAIN:\n%.1f%% %d \n\nWORK:\n%.1f%%" +
598                                     " %d \n\nALARM:\n%.1f%% %d \n\n%s",
599                             getDuration() - TimeZone.getDefault().getOffset(0),
600                             tests.size(), getResult(Measure.MAIN) * 100,
601                             getEvents(Measure.MAIN).size(), getResult(Measure.WORK) * 100,
602                             getEvents(Measure.WORK).size(), getResult(Measure.ALARM) * 100,
603                             getEvents(Measure.ALARM).size(), isRunning() ? "RUNNING..." :
604                                     getResult(Measure.TOTAL) >= 0.9f ? "TEST PASSED!" :
605                                             "TEST FAILED!");
606         }
607     }
608 
logDebug(String s)609     public static void logDebug(String s) {
610         if (DEBUG) {
611             Log.d(TAG, s);
612         }
613     }
614 }