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.launcher3.testing;
18 
19 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
20 
21 import android.app.Activity;
22 import android.app.Application;
23 import android.content.Context;
24 import android.os.Binder;
25 import android.os.Bundle;
26 import android.system.Os;
27 import android.view.View;
28 
29 import androidx.annotation.Keep;
30 
31 import com.android.launcher3.LauncherAppState;
32 import com.android.launcher3.LauncherSettings;
33 
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.LinkedList;
38 import java.util.Map;
39 import java.util.WeakHashMap;
40 import java.util.concurrent.CountDownLatch;
41 import java.util.concurrent.TimeUnit;
42 
43 /**
44  * Class to handle requests from tests, including debug ones.
45  */
46 public class DebugTestInformationHandler extends TestInformationHandler {
47     private static LinkedList sLeaks;
48     private static Collection<String> sEvents;
49     private static Application.ActivityLifecycleCallbacks sActivityLifecycleCallbacks;
50     private static final Map<Activity, Boolean> sActivities =
51             Collections.synchronizedMap(new WeakHashMap<>());
52     private static int sActivitiesCreatedCount = 0;
53 
DebugTestInformationHandler(Context context)54     public DebugTestInformationHandler(Context context) {
55         init(context);
56         if (sActivityLifecycleCallbacks == null) {
57             sActivityLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {
58                 @Override
59                 public void onActivityCreated(Activity activity, Bundle bundle) {
60                     sActivities.put(activity, true);
61                     ++sActivitiesCreatedCount;
62                 }
63 
64                 @Override
65                 public void onActivityStarted(Activity activity) {
66                 }
67 
68                 @Override
69                 public void onActivityResumed(Activity activity) {
70                 }
71 
72                 @Override
73                 public void onActivityPaused(Activity activity) {
74                 }
75 
76                 @Override
77                 public void onActivityStopped(Activity activity) {
78                 }
79 
80                 @Override
81                 public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
82                 }
83 
84                 @Override
85                 public void onActivityDestroyed(Activity activity) {
86                 }
87             };
88             ((Application) context.getApplicationContext())
89                     .registerActivityLifecycleCallbacks(sActivityLifecycleCallbacks);
90         }
91     }
92 
runGcAndFinalizersSync()93     private static void runGcAndFinalizersSync() {
94         Runtime.getRuntime().gc();
95         Runtime.getRuntime().runFinalization();
96 
97         final CountDownLatch fence = new CountDownLatch(1);
98         createFinalizationObserver(fence);
99         try {
100             do {
101                 Runtime.getRuntime().gc();
102                 Runtime.getRuntime().runFinalization();
103             } while (!fence.await(100, TimeUnit.MILLISECONDS));
104         } catch (InterruptedException ex) {
105             throw new RuntimeException(ex);
106         }
107     }
108 
109     // Create the observer in the scope of a method to minimize the chance that
110     // it remains live in a DEX/machine register at the point of the fence guard.
111     // This must be kept to avoid R8 inlining it.
112     @Keep
createFinalizationObserver(CountDownLatch fence)113     private static void createFinalizationObserver(CountDownLatch fence) {
114         new Object() {
115             @Override
116             protected void finalize() throws Throwable {
117                 try {
118                     fence.countDown();
119                 } finally {
120                     super.finalize();
121                 }
122             }
123         };
124     }
125 
126     @Override
call(String method, String arg)127     public Bundle call(String method, String arg) {
128         final Bundle response = new Bundle();
129         switch (method) {
130             case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
131                 return getLauncherUIProperty(Bundle::putInt,
132                         l -> l.getAppsView().getAppsStore().getDeferUpdatesFlags());
133             }
134 
135             case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
136                 TestProtocol.sDebugTracing = true;
137                 return response;
138 
139             case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
140                 TestProtocol.sDebugTracing = false;
141                 return response;
142 
143             case TestProtocol.REQUEST_PID: {
144                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, Os.getpid());
145                 return response;
146             }
147 
148             case TestProtocol.REQUEST_FORCE_GC: {
149                 runGcAndFinalizersSync();
150                 return response;
151             }
152 
153             case TestProtocol.REQUEST_VIEW_LEAK: {
154                 if (sLeaks == null) sLeaks = new LinkedList();
155                 sLeaks.add(new View(mContext));
156                 sLeaks.add(new View(mContext));
157                 return response;
158             }
159 
160             case TestProtocol.REQUEST_START_EVENT_LOGGING: {
161                 sEvents = new ArrayList<>();
162                 TestLogging.setEventConsumer(
163                         (sequence, event) -> {
164                             final Collection<String> events = sEvents;
165                             if (events != null) {
166                                 synchronized (events) {
167                                     events.add(sequence + '/' + event);
168                                 }
169                             }
170                         });
171                 return response;
172             }
173 
174             case TestProtocol.REQUEST_STOP_EVENT_LOGGING: {
175                 TestLogging.setEventConsumer(null);
176                 sEvents = null;
177                 return response;
178             }
179 
180             case TestProtocol.REQUEST_GET_TEST_EVENTS: {
181                 if (sEvents == null) {
182                     // sEvents can be null if Launcher died and restarted after
183                     // REQUEST_START_EVENT_LOGGING.
184                     return response;
185                 }
186 
187                 synchronized (sEvents) {
188                     response.putStringArrayList(
189                             TestProtocol.TEST_INFO_RESPONSE_FIELD, new ArrayList<>(sEvents));
190                 }
191                 return response;
192             }
193 
194             case TestProtocol.REQUEST_CLEAR_DATA: {
195                 final long identity = Binder.clearCallingIdentity();
196                 try {
197                     LauncherSettings.Settings.call(mContext.getContentResolver(),
198                             LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
199                     MAIN_EXECUTOR.submit(() ->
200                             LauncherAppState.getInstance(mContext).getModel().forceReload());
201                     return response;
202                 } finally {
203                     Binder.restoreCallingIdentity(identity);
204                 }
205             }
206 
207             case TestProtocol.REQUEST_GET_ACTIVITIES_CREATED_COUNT: {
208                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, sActivitiesCreatedCount);
209                 return response;
210             }
211 
212             case TestProtocol.REQUEST_GET_ACTIVITIES: {
213                 response.putStringArray(TestProtocol.TEST_INFO_RESPONSE_FIELD,
214                         sActivities.keySet().stream().map(
215                                 a -> a.getClass().getSimpleName() + " ("
216                                         + (a.isDestroyed() ? "destroyed" : "current") + ")")
217                                 .toArray(String[]::new));
218                 return response;
219             }
220 
221             default:
222                 return super.call(method, arg);
223         }
224     }
225 }
226