From b6e71bc544f1ccf75b805fc2242226a3e6e566c2 Mon Sep 17 00:00:00 2001 From: Danesh M Date: Wed, 8 Jun 2016 11:25:08 -0700 Subject: [PATCH] External view test CYNGNOS-3042 Change-Id: Ibdd11b631c6deea3eb030ffb1ba55b6ca5fe022b --- tests/Android.mk | 6 +- tests/proguard.flags | 6 + .../common/MockIBinderStubForInterface.java | 59 +++ .../tests/common/ThreadServiceTestCase.java | 200 +++++++++ .../externalviews/ViewProviderService.java | 132 ++++++ .../KeyguardExternalProviderTest.java | 380 ++++++++++++++++++ .../KeyguardExternalViewTest.java | 254 ++++++++++++ 7 files changed, 1035 insertions(+), 2 deletions(-) create mode 100644 tests/src/org/cyanogenmod/tests/common/MockIBinderStubForInterface.java create mode 100644 tests/src/org/cyanogenmod/tests/common/ThreadServiceTestCase.java create mode 100644 tests/src/org/cyanogenmod/tests/externalviews/ViewProviderService.java create mode 100644 tests/src/org/cyanogenmod/tests/externalviews/keyguardexternalviews/KeyguardExternalProviderTest.java create mode 100644 tests/src/org/cyanogenmod/tests/externalviews/keyguardexternalviews/KeyguardExternalViewTest.java diff --git a/tests/Android.mk b/tests/Android.mk index 105f5d0..5046bc4 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -20,7 +20,8 @@ LOCAL_MODULE_TAGS := tests LOCAL_STATIC_JAVA_LIBRARIES := \ org.cyanogenmod.platform.sdk \ - android-support-test + android-support-test \ + mockito-target LOCAL_DEX_PREOPT := false @@ -40,7 +41,8 @@ LOCAL_MODULE_TAGS := tests LOCAL_STATIC_JAVA_LIBRARIES := \ org.cyanogenmod.platform.sdk \ - android-support-test + android-support-test \ + mockito-target LOCAL_DEX_PREOPT := false diff --git a/tests/proguard.flags b/tests/proguard.flags index d9e855c..fd08027 100644 --- a/tests/proguard.flags +++ b/tests/proguard.flags @@ -43,6 +43,12 @@ -dontwarn junit.** -dontwarn org.junit.** +# keep mockito methods +-keep class org.mockito.** { *; } +-keep interface org.mockito.** { *; } +-keep class com.google.dexmaker.** { *; } +-keep interface com.google.dexmaker.** { *; } + # Always process -forceprocessing diff --git a/tests/src/org/cyanogenmod/tests/common/MockIBinderStubForInterface.java b/tests/src/org/cyanogenmod/tests/common/MockIBinderStubForInterface.java new file mode 100644 index 0000000..e2f7702 --- /dev/null +++ b/tests/src/org/cyanogenmod/tests/common/MockIBinderStubForInterface.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2016, The CyanogenMod 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 org.cyanogenmod.tests.common; + +import android.os.IBinder; +import android.os.IInterface; +import android.os.RemoteCallbackList; +import org.junit.Assert; +import org.mockito.Mockito; + +import java.lang.reflect.Field; + +/** + * Helper class to mock stubs for IInterfaces + * Ensures that when querying the local interface + * we return the instance itself as to preserve the mock instance + */ +public final class MockIBinderStubForInterface { + private MockIBinderStubForInterface() {} + + private static String getStubDescriptor(Class stubClass) { + String descriptor = null; + try { + Field f = stubClass.getDeclaredField("DESCRIPTOR"); + f.setAccessible(true); + descriptor = (String) f.get(stubClass); + } catch (NoSuchFieldException | IllegalAccessException e) { + Assert.fail(e.getMessage()); + } + return descriptor; + } + + public static T getMockInterface(Class stub) { + String descriptor = getStubDescriptor(stub); + T mockInterface = Mockito.mock(stub); + Mockito.doReturn(mockInterface) + .when(mockInterface) + .queryLocalInterface(descriptor == null ? + Mockito.anyString() : Mockito.eq(descriptor)); + Mockito.doReturn(Mockito.mock(IBinder.class)) + .when((IInterface) mockInterface) + .asBinder(); + return mockInterface; + } +} diff --git a/tests/src/org/cyanogenmod/tests/common/ThreadServiceTestCase.java b/tests/src/org/cyanogenmod/tests/common/ThreadServiceTestCase.java new file mode 100644 index 0000000..4404377 --- /dev/null +++ b/tests/src/org/cyanogenmod/tests/common/ThreadServiceTestCase.java @@ -0,0 +1,200 @@ +package org.cyanogenmod.tests.common; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.test.InstrumentationTestCase; +import android.test.ServiceTestCase; + +/** + * Tests a service in its own Thread. + * + * + *

