1 /*
2  * Copyright (C) 2022 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.content;
18 
19 import static android.content.Context.OVERRIDABLE_COMPONENT_CALLBACKS;
20 import static android.view.Display.DEFAULT_DISPLAY;
21 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 
25 import static org.junit.Assert.fail;
26 
27 import android.app.WindowConfiguration;
28 import android.compat.testing.PlatformCompatChangeRule;
29 import android.content.res.Configuration;
30 import android.graphics.Rect;
31 import android.hardware.display.DisplayManager;
32 import android.platform.test.annotations.Presubmit;
33 import android.view.Display;
34 import android.window.WindowContext;
35 
36 import androidx.test.core.app.ApplicationProvider;
37 import androidx.test.ext.junit.runners.AndroidJUnit4;
38 import androidx.test.filters.SmallTest;
39 
40 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
41 
42 import org.junit.Rule;
43 import org.junit.Test;
44 import org.junit.rules.TestRule;
45 import org.junit.runner.RunWith;
46 
47 
48 /**
49  *  Build/Install/Run:
50  *   atest FrameworksCoreTests:ContextWrapperTest
51  */
52 @Presubmit
53 @SmallTest
54 @RunWith(AndroidJUnit4.class)
55 public class ContextWrapperTest {
56     @Rule
57     public TestRule compatChangeRule = new PlatformCompatChangeRule();
58 
59     /**
60      * Before {@link android.os.Build.VERSION_CODES#TIRAMISU}, {@link ContextWrapper} must
61      * register {@link ComponentCallbacks} to {@link ContextWrapper#getApplicationContext} before
62      * {@link ContextWrapper#attachBaseContext(Context)}.
63      */
64     @DisableCompatChanges(OVERRIDABLE_COMPONENT_CALLBACKS)
65     @Test
testRegisterComponentCallbacksWithoutBaseContextBeforeT()66     public void testRegisterComponentCallbacksWithoutBaseContextBeforeT() {
67         final ContextWrapper wrapper = new TestContextWrapper(null /* base */);
68         final ComponentCallbacks callbacks = new TestComponentCallbacks2();
69 
70         // It should be no-op if unregister a ComponentCallbacks without registration.
71         wrapper.unregisterComponentCallbacks(callbacks);
72 
73         wrapper.registerComponentCallbacks(callbacks);
74 
75         assertThat(wrapper.mCallbacksRegisteredToSuper.size()).isEqualTo(1);
76         assertThat(wrapper.mCallbacksRegisteredToSuper.get(0)).isEqualTo(callbacks);
77 
78         wrapper.unregisterComponentCallbacks(callbacks);
79 
80         assertThat(wrapper.mCallbacksRegisteredToSuper.isEmpty()).isTrue();
81     }
82 
83     /**
84      * After {@link android.os.Build.VERSION_CODES#TIRAMISU}, {@link ContextWrapper} must
85      * throw {@link IllegalStateException} before {@link ContextWrapper#attachBaseContext(Context)}.
86      */
87     @Test
testRegisterComponentCallbacksWithoutBaseContextAfterT()88     public void testRegisterComponentCallbacksWithoutBaseContextAfterT() {
89         final ContextWrapper wrapper = new TestContextWrapper(null /* base */);
90         final ComponentCallbacks callbacks = new TestComponentCallbacks2();
91 
92         try {
93             wrapper.unregisterComponentCallbacks(callbacks);
94             fail("ContextWrapper#unregisterComponentCallbacks must throw Exception before"
95                     + " ContextWrapper#attachToBaseContext.");
96         } catch (IllegalStateException ignored) {
97             // It is expected to throw IllegalStateException.
98         }
99 
100         try {
101             wrapper.registerComponentCallbacks(callbacks);
102             fail("ContextWrapper#registerComponentCallbacks must throw Exception before"
103                     + " ContextWrapper#attachToBaseContext.");
104         } catch (IllegalStateException ignored) {
105             // It is expected to throw IllegalStateException.
106         }
107     }
108 
109     /**
110      * {@link ContextWrapper#registerComponentCallbacks(ComponentCallbacks)} must delegate to its
111      * {@link ContextWrapper#getBaseContext()}, so does
112      * {@link ContextWrapper#unregisterComponentCallbacks(ComponentCallbacks)}.
113      */
114     @Test
testRegisterComponentCallbacks()115     public void testRegisterComponentCallbacks() {
116         final Context appContext = ApplicationProvider.getApplicationContext();
117         final Display display = appContext.getSystemService(DisplayManager.class)
118                 .getDisplay(DEFAULT_DISPLAY);
119         final WindowContext windowContext = (WindowContext) appContext.createWindowContext(display,
120                 TYPE_APPLICATION_OVERLAY, null /* options */);
121         final ContextWrapper wrapper = new ContextWrapper(windowContext);
122         final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2();
123 
124         wrapper.registerComponentCallbacks(callbacks);
125 
126         assertThat(wrapper.mCallbacksRegisteredToSuper).isNull();
127 
128         final Configuration dispatchedConfig = new Configuration();
129         dispatchedConfig.fontScale = 1.2f;
130         dispatchedConfig.windowConfiguration.setWindowingMode(
131                 WindowConfiguration.WINDOWING_MODE_FREEFORM);
132         dispatchedConfig.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
133         windowContext.dispatchConfigurationChanged(dispatchedConfig);
134 
135         assertThat(callbacks.mConfiguration).isEqualTo(dispatchedConfig);
136     }
137 
138     private static class TestContextWrapper extends ContextWrapper {
TestContextWrapper(Context base)139         TestContextWrapper(Context base) {
140             super(base);
141         }
142 
143         @Override
getApplicationContext()144         public Context getApplicationContext() {
145             // The default implementation of ContextWrapper#getApplicationContext is to delegate
146             // to the base Context, and it leads to NPE if #registerComponentCallbacks is called
147             // directly before attach to base Context.
148             // We call to ApplicationProvider#getApplicationContext to prevent NPE because
149             // developers may have its implementation to prevent NPE without attaching base Context.
150             final Context baseContext = getBaseContext();
151             if (baseContext == null) {
152                 return ApplicationProvider.getApplicationContext();
153             } else {
154                 return super.getApplicationContext();
155             }
156         }
157     }
158 }
159