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