Files
frameworks_base/tests/testables/src/android/testing/BaseFragmentTest.java
lumark 7570cac60e Propogate per-display IME status to SysUI
CL [1] introduced SystemUI to support updating IME window status per
display. (i.e. IStatusBarService#setImeWindowStatus left TODO item to
add displayId parameter to select the target display.)

For IMMS, we applied this API base on current token's displayId, and refined
the code flow in Sysui to reset IME window state for non-target display's
NavBar when single IME switch to another display case.

And make sure some cases in IMMS should reset IME window status:
- When current method unbind.
- When current top window focus display is not same as current token's display.
  (That means the input session may connected but IME window not
  yet attached. Note that it doesn't include external display without
  system decoration and show IME window on default display case since it
  is intentional behavior and we still need to update status for this
  case.)

Also added testSetImeWindowStatusWhenImeSwitchOnDisplay in
NavigationBarFragmentTest to enhance the ability of verifying external
navigation bar.

[1]: 24e7a9fdb5

Bug: 127309955
Bug: 117478341
Test: atest NavigationBarFragmentTest
Test: manual as below steps:
      - Pre-condition:
          1. Enable desktop mode.
          2. Create simulated display.
      - Use case 1):
          1. Launch an activity with input field on simulated display.
          2. Bring up the IME there, expect back key icon on external
             NavBar will changed when IME bring up.
          3. Tap on primary display or launch an activity there.
	     (or pressing home key)
          4. Expect IME will hide & back key icon on external NavBar will
             set back.
      - Use case 2):
          1. Launch activity with input field on default display.
          2. Tapping EditText in activity on default display.
	  3. Expect IME will bring up here and back key icon on default
	     display will changed when IME bring up.
          4. Launch activity without input field in external display.
	     (i.e. clock app)
          5. Expect Both Default & external display's nav bar back key icon
	     will set back.

Change-Id: Ia414b8aea631e295cccd6f6da44d04bad16545c7
2019-04-19 13:33:21 +08:00

264 lines
7.7 KiB
Java

/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package android.testing;
import static org.junit.Assert.assertNotNull;
import android.annotation.Nullable;
import android.app.Fragment;
import android.app.FragmentController;
import android.app.FragmentHostCallback;
import android.app.FragmentManagerNonConfig;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.FrameLayout;
import androidx.test.InstrumentationRegistry;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* Base class for fragment class tests. Just adding one for any fragment will push it through
* general lifecycle events and ensure no basic leaks are happening. This class also implements
* the host for subclasses, so they can push it into desired states and do any unit testing
* required.
*/
public abstract class BaseFragmentTest {
private static final int VIEW_ID = 42;
private final Class<? extends Fragment> mCls;
protected Handler mHandler;
protected FrameLayout mView;
protected FragmentController mFragments;
protected Fragment mFragment;
@Rule
public final TestableContext mContext = getContext();
public BaseFragmentTest(Class<? extends Fragment> cls) {
mCls = cls;
}
protected void createRootView() {
mView = new FrameLayout(mContext);
}
@Before
public void setupFragment() throws Exception {
createRootView();
mView.setId(VIEW_ID);
assertNotNull("BaseFragmentTest must be tagged with @RunWithLooper",
TestableLooper.get(this));
TestableLooper.get(this).runWithLooper(() -> {
mHandler = new Handler();
mFragment = instantiate(mContext, mCls.getName(), null);
mFragments = FragmentController.createController(new HostCallbacks());
mFragments.attachHost(null);
mFragments.getFragmentManager().beginTransaction()
.replace(VIEW_ID, mFragment)
.commit();
});
}
/**
* Allows tests to sub-class TestableContext if they want to provide any extended functionality
* or provide a {@link LeakCheck} to the TestableContext upon instantiation.
*/
protected TestableContext getContext() {
return new TestableContext(InstrumentationRegistry.getContext());
}
@After
public void tearDown() throws Exception {
if (mFragments != null) {
// Set mFragments to null to let it know not to destroy.
TestableLooper.get(this).runWithLooper(() -> mFragments.dispatchDestroy());
}
}
@Test
public void testCreateDestroy() {
mFragments.dispatchCreate();
processAllMessages();
destroyFragments();
}
@Test
public void testStartStop() {
mFragments.dispatchStart();
processAllMessages();
mFragments.dispatchStop();
processAllMessages();
}
@Test
public void testResumePause() {
mFragments.dispatchResume();
processAllMessages();
mFragments.dispatchPause();
processAllMessages();
}
@Test
public void testAttachDetach() {
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
LayoutParams.TYPE_SYSTEM_ALERT,
0, PixelFormat.TRANSLUCENT);
mFragments.dispatchResume();
processAllMessages();
attachFragmentToWindow();
detachFragmentToWindow();
mFragments.dispatchPause();
processAllMessages();
}
@Test
public void testRecreate() {
mFragments.dispatchResume();
processAllMessages();
recreateFragment();
processAllMessages();
}
@Test
public void testMultipleResumes() {
mFragments.dispatchResume();
processAllMessages();
mFragments.dispatchStop();
processAllMessages();
mFragments.dispatchResume();
processAllMessages();
}
protected void recreateFragment() {
mFragments.dispatchPause();
Parcelable p = mFragments.saveAllState();
mFragments.dispatchDestroy();
mFragments = FragmentController.createController(new HostCallbacks());
mFragments.attachHost(null);
mFragments.restoreAllState(p, (FragmentManagerNonConfig) null);
mFragments.dispatchResume();
mFragment = mFragments.getFragmentManager().findFragmentById(VIEW_ID);
}
protected void attachFragmentToWindow() {
ViewUtils.attachView(mView);
TestableLooper.get(this).processAllMessages();
}
protected void detachFragmentToWindow() {
ViewUtils.detachView(mView);
TestableLooper.get(this).processAllMessages();
}
protected void destroyFragments() {
mFragments.dispatchDestroy();
processAllMessages();
mFragments = null;
}
protected void processAllMessages() {
TestableLooper.get(this).processAllMessages();
}
/**
* Method available for override to replace fragment instantiation.
*/
protected Fragment instantiate(Context context, String className, @Nullable Bundle arguments) {
return Fragment.instantiate(context, className, arguments);
}
private View findViewById(int id) {
return mView.findViewById(id);
}
private class HostCallbacks extends FragmentHostCallback<BaseFragmentTest> {
public HostCallbacks() {
super(mContext, BaseFragmentTest.this.mHandler, 0);
}
@Override
public BaseFragmentTest onGetHost() {
return BaseFragmentTest.this;
}
@Override
public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
}
@Override
public Fragment instantiate(Context context, String className, Bundle arguments) {
return BaseFragmentTest.this.instantiate(context, className, arguments);
}
@Override
public boolean onShouldSaveFragmentState(Fragment fragment) {
return true; // True for now.
}
@Override
public LayoutInflater onGetLayoutInflater() {
return LayoutInflater.from(mContext);
}
@Override
public boolean onUseFragmentManagerInflaterFactory() {
return true;
}
@Override
public boolean onHasWindowAnimations() {
return false;
}
@Override
public int onGetWindowAnimations() {
return 0;
}
@Override
public void onAttachFragment(Fragment fragment) {
}
@Nullable
@Override
public View onFindViewById(int id) {
return BaseFragmentTest.this.findViewById(id);
}
@Override
public boolean onHasView() {
return true;
}
}
}