From dc6bc4820e619e50b75b64929b1f6964aad90bbe Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 10 Jan 2018 16:07:23 -0800 Subject: [PATCH] Add developer tiles for layer and window trace Bug: 64831661 Test: Toggle layer and window trace from new QS Tile Test: make RunSettingsRoboTests ROBOTEST_FILTER=LayerTraceTest && make RunSettingsRoboTests ROBOTEST_FILTER=WindowTraceTest Change-Id: I86b63361821e1bf5dd6a934e7fcb7e810740b74a --- AndroidManifest.xml | 20 +++ res/drawable/tile_icon_layer_trace.xml | 29 +++++ res/drawable/tile_icon_window_trace.xml | 29 +++++ res/values/strings.xml | 6 + .../development/qstile/DevelopmentTiles.java | 109 ++++++++++++++++ .../wrapper/IWindowManagerWrapper.java | 55 ++++++++ ...dwareOverlaysPreferenceControllerTest.java | 1 + .../settings/development/ShadowParcel.java | 20 --- ...urfaceUpdatesPreferenceControllerTest.java | 1 + .../development/qstile/LayerTraceTest.java | 123 ++++++++++++++++++ .../development/qstile/WindowTraceTest.java | 97 ++++++++++++++ .../testutils/shadow/ShadowParcel.java | 32 +++++ 12 files changed, 502 insertions(+), 20 deletions(-) create mode 100644 res/drawable/tile_icon_layer_trace.xml create mode 100644 res/drawable/tile_icon_window_trace.xml create mode 100644 src/com/android/settings/wrapper/IWindowManagerWrapper.java delete mode 100644 tests/robotests/src/com/android/settings/development/ShadowParcel.java create mode 100644 tests/robotests/src/com/android/settings/development/qstile/LayerTraceTest.java create mode 100644 tests/robotests/src/com/android/settings/development/qstile/WindowTraceTest.java create mode 100644 tests/robotests/src/com/android/settings/testutils/shadow/ShadowParcel.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8c1ab5648b0..5fe84d5fd5e 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3220,6 +3220,26 @@ + + + + + + + + + + + + + + + diff --git a/res/drawable/tile_icon_window_trace.xml b/res/drawable/tile_icon_window_trace.xml new file mode 100644 index 00000000000..25630494476 --- /dev/null +++ b/res/drawable/tile_icon_window_trace.xml @@ -0,0 +1,29 @@ + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 8c7b6f4789f..242e36ef01e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8683,6 +8683,12 @@ Quick settings developer tiles + + Window Trace + + + Layer Trace + Work profile settings diff --git a/src/com/android/settings/development/qstile/DevelopmentTiles.java b/src/com/android/settings/development/qstile/DevelopmentTiles.java index bc3fcb5af77..fea658869c2 100644 --- a/src/com/android/settings/development/qstile/DevelopmentTiles.java +++ b/src/com/android/settings/development/qstile/DevelopmentTiles.java @@ -16,20 +16,27 @@ package com.android.settings.development.qstile; +import android.os.IBinder; +import android.os.Parcel; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemProperties; import android.provider.Settings; import android.service.quicksettings.Tile; import android.service.quicksettings.TileService; +import android.support.annotation.VisibleForTesting; +import android.util.Log; import android.view.IWindowManager; import android.view.ThreadedRenderer; import android.view.View; import android.view.WindowManagerGlobal; import com.android.internal.app.LocalePicker; +import com.android.settings.wrapper.IWindowManagerWrapper; import com.android.settingslib.development.SystemPropPoker; public abstract class DevelopmentTiles extends TileService { + private static final String TAG = "DevelopmentTiles"; protected abstract boolean isEnabled(); @@ -131,4 +138,106 @@ public abstract class DevelopmentTiles extends TileService { } catch (RemoteException e) { } } } + + /** + * Tile to toggle Window Trace. + */ + public static class WindowTrace extends DevelopmentTiles { + @VisibleForTesting + IWindowManagerWrapper mWindowManager; + + @Override + public void onCreate() { + super.onCreate(); + mWindowManager = new IWindowManagerWrapper(WindowManagerGlobal + .getWindowManagerService()); + } + + @Override + protected boolean isEnabled() { + try { + return mWindowManager.isWindowTraceEnabled(); + } catch (RemoteException e) { + Log.e(TAG, + "Could not get window trace status, defaulting to false." + e.toString()); + } + return false; + } + + @Override + protected void setIsEnabled(boolean isEnabled) { + try { + if (isEnabled) { + mWindowManager.startWindowTrace(); + } else { + mWindowManager.stopWindowTrace(); + } + } catch (RemoteException e) { + Log.e(TAG, "Could not set window trace status." + e.toString()); + } + } + } + + /** + * Tile to toggle Layer Trace. + */ + public static class LayerTrace extends DevelopmentTiles { + @VisibleForTesting + static final int SURFACE_FLINGER_LAYER_TRACE_CONTROL_CODE = 1025; + @VisibleForTesting + static final int SURFACE_FLINGER_LAYER_TRACE_STATUS_CODE = 1026; + @VisibleForTesting + IBinder mSurfaceFlinger; + + @Override + public void onCreate() { + super.onCreate(); + mSurfaceFlinger = ServiceManager.getService("SurfaceFlinger"); + } + + @Override + protected boolean isEnabled() { + boolean surfaceTraceEnabled = false; + Parcel reply = null; + Parcel data = null; + try { + if (mSurfaceFlinger != null) { + reply = Parcel.obtain(); + data = Parcel.obtain(); + data.writeInterfaceToken("android.ui.ISurfaceComposer"); + mSurfaceFlinger.transact(SURFACE_FLINGER_LAYER_TRACE_STATUS_CODE, + data, reply, 0 /* flags */ ); + surfaceTraceEnabled = reply.readBoolean(); + } + } catch (RemoteException e) { + Log.e(TAG, "Could not get layer trace status, defaulting to false." + e.toString()); + } finally { + if (data != null) { + data.recycle(); + reply.recycle(); + } + } + return surfaceTraceEnabled; + } + + @Override + protected void setIsEnabled(boolean isEnabled) { + Parcel data = null; + try { + if (mSurfaceFlinger != null) { + data = Parcel.obtain(); + data.writeInterfaceToken("android.ui.ISurfaceComposer"); + data.writeInt(isEnabled ? 1 : 0); + mSurfaceFlinger.transact(SURFACE_FLINGER_LAYER_TRACE_CONTROL_CODE, + data, null, 0 /* flags */); + } + } catch (RemoteException e) { + Log.e(TAG, "Could not set layer tracing." + e.toString()); + } finally { + if (data != null) { + data.recycle(); + } + } + } + } } \ No newline at end of file diff --git a/src/com/android/settings/wrapper/IWindowManagerWrapper.java b/src/com/android/settings/wrapper/IWindowManagerWrapper.java new file mode 100644 index 00000000000..8c2ed35935f --- /dev/null +++ b/src/com/android/settings/wrapper/IWindowManagerWrapper.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 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 com.android.settings.wrapper; + +import android.os.RemoteException; +import android.view.IWindowManager; + +/** + * This class replicates a subset of the android.view.IWindowManager. The class + * exists so that we can use a thin wrapper around the IWindowManager in production code + * and a mock in tests. + */ +public class IWindowManagerWrapper { + + private final IWindowManager mWindowManager; + + public IWindowManagerWrapper(IWindowManager wm) { + mWindowManager = wm; + } + + /** + * Returns true if window trace is enabled. + */ + public boolean isWindowTraceEnabled() throws RemoteException { + return mWindowManager.isWindowTraceEnabled(); + } + + /** + * Starts a window trace. + */ + public void startWindowTrace() throws RemoteException { + mWindowManager.startWindowTrace(); + } + + /** + * Stops a window trace. + */ + public void stopWindowTrace() throws RemoteException { + mWindowManager.stopWindowTrace(); + } +} diff --git a/tests/robotests/src/com/android/settings/development/HardwareOverlaysPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/HardwareOverlaysPreferenceControllerTest.java index 09e48d3971e..8522b995c53 100644 --- a/tests/robotests/src/com/android/settings/development/HardwareOverlaysPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/HardwareOverlaysPreferenceControllerTest.java @@ -37,6 +37,7 @@ import android.support.v7.preference.PreferenceScreen; import com.android.settings.TestConfig; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowParcel; import org.junit.Before; import org.junit.Test; diff --git a/tests/robotests/src/com/android/settings/development/ShadowParcel.java b/tests/robotests/src/com/android/settings/development/ShadowParcel.java deleted file mode 100644 index 965c959cb0f..00000000000 --- a/tests/robotests/src/com/android/settings/development/ShadowParcel.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.android.settings.development; - -import android.os.Parcel; - -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; - -/** - * This class provides helpers to test logic that reads from parcels. - */ -@Implements(Parcel.class) -public class ShadowParcel { - - static int sReadIntResult; - - @Implementation - public int readInt() { - return sReadIntResult; - } -} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/development/ShowSurfaceUpdatesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/ShowSurfaceUpdatesPreferenceControllerTest.java index a5cfa2274da..32768b652f9 100644 --- a/tests/robotests/src/com/android/settings/development/ShowSurfaceUpdatesPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/ShowSurfaceUpdatesPreferenceControllerTest.java @@ -37,6 +37,7 @@ import android.support.v7.preference.PreferenceScreen; import com.android.settings.TestConfig; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowParcel; import org.junit.Before; import org.junit.Test; diff --git a/tests/robotests/src/com/android/settings/development/qstile/LayerTraceTest.java b/tests/robotests/src/com/android/settings/development/qstile/LayerTraceTest.java new file mode 100644 index 00000000000..594b96c8e71 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/qstile/LayerTraceTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2018 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 com.android.settings.development.qstile; + +import static com.android.settings.development.qstile.DevelopmentTiles.LayerTrace + .SURFACE_FLINGER_LAYER_TRACE_CONTROL_CODE; +import static com.android.settings.development.qstile.DevelopmentTiles.LayerTrace + .SURFACE_FLINGER_LAYER_TRACE_STATUS_CODE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.shadow.ShadowParcel; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class LayerTraceTest { + @Mock + private IBinder mSurfaceFlinger; + + private DevelopmentTiles.LayerTrace mLayerTraceTile; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mLayerTraceTile = spy(new DevelopmentTiles.LayerTrace()); + mLayerTraceTile.onCreate(); + ReflectionHelpers.setField(mLayerTraceTile, "mSurfaceFlinger", mSurfaceFlinger); + } + + @After + public void after() { + verifyNoMoreInteractions(mSurfaceFlinger); + } + + @Test + @Config(shadows = {ShadowParcel.class}) + public void sfReturnsTraceEnabled_shouldReturnEnabled() throws RemoteException { + ShadowParcel.sReadBoolResult = true; + assertThat(mLayerTraceTile.isEnabled()).isTrue(); + verify(mSurfaceFlinger) + .transact(eq(SURFACE_FLINGER_LAYER_TRACE_STATUS_CODE), any(), any(), + eq(0 /* flags */)); + } + + @Test + @Config(shadows = {ShadowParcel.class}) + public void sfReturnsTraceDisabled_shouldReturnDisabled() throws RemoteException { + ShadowParcel.sReadBoolResult = false; + assertThat(mLayerTraceTile.isEnabled()).isFalse(); + verify(mSurfaceFlinger) + .transact(eq(SURFACE_FLINGER_LAYER_TRACE_STATUS_CODE), any(), any(), + eq(0 /* flags */)); + } + + @Test + public void sfUnavailable_shouldReturnDisabled() throws RemoteException { + ReflectionHelpers.setField(mLayerTraceTile, "mSurfaceFlinger", null); + assertThat(mLayerTraceTile.isEnabled()).isFalse(); + } + + @Test + @Config(shadows = {ShadowParcel.class}) + public void setIsEnableTrue_shouldEnableLayerTrace() throws RemoteException { + mLayerTraceTile.setIsEnabled(true); + assertThat(ShadowParcel.sWriteIntResult).isEqualTo(1); + verify(mSurfaceFlinger) + .transact(eq(SURFACE_FLINGER_LAYER_TRACE_CONTROL_CODE), any(), isNull(), + eq(0 /* flags */)); + } + + @Test + @Config(shadows = {ShadowParcel.class}) + public void setIsEnableFalse_shouldDisableLayerTrace() throws RemoteException { + mLayerTraceTile.setIsEnabled(false); + assertThat(ShadowParcel.sWriteIntResult).isEqualTo(0); + verify(mSurfaceFlinger) + .transact(eq(SURFACE_FLINGER_LAYER_TRACE_CONTROL_CODE), any(), isNull(), + eq(0 /* flags */)); + } + + @Test + public void setIsEnableAndSfUnavailable_shouldDoNothing() throws RemoteException { + ReflectionHelpers.setField(mLayerTraceTile, "mSurfaceFlinger", null); + mLayerTraceTile.setIsEnabled(true); + mLayerTraceTile.setIsEnabled(false); + } +} diff --git a/tests/robotests/src/com/android/settings/development/qstile/WindowTraceTest.java b/tests/robotests/src/com/android/settings/development/qstile/WindowTraceTest.java new file mode 100644 index 00000000000..3c4d9baad3b --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/qstile/WindowTraceTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2018 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 com.android.settings.development.qstile; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.os.RemoteException; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.shadow.ShadowParcel; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.wrapper.IWindowManagerWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class WindowTraceTest { + @Mock + private IWindowManagerWrapper mWindowManager; + + private DevelopmentTiles.WindowTrace mWindowTrace; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mWindowTrace = spy(new DevelopmentTiles.WindowTrace()); + mWindowTrace.onCreate(); + ReflectionHelpers.setField(mWindowTrace, "mWindowManager", mWindowManager); + } + + @Test + public void wmReturnsTraceEnabled_shouldReturnEnabled() throws RemoteException { + doReturn(true).when(mWindowManager).isWindowTraceEnabled(); + assertThat(mWindowTrace.isEnabled()).isTrue(); + } + + @Test + public void wmReturnsTraceDisabled_shouldReturnDisabled() throws RemoteException { + doReturn(false).when(mWindowManager).isWindowTraceEnabled(); + assertThat(mWindowTrace.isEnabled()).isFalse(); + } + + @Test + public void wmThrowsRemoteException_shouldReturnDisabled() throws RemoteException { + doThrow(new RemoteException("Unknown")) + .when(mWindowManager).isWindowTraceEnabled(); + assertThat(mWindowTrace.isEnabled()).isFalse(); + } + + @Test + public void setIsEnableTrue_shouldEnableWindowTrace() throws RemoteException { + mWindowTrace.setIsEnabled(true); + verify(mWindowManager).startWindowTrace(); + verifyNoMoreInteractions(mWindowManager); + } + + @Test + @Config(shadows = {ShadowParcel.class}) + public void setIsEnableFalse_shouldDisableWindowTrace() throws RemoteException { + mWindowTrace.setIsEnabled(false); + verify(mWindowManager).stopWindowTrace(); + verifyNoMoreInteractions(mWindowManager); + } + + @Test + public void setIsEnableAndWmThrowsRemoteException_shouldDoNothing() throws RemoteException { + doThrow(new RemoteException("Unknown")).when(mWindowManager).isWindowTraceEnabled(); + mWindowTrace.setIsEnabled(true); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowParcel.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowParcel.java new file mode 100644 index 00000000000..6e42fea56d5 --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowParcel.java @@ -0,0 +1,32 @@ +package com.android.settings.testutils.shadow; + +import android.os.Parcel; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +/** + * This class provides helpers to test logic that reads from parcels. + */ +@Implements(Parcel.class) +public class ShadowParcel { + + public static int sReadIntResult; + public static int sWriteIntResult; + public static boolean sReadBoolResult; + + @Implementation + public int readInt() { + return sReadIntResult; + } + + @Implementation + public void writeInt(int val) { + sWriteIntResult = val; + } + + @Implementation + public boolean readBoolean() { + return sReadBoolResult; + } +}