1/*
2 * Copyright (C) 2014 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 */
16package com.android.systemui;
17
18import static org.mockito.Mockito.spy;
19import static org.mockito.Mockito.when;
20
21import android.app.Instrumentation;
22import android.os.Handler;
23import android.os.Looper;
24import android.os.MessageQueue;
25import android.os.ParcelFileDescriptor;
26import android.support.test.InstrumentationRegistry;
27import android.testing.DexmakerShareClassLoaderRule;
28import android.testing.LeakCheck;
29import android.util.Log;
30
31import com.android.keyguard.KeyguardUpdateMonitor;
32
33import org.junit.After;
34import org.junit.Before;
35import org.junit.Rule;
36
37import java.io.FileInputStream;
38import java.io.IOException;
39import java.util.concurrent.ExecutionException;
40import java.util.concurrent.Future;
41
42/**
43 * Base class that does System UI specific setup.
44 */
45public abstract class SysuiTestCase {
46
47    private static final String TAG = "SysuiTestCase";
48
49    private Handler mHandler;
50    @Rule
51    public SysuiTestableContext mContext = new SysuiTestableContext(
52            InstrumentationRegistry.getContext(), getLeakCheck());
53    @Rule
54    public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
55            new DexmakerShareClassLoaderRule();
56    public TestableDependency mDependency = new TestableDependency(mContext);
57    private Instrumentation mRealInstrumentation;
58
59    @Before
60    public void SysuiSetup() throws Exception {
61        mContext.setTheme(R.style.Theme_SystemUI);
62        SystemUIFactory.createFromConfig(mContext);
63
64        mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
65        Instrumentation inst = spy(mRealInstrumentation);
66        when(inst.getContext()).thenAnswer(invocation -> {
67            throw new RuntimeException(
68                    "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
69        });
70        when(inst.getTargetContext()).thenAnswer(invocation -> {
71            throw new RuntimeException(
72                    "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
73        });
74        InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
75        KeyguardUpdateMonitor.disableHandlerCheckForTesting(inst);
76    }
77
78    @After
79    public void SysuiTeardown() {
80        InstrumentationRegistry.registerInstance(mRealInstrumentation,
81                InstrumentationRegistry.getArguments());
82    }
83
84    protected LeakCheck getLeakCheck() {
85        return null;
86    }
87
88    public SysuiTestableContext getContext() {
89        return mContext;
90    }
91
92    protected void runShellCommand(String command) throws IOException {
93        ParcelFileDescriptor pfd = mRealInstrumentation.getUiAutomation()
94                .executeShellCommand(command);
95
96        // Read the input stream fully.
97        FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
98        while (fis.read() != -1);
99        fis.close();
100    }
101
102    protected void waitForIdleSync() {
103        if (mHandler == null) {
104            mHandler = new Handler(Looper.getMainLooper());
105        }
106        waitForIdleSync(mHandler);
107    }
108
109    protected void waitForUiOffloadThread() {
110        Future<?> future = Dependency.get(UiOffloadThread.class).submit(() -> {});
111        try {
112            future.get();
113        } catch (InterruptedException | ExecutionException e) {
114            Log.e(TAG, "Failed to wait for ui offload thread.", e);
115        }
116    }
117
118    public static void waitForIdleSync(Handler h) {
119        validateThread(h.getLooper());
120        Idler idler = new Idler(null);
121        h.getLooper().getQueue().addIdleHandler(idler);
122        // Ensure we are non-idle, so the idle handler can run.
123        h.post(new EmptyRunnable());
124        idler.waitForIdle();
125    }
126
127    private static final void validateThread(Looper l) {
128        if (Looper.myLooper() == l) {
129            throw new RuntimeException(
130                "This method can not be called from the looper being synced");
131        }
132    }
133
134    public static final class EmptyRunnable implements Runnable {
135        public void run() {
136        }
137    }
138
139    public static final class Idler implements MessageQueue.IdleHandler {
140        private final Runnable mCallback;
141        private boolean mIdle;
142
143        public Idler(Runnable callback) {
144            mCallback = callback;
145            mIdle = false;
146        }
147
148        @Override
149        public boolean queueIdle() {
150            if (mCallback != null) {
151                mCallback.run();
152            }
153            synchronized (this) {
154                mIdle = true;
155                notifyAll();
156            }
157            return false;
158        }
159
160        public void waitForIdle() {
161            synchronized (this) {
162                while (!mIdle) {
163                    try {
164                        wait();
165                    } catch (InterruptedException e) {
166                    }
167                }
168            }
169        }
170    }
171}
172