1 /*
2  * Copyright (C) 2024 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.startinfoapp;
18 
19 import android.app.Activity;
20 import android.app.ActivityManager;
21 import android.app.ApplicationStartInfo;
22 import android.content.Intent;
23 import android.os.Bundle;
24 
25 import java.util.List;
26 import java.util.concurrent.Executors;
27 import java.util.concurrent.ScheduledExecutorService;
28 import java.util.function.Consumer;
29 
30 /**
31  * An activity to install to test ApplicationStartInfo.
32  *
33  * Specific test cases can be requested by putting an intent bundle extra with
34  * {@link REQUEST_KEY_ACTION} paired to one of the supported REQUEST_VALUE_ cases.
35  *
36  * A result will be provided back via a broadcast with action {@link REPLY_ACTION_COMPLETE} set for
37  * all cases, success and failure.
38  */
39 public class ApiTestActivity extends Activity {
40 
41     private static final String REQUEST_KEY_ACTION = "action";
42     private static final String REQUEST_KEY_TIMESTAMP_KEY_FIRST = "timestamp_key_first";
43     private static final String REQUEST_KEY_TIMESTAMP_VALUE_FIRST = "timestamp_value_first";
44     private static final String REQUEST_KEY_TIMESTAMP_KEY_LAST = "timestamp_key_last";
45     private static final String REQUEST_KEY_TIMESTAMP_VALUE_LAST = "timestamp_value_last";
46 
47     // Request value for app to query and verify its own start.
48     private static final int REQUEST_VALUE_QUERY_START = 1;
49 
50     // Request value for app to add the provided timestamp to start info.
51     private static final int REQUEST_VALUE_ADD_TIMESTAMP = 2;
52 
53     // Request value for app to add a listener and respond when it gets triggered.
54     private static final int REQUEST_VALUE_LISTENER_ADD_ONE = 3;
55 
56     // Request value for app to add 2 listeners and respond when each gets triggered.
57     private static final int REQUEST_VALUE_LISTENER_ADD_MULTIPLE = 4;
58 
59     // Request value for app to add 2 listeners, remove 1, and respond success when correct one
60     // is triggered and failure if incorrect one is triggered.
61     private static final int REQUEST_VALUE_LISTENER_ADD_REMOVE = 5;
62 
63     // Request value for app to immediately crash. No reply will be sent.
64     private static final int REQUEST_VALUE_CRASH = 6;
65 
66     // Broadcast action to return result for request.
67     private static final String REPLY_ACTION_COMPLETE =
68             "com.android.cts.startinfoapp.ACTION_COMPLETE";
69 
70     private static final String REPLY_EXTRA_STATUS_KEY = "status";
71 
72     private static final int REPLY_EXTRA_SUCCESS_VALUE = 1;
73     private static final int REPLY_EXTRA_FAILURE_VALUE = 2;
74 
75     private static final int REPLY_STATUS_NONE = -1;
76 
77     @Override
onCreate(Bundle bundle)78     public void onCreate(Bundle bundle) {
79         super.onCreate(bundle);
80 
81         Intent intent = getIntent();
82         if (intent == null) {
83             return;
84         }
85 
86         Bundle extras = intent.getExtras();
87         if (extras == null) {
88             return;
89         }
90 
91         int action = extras.getInt(REQUEST_KEY_ACTION, -1);
92         if (action == -1) {
93             return;
94         }
95         switch (action) {
96             case REQUEST_VALUE_QUERY_START:
97                 queryStart();
98                 break;
99             case REQUEST_VALUE_ADD_TIMESTAMP:
100                 addTimestamp(extras);
101                 break;
102             case REQUEST_VALUE_LISTENER_ADD_ONE:
103                 addOneListener();
104                 break;
105             case REQUEST_VALUE_LISTENER_ADD_MULTIPLE:
106                 addMultipleListeners();
107                 break;
108             case REQUEST_VALUE_LISTENER_ADD_REMOVE:
109                 addAndRemoveListener();
110                 break;
111             case REQUEST_VALUE_CRASH:
112                 throw new RuntimeException("This is a test");
113         }
114     }
115 
116     /**
117      * Query a single start from historical records and confirms it exists.
118      *
119      * Records are expected to be available, though not necessarily complete, after the start begins
120      * even if the start has not completed yet.
121      *
122      * Sends broadcast with  {@link REPLY_EXTRA_SUCCESS_VALUE} if a start record was successfully
123      * received, and {@link REPLY_EXTRA_FAILURE_VALUE} if not.
124      */
queryStart()125     private void queryStart() {
126         ActivityManager am = getSystemService(ActivityManager.class);
127         List<ApplicationStartInfo> starts = am.getHistoricalProcessStartReasons(1);
128 
129         boolean success = starts != null && starts.size() == 1;
130         reply(success ? REPLY_EXTRA_SUCCESS_VALUE : REPLY_EXTRA_FAILURE_VALUE);
131     }
132 
133     /**
134      * Adds provided timestamps to the ongoing start record. Does not confirm that they were added
135      * successfully.
136      *
137      * Sends broadcast with no status when complete.
138      */
addTimestamp(Bundle extras)139     private void addTimestamp(Bundle extras) {
140         int keyFirst = extras.getInt(REQUEST_KEY_TIMESTAMP_KEY_FIRST, 21);
141         long valFirst = extras.getLong(REQUEST_KEY_TIMESTAMP_VALUE_FIRST, 123456789L);
142         int keyLast = extras.getInt(REQUEST_KEY_TIMESTAMP_KEY_LAST, 30);
143         long valLast = extras.getLong(REQUEST_KEY_TIMESTAMP_VALUE_LAST, 123456789L);
144 
145         ActivityManager am = getSystemService(ActivityManager.class);
146         am.addStartInfoTimestamp(keyFirst, valFirst);
147         am.addStartInfoTimestamp(keyLast, valLast);
148 
149         reply(REPLY_STATUS_NONE);
150     }
151 
152     /**
153      * Add 1 listener.
154      *
155      * Listener is expected to be triggered upon completion of start, or immediately if the start
156      * is already complete.
157      *
158      * Result will be broadcast when listener is triggered.
159      */
addOneListener()160     private void addOneListener() {
161         ActivityManager am = getSystemService(ActivityManager.class);
162         Consumer<ApplicationStartInfo> listener = new Consumer<ApplicationStartInfo>() {
163             @Override
164             public void accept(ApplicationStartInfo info) {
165                 reply(REPLY_EXTRA_SUCCESS_VALUE);
166             }
167         };
168         am.addApplicationStartInfoCompletionListener(Executors.newSingleThreadScheduledExecutor(),
169                 listener);
170     }
171 
172     /**
173      * Add 2 listeners.
174      *
175      * Listener is expected to be triggered upon completion of start, or immediately if the start
176      * is already complete.
177      *
178      * Result will be broadcast for each listener when triggered.
179      */
addMultipleListeners()180     private void addMultipleListeners() {
181         ActivityManager am = getSystemService(ActivityManager.class);
182         final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
183 
184         Consumer<ApplicationStartInfo> listenerFirst = new Consumer<ApplicationStartInfo>() {
185             @Override
186             public void accept(ApplicationStartInfo info) {
187                 reply(REPLY_EXTRA_SUCCESS_VALUE);
188             }
189         };
190         Consumer<ApplicationStartInfo> listenerSecond = new Consumer<ApplicationStartInfo>() {
191             @Override
192             public void accept(ApplicationStartInfo info) {
193                 reply(REPLY_EXTRA_SUCCESS_VALUE);
194             }
195         };
196 
197         am.addApplicationStartInfoCompletionListener(executor, listenerFirst);
198         am.addApplicationStartInfoCompletionListener(executor, listenerSecond);
199     }
200 
201     /**
202      * Add 2 listeners and then remove 1.
203      *
204      * Listener is expected to be triggered upon completion of start, or immediately if the start
205      * is already complete.
206      *
207      * Result will be broadcast for each listener if triggered. Result status will be
208      * {@link REPLY_EXTRA_SUCCESS_VALUE} when listener that was not removed is triggered, and
209      * {@link REPLY_EXTRA_FAILURE_VALUE} if listener that was removed is triggered. This method is
210      * intended to be called during startup so that the first listener has time to be removed.
211      */
addAndRemoveListener()212     private void addAndRemoveListener() {
213         ActivityManager am = getSystemService(ActivityManager.class);
214         final Object mLock = new Object();
215         final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
216 
217         Consumer<ApplicationStartInfo> listenerToRemove = new Consumer<ApplicationStartInfo>() {
218             @Override
219             public void accept(ApplicationStartInfo info) {
220                 synchronized (mLock) {
221                     reply(REPLY_EXTRA_FAILURE_VALUE);
222                 }
223             }
224         };
225         Consumer<ApplicationStartInfo> listenerToTrigger = new Consumer<ApplicationStartInfo>() {
226             @Override
227             public void accept(ApplicationStartInfo info) {
228                 synchronized (mLock) {
229                     reply(REPLY_EXTRA_SUCCESS_VALUE);
230                 }
231             };
232         };
233 
234         am.addApplicationStartInfoCompletionListener(executor, listenerToRemove);
235         am.addApplicationStartInfoCompletionListener(executor, listenerToTrigger);
236 
237         am.removeApplicationStartInfoCompletionListener(listenerToRemove);
238     }
239 
reply(int status)240     private void reply(int status) {
241         Intent reply = new Intent();
242         reply.setAction(REPLY_ACTION_COMPLETE);
243         if (status != REPLY_STATUS_NONE) {
244             reply.putExtra(REPLY_EXTRA_STATUS_KEY, status);
245         }
246         sendBroadcast(reply);
247     }
248 
249     @Override
onNewIntent(Intent intent)250     protected void onNewIntent(Intent intent) {
251         super.onNewIntent(intent);
252     }
253 }
254