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