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