1 /* 2 * Copyright (C) 2019 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 android.server.wm; 18 19 import static android.server.wm.app.Components.ClickableToastActivity.ACTION_TOAST_DISPLAYED; 20 import static android.server.wm.app.Components.ClickableToastActivity.ACTION_TOAST_TAP_DETECTED; 21 22 import static com.google.common.truth.Truth.assertThat; 23 24 import static org.junit.Assert.assertFalse; 25 import static org.junit.Assert.assertNotEquals; 26 import static org.junit.Assert.assertNotNull; 27 import static org.junit.Assert.assertTrue; 28 29 import android.app.Instrumentation; 30 import android.content.BroadcastReceiver; 31 import android.content.ContentResolver; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.IntentFilter; 35 import android.os.ConditionVariable; 36 import android.platform.test.annotations.Presubmit; 37 import android.provider.Settings; 38 import android.server.wm.WindowManagerState.WindowState; 39 import android.server.wm.app.Components; 40 import android.view.WindowManager.LayoutParams; 41 import android.widget.Toast; 42 43 import androidx.test.platform.app.InstrumentationRegistry; 44 45 import com.android.compatibility.common.util.SystemUtil; 46 47 import org.junit.After; 48 import org.junit.Test; 49 50 import java.util.Collections; 51 import java.util.HashMap; 52 import java.util.Map; 53 54 import javax.annotation.Nullable; 55 56 @Presubmit 57 public class ToastWindowTest extends ActivityManagerTestBase { 58 private static final String TOAST_WINDOW_TITLE = "Toast"; 59 private static final String SETTING_HIDDEN_API_POLICY = "hidden_api_policy"; 60 private static final long TOAST_DISPLAY_TIMEOUT_MS = 8000; 61 private static final long TOAST_TAP_TIMEOUT_MS = 3500; 62 private static final int ACTIVITY_FOCUS_TIMEOUT_MS = 3000; 63 64 private Instrumentation mInstrumentation; 65 @Nullable 66 private String mPreviousHiddenApiPolicy; 67 private Map<String, ConditionVariable> mBroadcastsReceived; 68 69 private BroadcastReceiver mAppCommunicator = new BroadcastReceiver() { 70 @Override 71 public void onReceive(Context context, Intent intent) { 72 getBroadcastReceivedVariable(intent.getAction()).open(); 73 } 74 }; 75 76 @Override setUp()77 public void setUp() throws Exception { 78 super.setUp(); 79 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 80 81 SystemUtil.runWithShellPermissionIdentity(() -> { 82 ContentResolver resolver = mContext.getContentResolver(); 83 mPreviousHiddenApiPolicy = Settings.Global.getString(resolver, 84 SETTING_HIDDEN_API_POLICY); 85 Settings.Global.putString(resolver, SETTING_HIDDEN_API_POLICY, "1"); 86 }); 87 // Stopping just in case, to make sure reflection is allowed 88 stopTestPackage(Components.getPackageName()); 89 90 // These are parallel broadcasts, not affected by a busy queue 91 mBroadcastsReceived = Collections.synchronizedMap(new HashMap<>()); 92 IntentFilter filter = new IntentFilter(); 93 filter.addAction(ACTION_TOAST_DISPLAYED); 94 filter.addAction(ACTION_TOAST_TAP_DETECTED); 95 mContext.registerReceiver(mAppCommunicator, filter); 96 } 97 98 @After tearDown()99 public void tearDown() { 100 mContext.unregisterReceiver(mAppCommunicator); 101 SystemUtil.runWithShellPermissionIdentity(() -> { 102 Settings.Global.putString(mContext.getContentResolver(), SETTING_HIDDEN_API_POLICY, 103 mPreviousHiddenApiPolicy); 104 }); 105 } 106 107 @Test testToastWindowTokenIsRemovedAfterToastIsHidden()108 public void testToastWindowTokenIsRemovedAfterToastIsHidden() { 109 mInstrumentation.runOnMainSync(() -> { 110 Toast.makeText(mContext, "Toast token test", Toast.LENGTH_SHORT).show(); 111 }); 112 113 WindowManagerStateHelper wmState = getWmState(); 114 wmState.waitFor( 115 state -> state.findFirstWindowWithType(LayoutParams.TYPE_TOAST) == null, 116 "Toast wasn't hidden on time"); 117 wmState.waitFor( 118 state -> state.getMatchingVisibleWindowState(TOAST_WINDOW_TITLE).isEmpty(), 119 "Toast token wasn't removed on time"); 120 } 121 122 @Test testToastWindowIsNotClickable()123 public void testToastWindowIsNotClickable() { 124 Intent intent = new Intent(); 125 intent.setComponent(Components.CLICKABLE_TOAST_ACTIVITY); 126 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 127 mContext.startActivity(intent); 128 waitForActivityFocused(ACTIVITY_FOCUS_TIMEOUT_MS, Components.CLICKABLE_TOAST_ACTIVITY); 129 boolean toastDisplayed = getBroadcastReceivedVariable(ACTION_TOAST_DISPLAYED).block( 130 TOAST_DISPLAY_TIMEOUT_MS); 131 assertTrue("Toast not displayed on time", toastDisplayed); 132 WindowManagerState wmState = getWmState(); 133 wmState.computeState(); 134 WindowState toastWindow = wmState.findFirstWindowWithType(LayoutParams.TYPE_TOAST); 135 assertNotNull("Couldn't retrieve toast window", toastWindow); 136 137 tapOnCenter(toastWindow.getContainingFrame(), toastWindow.getDisplayId()); 138 139 boolean toastClicked = getBroadcastReceivedVariable(ACTION_TOAST_TAP_DETECTED).block( 140 TOAST_TAP_TIMEOUT_MS); 141 assertFalse("Toast tap detected", toastClicked); 142 } 143 getBroadcastReceivedVariable(String action)144 private ConditionVariable getBroadcastReceivedVariable(String action) { 145 return mBroadcastsReceived.computeIfAbsent(action, key -> new ConditionVariable()); 146 } 147 } 148