+ * The {@link ServiceTestCase} class creates the service in the same thread the + * test is running. In consequence Handlers and other constructs that depend on + * the fact that the service methods are always run on the main thread + * won't work. + *

+ * + *

+ * To circumvent this, this test class creates a {@link HandlerThread} on setup + * to simulate the main tread and provides helper constructs to ease the + * communication between the Service and the test class : + *

+ * + *
    + *
  • The {@link #runOnServiceThread(Runnable)} methods allows to run code on + * the service pseudo-main thread.
  • + *
  • The {@link #startService(boolean, ServiceRunnable)} mehod allows starting + * the service in its own thread with some additional initialization code.
  • + *
+ * + * + * @author Antoine Martin + * + */ +public abstract class ThreadServiceTestCase extends ServiceTestCase { + + /** Typical maximum wait time for something to happen on the service */ + public static final long WAIT_TIME = 5 * 1000; + + /* + * This class provides final mutable values through indirection + */ + static class Holder { + H value; + } + + protected Handler serviceHandler; + protected Looper serviceLooper; + /* + * Got to catch this again because of damn package visibility of + * mServiceClass in base class. + */ + protected Class serviceClass; + + public ThreadServiceTestCase(Class serviceClass) { + super(serviceClass); + this.serviceClass = serviceClass; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + // Setup service thread + HandlerThread serviceThread = new HandlerThread("[" + serviceClass.getSimpleName() + "Thread]"); + serviceThread.start(); + serviceLooper = serviceThread.getLooper(); + serviceHandler = new Handler(serviceLooper); + } + + @Override + public void testServiceTestCaseSetUpProperly() throws Exception { + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + // teardown service thread + if (serviceLooper != null) + serviceLooper.quit(); + serviceHandler = null; + } + + /** + * Runs the specified runnable on the service tread and waits for its + * completion. + * + * @see InstrumentationTestCase#runTestOnUiThread(Runnable) + * @param r + * The runnable to run on the pseudo-main thread. + */ + protected void runOnServiceThread(final Runnable r) { + final CountDownLatch serviceSignal = new CountDownLatch(1); + serviceHandler.post(new Runnable() { + + @Override + public void run() { + r.run(); + serviceSignal.countDown(); + } + }); + + try { + serviceSignal.await(); + } catch (InterruptedException ie) { + fail("The Service thread has been interrupted"); + } + } + + /** + * Runnable interface allowing service initialization personalization. + * + * @author Antoine Martin + * + */ + protected interface ServiceRunnable { + public void run(Service service); + } + + /** + * Initialize the service in its own thread and returns it. + * + * @param bound + * if {@code true}, the service will be created as if it was + * bound by a client. if {@code false}, it will be created by a + * {@code startService} call. + * @param r + * {@link ServiceRunnable} instance that will be called with the + * created service. + * @return The created service. + */ + protected T startService(final ServiceRunnable r) { + final Holder serviceHolder = new Holder(); + + // I want to create my service in its own 'Main thread' + // So it can use its handler + runOnServiceThread(new Runnable() { + + @Override + public void run() { + T service = null; + startService(new Intent(getContext(), serviceClass)); + service = getService(); + if (r != null) + r.run(service); + serviceHolder.value = service; + } + }); + + return serviceHolder.value; + } + + protected IBinder bindService(final ServiceRunnable r) { + final Holder serviceHolder = new Holder(); + + // I want to create my service in its own 'Main thread' + // So it can use its handler + runOnServiceThread(new Runnable() { + + @Override + public void run() { + T service = null; + IBinder binder = bindService(new Intent(getContext(), serviceClass)); + service = getService(); + if (r != null) + r.run(service); + serviceHolder.value = binder; + } + }); + + return serviceHolder.value; + } + + public static class ServiceSyncHelper { + // The semaphore will wakeup clients + protected final Semaphore semaphore = new Semaphore(0); + + /** + * Waits for some response coming from the service. + * + * @param timeout + * The maximum time to wait. + * @throws InterruptedException + * if the Thread is interrupted or reaches the timeout. + */ + public synchronized void waitListener(long timeout) throws InterruptedException { + if (!semaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS)) + throw new InterruptedException(); + } + } + +} \ No newline at end of file diff --git a/tests/src/org/cyanogenmod/tests/externalviews/ViewProviderService.java b/tests/src/org/cyanogenmod/tests/externalviews/ViewProviderService.java new file mode 100644 index 0000000..7f0f8dd --- /dev/null +++ b/tests/src/org/cyanogenmod/tests/externalviews/ViewProviderService.java @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2016, The CyanogenMod 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 org.cyanogenmod.tests.externalviews.keyguardexternalviews; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.Space; +import cyanogenmod.externalviews.KeyguardExternalViewProviderService; +import org.mockito.Mockito; + +public class ViewProviderService extends KeyguardExternalViewProviderService { + private ViewProvider mProvider; + + public ViewProviderService() {} + + @Override + public KeyguardExternalViewProviderService.Provider createExternalView(Bundle options) { + if (mProvider == null) { + mProvider = Mockito.spy(new ViewProvider(options)); + } + return mProvider; + } + + public ViewProvider getProvider() { + return mProvider; + } + + public class ViewProvider extends KeyguardExternalViewProviderService.Provider { + private ViewProvider mTracker; + private View mView; + + public ViewProvider(Bundle options) { + super(options); + } + + public View getView() { + return mView; + } + + public ViewProvider getTracker() { + return mTracker; + } + + @Override + public View onCreateView() { + if (mTracker == null) { + mTracker = Mockito.mock(ViewProvider.class); + } + mTracker.onCreateView(); + if (mView == null) { + mView = new Space(getBaseContext()); + } + return mView; + } + @Override + public void onKeyguardShowing(boolean screenOn) { + mTracker.onKeyguardShowing(screenOn); + } + @Override + public void onKeyguardDismissed() { + mTracker.onKeyguardDismissed(); + } + @Override + public void onBouncerShowing(boolean showing) { + mTracker.onBouncerShowing(showing); + } + @Override + public void onScreenTurnedOn() { + mTracker.onScreenTurnedOn(); + } + @Override + public void onScreenTurnedOff() { + mTracker.onScreenTurnedOff(); + } + + @Override + protected void onAttach() { + mTracker.onAttach(); + } + + @Override + protected void onDetach() { + mTracker.onDetach(); + } + + @Override + protected void onLockscreenSlideOffsetChanged(float swipeProgress) { + mTracker.onLockscreenSlideOffsetChanged(swipeProgress); + } + + public boolean requestDismissImpl() { + return requestDismiss(); + } + + public boolean requestDismissAndStartActivityImpl(Intent intent) { + return requestDismissAndStartActivity(intent); + } + + public void setInteractivityImpl(boolean interactive) { + setInteractivity(interactive); + } + + public void slideLockscreenInImpl() { + slideLockscreenIn(); + } + + public Bundle getOptionsImpl() { + return getOptions(); + } + + public void collapseNotificationPanelImpl() { + mTracker.collapseNotificationPanelImpl(); + collapseNotificationPanel(); + } + } +}; \ No newline at end of file diff --git a/tests/src/org/cyanogenmod/tests/externalviews/keyguardexternalviews/KeyguardExternalProviderTest.java b/tests/src/org/cyanogenmod/tests/externalviews/keyguardexternalviews/KeyguardExternalProviderTest.java new file mode 100644 index 0000000..259d22d --- /dev/null +++ b/tests/src/org/cyanogenmod/tests/externalviews/keyguardexternalviews/KeyguardExternalProviderTest.java @@ -0,0 +1,380 @@ +/** + * Copyright (c) 2015-2016, The CyanogenMod 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 org.cyanogenmod.tests.externalviews.keyguardexternalviews; + +import android.content.Context; +import android.content.Intent; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.*; +import android.test.ServiceTestCase; +import android.view.*; +import android.widget.Space; +import android.widget.TextView; +import cyanogenmod.externalviews.IExternalViewProviderFactory; +import cyanogenmod.externalviews.IKeyguardExternalViewCallbacks; +import cyanogenmod.externalviews.IKeyguardExternalViewProvider; +import cyanogenmod.externalviews.KeyguardExternalViewProviderService; +import org.cyanogenmod.tests.common.MockIBinderStubForInterface; +import org.cyanogenmod.tests.common.ThreadServiceTestCase; +import org.junit.Assert; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.lang.reflect.Field; + +public class KeyguardExternalProviderTest extends ThreadServiceTestCase { + private WindowManager mWindowManagerMock; + private IExternalViewProviderFactory mProvider; + + public KeyguardExternalProviderTest() { + super(ViewProviderService.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + IBinder bind = bindService((ServiceRunnable) null); + assert (bind != null); + + mProvider = IExternalViewProviderFactory.Stub.asInterface(bind); + assert (mProvider != null); + + final Bundle bundle = new Bundle(); + IBinder bindView = mProvider.createExternalView(bundle); + IKeyguardExternalViewProvider view = IKeyguardExternalViewProvider.Stub.asInterface(bindView); + assert (view != null); + + runOnServiceThread(new Runnable() { + @Override + public void run() { + Mockito.verify(getService(), Mockito.times(1)) + .createExternalView(Mockito.eq(bundle)); + Mockito.verify(getService().getProvider().getTracker(), Mockito.times(1)) + .onCreateView(); + } + }); + } + + @Override + protected void setupService() { + super.setupService(); + + // Update the service instance with our spy so we can track it + try { + Field f = ServiceTestCase.class.getDeclaredField("mService"); + f.setAccessible(true); + ViewProviderService woot = ViewProviderService.class.newInstance(); + ViewProviderService spy = Mockito.spy(woot); + f.set(this, spy); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + + // Setup mock context + Context context = Mockito.mock(Context.class, Mockito.CALLS_REAL_METHODS); + Mockito.doReturn(getContext().getApplicationInfo()).when(context).getApplicationInfo(); + Mockito.doReturn(getContext().getResources()).when(context).getResources(); + Mockito.doReturn(getContext().getTheme()).when(context).getTheme(); + Mockito.doReturn(getContext().getPackageManager()).when(context).getPackageManager(); + Mockito.doReturn(1).when(context).checkCallingOrSelfPermission(Mockito.anyString()); + Mockito.doReturn(getContext().getMainLooper()).when(context).getMainLooper(); + + // Setup mock window manager + mWindowManagerMock = Mockito.mock(WindowManager.class); + WindowManager actualWindowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); + Mockito.doReturn(mWindowManagerMock).when(context).getSystemService(Mockito.eq(Context.WINDOW_SERVICE)); + Mockito.doReturn(actualWindowManager.getDefaultDisplay()).when(mWindowManagerMock).getDefaultDisplay(); + Mockito.doReturn(getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)) + .when(context).getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + // Attach our mock context to service + getService().attach( + context, + null, // ActivityThread not actually used in Service + ViewProviderService.class.getName(), + null, // token not needed when not talking with the activity manager + getApplication(), + null // mocked services don't talk with the activity manager + ); + } + + public void testCallbacks() throws Exception { + IBinder bind = getService().onBind(new Intent()); + final IExternalViewProviderFactory provider = IExternalViewProviderFactory.Stub.asInterface(bind); + assert (provider != null); + + // Ensure on bind we were asked to create an external view + final Bundle bundle = new Bundle(); + IBinder bindView = provider.createExternalView(bundle); + final IKeyguardExternalViewProvider view = IKeyguardExternalViewProvider.Stub.asInterface(bindView); + assert (view != null); + + // Ensure the bundle we constructed with is intact + Bundle b = getService().getProvider().getOptionsImpl(); + assert (b == bundle); + + Mockito.reset(getService().getProvider().getTracker()); + view.onScreenTurnedOff(); + runOnServiceThread(new Runnable() { + @Override + public void run() { + Mockito.verify(getService().getProvider().getTracker(), Mockito.times(1)) + .onScreenTurnedOff(); + Mockito.verifyNoMoreInteractions(getService().getProvider().getTracker()); + } + }); + + Mockito.reset(getService().getProvider().getTracker()); + view.onKeyguardDismissed(); + runOnServiceThread(new Runnable() { + @Override + public void run() { + Mockito.verify(getService().getProvider().getTracker(), Mockito.times(1)) + .onKeyguardDismissed(); + Mockito.verifyNoMoreInteractions(getService().getProvider().getTracker()); + } + }); + + Mockito.reset(getService().getProvider().getTracker()); + view.onBouncerShowing(true); + runOnServiceThread(new Runnable() { + @Override + public void run() { + Mockito.verify(getService().getProvider().getTracker(), Mockito.times(1)) + .onBouncerShowing(Mockito.eq(true)); + Mockito.verifyNoMoreInteractions(getService().getProvider().getTracker()); + } + }); + + Mockito.reset(getService().getProvider().getTracker()); + view.onKeyguardShowing(true); + runOnServiceThread(new Runnable() { + @Override + public void run() { + Mockito.verify(getService().getProvider().getTracker(), Mockito.times(1)) + .onKeyguardShowing(Mockito.eq(true)); + Mockito.verifyNoMoreInteractions(getService().getProvider().getTracker()); + } + }); + + Mockito.reset(getService().getProvider().getTracker()); + view.onLockscreenSlideOffsetChanged(1f); + runOnServiceThread(new Runnable() { + @Override + public void run() { + Mockito.verify(getService().getProvider().getTracker(), Mockito.times(1)) + .onLockscreenSlideOffsetChanged(Mockito.eq(1f)); + Mockito.verifyNoMoreInteractions(getService().getProvider().getTracker()); + } + }); + + Mockito.reset(getService().getProvider().getTracker()); + view.onAttach(null); + runOnServiceThread(new Runnable() { + @Override + public void run() { + Mockito.verify(getService().getProvider().getTracker(), Mockito.times(1)).onAttach(); + Mockito.verifyNoMoreInteractions(getService().getProvider().getTracker()); + + ArgumentCaptor params = ArgumentCaptor + .forClass(WindowManager.LayoutParams.class); + ArgumentCaptor viewGroup = ArgumentCaptor + .forClass(ViewGroup.class); + Mockito.verify(mWindowManagerMock, Mockito.times(1)) + .addView(viewGroup.capture(), params.capture()); + + ViewGroup decorView = viewGroup.getAllValues().get(0); + assert (decorView.getChildCount() == 1); + assert (decorView.getChildAt(0) == getService().getProvider().getView()); + + WindowManager.LayoutParams param = params.getAllValues().get(0); + assert ((param.type & WindowManager.LayoutParams.TYPE_KEYGUARD_PANEL) != 0); + + int flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | + WindowManager.LayoutParams.FLAG_FULLSCREEN; + assert ((param.flags & flags) != 0); + + assert ((param.gravity & Gravity.LEFT | Gravity.TOP) != 0); + assert ((param.format & PixelFormat.TRANSPARENT) != 0); + } + }); + + Mockito.reset(getService().getProvider().getTracker()); + view.onDetach(); + runOnServiceThread(new Runnable() { + @Override + public void run() { + Mockito.verify(getService().getProvider().getTracker(), Mockito.times(1)).onDetach(); + Mockito.verifyNoMoreInteractions(getService().getProvider().getTracker()); + + ArgumentCaptor viewGroup = ArgumentCaptor + .forClass(ViewGroup.class); + Mockito.verify(mWindowManagerMock, Mockito.times(1)) + .removeView(viewGroup.capture()); + + ViewGroup decorView = viewGroup.getAllValues().get(0); + assert (decorView.getChildCount() == 1); + assert (decorView.getChildAt(0) == getService().getProvider().getView()); + } + }); + } + + public void testCallbackRegistration() throws Exception { + assert (getService() != null); + + IBinder bind = getService().onBind(new Intent()); + final IExternalViewProviderFactory provider = IExternalViewProviderFactory.Stub.asInterface(bind); + assert (provider != null); + + // Ensure on bind we were asked to create an external view + final Bundle bundle = new Bundle(); + IBinder bindView = provider.createExternalView(bundle); + final IKeyguardExternalViewProvider view = IKeyguardExternalViewProvider.Stub.asInterface(bindView); + assert (view != null); + + final IKeyguardExternalViewCallbacks.Stub callback = MockIBinderStubForInterface + .getMockInterface(IKeyguardExternalViewCallbacks.Stub.class); + view.registerCallback(callback); + getService().getProvider().requestDismissImpl(); + runOnServiceThread(new Runnable() { + @Override + public void run() { + try { + Mockito.verify(callback, Mockito.times(1)).requestDismiss(); + Mockito.verify(callback, Mockito.times(1)).asBinder(); + } catch (RemoteException e) { + Assert.fail(e.getMessage()); + } + Mockito.verifyNoMoreInteractions(callback); + } + }); + + Mockito.reset(callback); + final Intent i = new Intent(); + getService().getProvider().requestDismissAndStartActivityImpl(i); + runOnServiceThread(new Runnable() { + @Override + public void run() { + try { + Mockito.verify(callback, Mockito.times(1)).requestDismissAndStartActivity(Mockito.eq(i)); + } catch (RemoteException e) { + Assert.fail(e.getMessage()); + } + Mockito.verifyNoMoreInteractions(callback); + } + }); + + Mockito.reset(callback); + getService().getProvider().setInteractivityImpl(true); + runOnServiceThread(new Runnable() { + @Override + public void run() { + try { + Mockito.verify(callback, Mockito.times(1)).setInteractivity(Mockito.eq(true)); + } catch (RemoteException e) { + Assert.fail(e.getMessage()); + } + Mockito.verifyNoMoreInteractions(callback); + } + }); + + Mockito.reset(callback); + getService().getProvider().slideLockscreenInImpl(); + runOnServiceThread(new Runnable() { + @Override + public void run() { + try { + Mockito.verify(callback, Mockito.times(1)).slideLockscreenIn(); + } catch (RemoteException e) { + Assert.fail(e.getMessage()); + } + Mockito.verifyNoMoreInteractions(callback); + } + }); + + Mockito.reset(getService().getProvider().getTracker()); + getService().getProvider().collapseNotificationPanelImpl(); + runOnServiceThread(new Runnable() { + @Override + public void run() { + Mockito.verify(getService().getProvider().getTracker(), Mockito.times(1)) + .collapseNotificationPanelImpl(); + Mockito.verifyNoMoreInteractions(getService().getProvider().getTracker()); + } + }); + } + + public void testAlterWindow() throws Exception { + assert (getService() != null); + + IBinder bind = getService().onBind(new Intent()); + final IExternalViewProviderFactory provider = IExternalViewProviderFactory.Stub.asInterface(bind); + assert (provider != null); + + // Ensure on bind we were asked to create an external view + final Bundle bundle = new Bundle(); + IBinder bindView = provider.createExternalView(bundle); + final IKeyguardExternalViewProvider view = IKeyguardExternalViewProvider.Stub.asInterface(bindView); + assert (view != null); + + // Test visible false + Mockito.reset(mWindowManagerMock); + final Rect rect = new Rect(0, 0, 100, 100); + view.alterWindow(0, 0, 100, 100, false, rect); + runOnServiceThread(new Runnable() { + @Override + public void run() { + Mockito.verifyNoMoreInteractions(mWindowManagerMock); + } + }); + + // Test visible true + Mockito.reset(mWindowManagerMock); + view.alterWindow(10, 20, 30, 40, true, rect); + runOnServiceThread(new Runnable() { + @Override + public void run() { + ArgumentCaptor params = ArgumentCaptor + .forClass(WindowManager.LayoutParams.class); + ArgumentCaptor viewGroup = ArgumentCaptor + .forClass(ViewGroup.class); + Mockito.verify(mWindowManagerMock, Mockito.times(1)) + .updateViewLayout(viewGroup.capture(), params.capture()); + + ViewGroup decorView = viewGroup.getAllValues().get(0); + View child = decorView.getChildAt(0); + assert (decorView.getChildCount() == 1); + assert (child == getService().getProvider().getView()); + assert (child.getVisibility() == View.VISIBLE); + assert (child.getClipBounds().equals(rect)); + + WindowManager.LayoutParams param = params.getAllValues().get(0); + assert (param.x == 10); + assert (param.y == 20); + assert (param.width == 30); + assert (param.height == 40); + Mockito.verifyNoMoreInteractions(mWindowManagerMock); + } + }); + } +} diff --git a/tests/src/org/cyanogenmod/tests/externalviews/keyguardexternalviews/KeyguardExternalViewTest.java b/tests/src/org/cyanogenmod/tests/externalviews/keyguardexternalviews/KeyguardExternalViewTest.java new file mode 100644 index 0000000..a12ac27 --- /dev/null +++ b/tests/src/org/cyanogenmod/tests/externalviews/keyguardexternalviews/KeyguardExternalViewTest.java @@ -0,0 +1,254 @@ +/** + * Copyright (c) 2016, The CyanogenMod 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 org.cyanogenmod.tests.externalviews.keyguardexternalviews; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.test.AndroidTestCase; +import android.view.WindowManager; +import cyanogenmod.externalviews.IExternalViewProviderFactory; +import cyanogenmod.externalviews.IKeyguardExternalViewCallbacks; +import cyanogenmod.externalviews.IKeyguardExternalViewProvider; +import cyanogenmod.externalviews.KeyguardExternalView; +import org.cyanogenmod.tests.common.MockIBinderStubForInterface; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public class KeyguardExternalViewTest extends AndroidTestCase { + private IKeyguardExternalViewProvider.Stub mIKeyguardExternalViewProvider; + private IExternalViewProviderFactory.Stub mExternalViewProviderFactory; + private WindowManager mWindowManagerMock; + private Context mContextMock; + private ServiceConnection mServiceConnection; + private IKeyguardExternalViewCallbacks mKeyguardCallback; + private KeyguardExternalView mExternalView; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + WindowManager windowManager = (WindowManager) getContext() + .getSystemService(Context.WINDOW_SERVICE); + + // Ensure we mock context but invoke non intercepted calls to impl + mContextMock = Mockito.mock(Context.class, Mockito.CALLS_REAL_METHODS); + + // Needed since ExternalView's base class instantiates things off this. + // We can't use a spy here since ContextImpl is hidden (PowerMock ?) + // For now just redirect these to the test context + Mockito.doReturn(getContext().getApplicationInfo()).when(mContextMock).getApplicationInfo(); + Mockito.doReturn(getContext().getResources()).when(mContextMock).getResources(); + Mockito.doReturn(getContext().getTheme()).when(mContextMock).getTheme(); + + // Mock window manager to ensure we don't try to add the windows + mWindowManagerMock = Mockito.mock(WindowManager.class); + Mockito.doReturn(mWindowManagerMock).when(mContextMock).getSystemService(Context.WINDOW_SERVICE); + Mockito.doReturn(windowManager.getDefaultDisplay()).when(mWindowManagerMock).getDefaultDisplay(); + + // Mock the viewProvider/KeyguardView to keep track of callback invocations + mIKeyguardExternalViewProvider = MockIBinderStubForInterface + .getMockInterface(IKeyguardExternalViewProvider.Stub.class); + mExternalViewProviderFactory = MockIBinderStubForInterface + .getMockInterface(IExternalViewProviderFactory.Stub.class); + + // Ensure we return our view provider when the factory is asked to create external view + Mockito.doReturn(mIKeyguardExternalViewProvider) + .when(mExternalViewProviderFactory) + .createExternalView(Mockito.any(Bundle.class)); + + // Store the callback object registered by the view + Mockito.doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + mKeyguardCallback = (IKeyguardExternalViewCallbacks) invocation.getArguments()[0]; + return null; + } + }).when(mIKeyguardExternalViewProvider) + .registerCallback(Mockito.notNull(IKeyguardExternalViewCallbacks.class)); + + // Simulate bound service connection when bindService is invoked + Mockito.doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + ServiceConnection connection = (ServiceConnection) invocation.getArguments()[1]; + connection.onServiceConnected(null, mExternalViewProviderFactory); + mServiceConnection = connection; + return true; + } + }).when(mContextMock).bindService(Mockito.any(Intent.class), + Mockito.any(ServiceConnection.class), Mockito.anyInt()); + } + + public void testValidServiceBind() { + mExternalView = new KeyguardExternalView(mContextMock, null, new ComponentName("", "")); + + // Ensure we attempted to bind to the service + Mockito.verify(mContextMock, Mockito.times(1)).bindService(Mockito.any(Intent.class), + Mockito.any(ServiceConnection.class), Mockito.anyInt()); + } + + public void testInvalidServiceBind() { + mExternalView = new KeyguardExternalView(mContextMock, null, null); + // Ensure we did not attempt to bind to the service + Mockito.verify(mContextMock, Mockito.never()).bindService(Mockito.any(Intent.class), + Mockito.any(ServiceConnection.class), Mockito.anyInt()); + } + + public void testServiceAndCallbacksRegistered() throws RemoteException { + testValidServiceBind(); + + // Ensure a view was asked to be created + Mockito.verify(mExternalViewProviderFactory, Mockito.times(1)) + .createExternalView(Mockito.any(Bundle.class)); + + // Ensure callbacks were registered + Mockito.verify(mIKeyguardExternalViewProvider, Mockito.times(1)) + .registerCallback(Mockito.notNull(IKeyguardExternalViewCallbacks.class)); + + assertNotNull(mKeyguardCallback); + } + + public void testServiceUnbindAndCallbacksUnRegistered() throws RemoteException { + testServiceAndCallbacksRegistered(); + + assertNotNull(mServiceConnection); + mServiceConnection.onServiceDisconnected(null); + + // Ensure callbacks were registered + Mockito.verify(mIKeyguardExternalViewProvider, Mockito.times(1)) + .unregisterCallback(Mockito.notNull(IKeyguardExternalViewCallbacks.class)); + } + + // Ensure provider is alerted view callbacks + public void testViewProviderCallbacks() throws RemoteException { + testServiceAndCallbacksRegistered(); + + Mockito.reset(mIKeyguardExternalViewProvider); + mExternalView.onKeyguardShowing(true); + Mockito.verify(mIKeyguardExternalViewProvider, + Mockito.times(1)).onKeyguardShowing(Mockito.anyBoolean()); + Mockito.verifyNoMoreInteractions(mIKeyguardExternalViewProvider); + + Mockito.reset(mIKeyguardExternalViewProvider); + mExternalView.onAttachedToWindow(); + Mockito.verify(mIKeyguardExternalViewProvider, + Mockito.times(1)).onAttach(Mockito.any(IBinder.class)); + Mockito.verifyNoMoreInteractions(mIKeyguardExternalViewProvider); + + Mockito.reset(mIKeyguardExternalViewProvider); + mExternalView.onDetachedFromWindow(); + Mockito.verify(mIKeyguardExternalViewProvider, Mockito.times(1)).onDetach(); + Mockito.verifyNoMoreInteractions(mIKeyguardExternalViewProvider); + + Mockito.reset(mIKeyguardExternalViewProvider); + mExternalView.onBouncerShowing(true); + Mockito.verify(mIKeyguardExternalViewProvider, + Mockito.times(1)).onBouncerShowing(Mockito.anyBoolean()); + Mockito.verifyNoMoreInteractions(mIKeyguardExternalViewProvider); + + Mockito.reset(mIKeyguardExternalViewProvider); + mExternalView.onKeyguardDismissed(); + Mockito.verify(mIKeyguardExternalViewProvider, + Mockito.times(1)).onKeyguardDismissed(); + Mockito.verifyNoMoreInteractions(mIKeyguardExternalViewProvider); + + Mockito.reset(mIKeyguardExternalViewProvider); + mExternalView.onLockscreenSlideOffsetChanged(1f); + Mockito.verify(mIKeyguardExternalViewProvider, + Mockito.times(1)).onLockscreenSlideOffsetChanged(Mockito.eq(1f)); + Mockito.verifyNoMoreInteractions(mIKeyguardExternalViewProvider); + + Mockito.reset(mIKeyguardExternalViewProvider); + mExternalView.onScreenTurnedOff(); + Mockito.verify(mIKeyguardExternalViewProvider, Mockito.times(1)).onScreenTurnedOff(); + Mockito.verifyNoMoreInteractions(mIKeyguardExternalViewProvider); + + Mockito.reset(mIKeyguardExternalViewProvider); + mExternalView.onScreenTurnedOn(); + Mockito.verify(mIKeyguardExternalViewProvider, Mockito.times(1)).onScreenTurnedOn(); + Mockito.verifyNoMoreInteractions(mIKeyguardExternalViewProvider); + } + + public void testWindowMovement() throws RemoteException { + testServiceAndCallbacksRegistered(); + Mockito.reset(mIKeyguardExternalViewProvider); + + mExternalView.setLeft(0); + mExternalView.setTop(0); + mExternalView.setRight(100); + mExternalView.setBottom(100); + + mExternalView.onPreDraw(); + Mockito.verify(mIKeyguardExternalViewProvider, Mockito.times(1)) + .alterWindow(Mockito.eq(0), Mockito.eq(0), Mockito.anyInt(), + Mockito.anyInt(), Mockito.eq(true), Mockito.any(Rect.class)); + Mockito.verifyNoMoreInteractions(mIKeyguardExternalViewProvider); + } + + public void testWindowAttachmentCallbacks() throws RemoteException { + testServiceAndCallbacksRegistered(); + + KeyguardExternalView.OnWindowAttachmentChangedListener callback = + Mockito.mock(KeyguardExternalView.OnWindowAttachmentChangedListener.class); + mExternalView.registerOnWindowAttachmentChangedListener(callback); + + mKeyguardCallback.onAttachedToWindow(); + Mockito.verify(callback, Mockito.times(1)).onAttachedToWindow(); + Mockito.verifyNoMoreInteractions(callback); + + mKeyguardCallback.onDetachedFromWindow(); + Mockito.verify(callback, Mockito.times(1)).onDetachedFromWindow(); + Mockito.verifyNoMoreInteractions(callback); + } + + public void testKeyguardViewCallbacks() throws RemoteException { + testServiceAndCallbacksRegistered(); + + KeyguardExternalView.KeyguardExternalViewCallbacks callback = Mockito.mock( + KeyguardExternalView.KeyguardExternalViewCallbacks.class); + mExternalView.registerKeyguardExternalViewCallback(callback); + + mKeyguardCallback.requestDismiss(); + Mockito.verify(callback, Mockito.times(1)).requestDismiss(); + Mockito.verifyNoMoreInteractions(callback); + + Intent i = new Intent(); + mKeyguardCallback.requestDismissAndStartActivity(i); + Mockito.verify(callback, Mockito.times(1)) + .requestDismissAndStartActivity(Mockito.eq(i)); + Mockito.verifyNoMoreInteractions(callback); + + mKeyguardCallback.setInteractivity(true); + assert(mExternalView.isInteractive()); + Mockito.verifyNoMoreInteractions(callback); + + mKeyguardCallback.slideLockscreenIn(); + Mockito.verify(callback, Mockito.times(1)).slideLockscreenIn(); + Mockito.verifyNoMoreInteractions(callback); + + mExternalView.binderDied(); + Mockito.verify(callback, Mockito.times(1)).providerDied(); + Mockito.verifyNoMoreInteractions(callback); + } +}