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;
+ }
+}