Move out test utilities to a Testables library

Test: runtest --path frameworks/base/tests/testablets/tests
        && runtest systemui

Change-Id: Ideef4aef5f26136b1741c556b9be5884f38842a0
diff --git a/tests/testables/src/android/testing/BaseFragmentTest.java b/tests/testables/src/android/testing/BaseFragmentTest.java
new file mode 100644
index 0000000..53841d5
--- /dev/null
+++ b/tests/testables/src/android/testing/BaseFragmentTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.support.test.InstrumentationRegistry;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.FrameLayout;
+
+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;
+    private Handler mHandler;
+    private FrameLayout mView;
+    protected FragmentController mFragments;
+    protected Fragment mFragment;
+
+    @Rule
+    public final TestableContext mContext = getContext();
+
+    public BaseFragmentTest(Class<? extends Fragment> cls) {
+        mCls = cls;
+    }
+
+    @Before
+    public void setupFragment() throws Exception {
+        mView = new FrameLayout(mContext);
+        mView.setId(VIEW_ID);
+
+        assertNotNull("BaseFragmentTest must be tagged with @RunWithLooper",
+                TestableLooper.get(this));
+        TestableLooper.get(this).runWithLooper(() -> {
+            mHandler = new Handler();
+
+            mFragment = mCls.newInstance();
+            mFragments = FragmentController.createController(new HostCallbacks());
+            mFragments.attachHost(null);
+            mFragments.getFragmentManager().beginTransaction()
+                    .replace(VIEW_ID, mFragment)
+                    .commit();
+        });
+    }
+
+    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();
+        mFragments.dispatchPause();
+        Parcelable p = mFragments.saveAllState();
+        mFragments.dispatchDestroy();
+
+        mFragments = FragmentController.createController(new HostCallbacks());
+        mFragments.attachHost(null);
+        mFragments.restoreAllState(p, (FragmentManagerNonConfig) null);
+        mFragments.dispatchResume();
+        processAllMessages();
+    }
+
+    @Test
+    public void testMultipleResumes() {
+        mFragments.dispatchResume();
+        processAllMessages();
+        mFragments.dispatchStop();
+        processAllMessages();
+        mFragments.dispatchResume();
+        processAllMessages();
+    }
+
+    protected void attachFragmentToWindow() {
+        ViewUtils.attachView(mView);
+        TestableLooper.get(this).processMessages(1);
+    }
+
+    protected void detachFragmentToWindow() {
+        ViewUtils.detachView(mView);
+        TestableLooper.get(this).processMessages(1);
+    }
+
+    protected void destroyFragments() {
+        mFragments.dispatchDestroy();
+        processAllMessages();
+        mFragments = null;
+    }
+
+    protected void processAllMessages() {
+        TestableLooper.get(this).processAllMessages();
+    }
+
+    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 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;
+        }
+    }
+}