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
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8c1ab56..5fe84d5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3220,6 +3220,26 @@
                 <action android:name="android.service.quicksettings.action.QS_TILE" />
             </intent-filter>
         </service>
+        <service
+            android:name=".development.qstile.DevelopmentTiles$WindowTrace"
+            android:label="@string/window_trace_quick_settings_title"
+            android:icon="@drawable/tile_icon_window_trace"
+            android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
+            android:enabled="false">
+            <intent-filter>
+                <action android:name="android.service.quicksettings.action.QS_TILE" />
+            </intent-filter>
+        </service>
+        <service
+            android:name=".development.qstile.DevelopmentTiles$LayerTrace"
+            android:label="@string/layer_trace_quick_settings_title"
+            android:icon="@drawable/tile_icon_layer_trace"
+            android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
+            android:enabled="false">
+            <intent-filter>
+                <action android:name="android.service.quicksettings.action.QS_TILE" />
+            </intent-filter>
+        </service>
 
         <activity android:name=".HelpTrampoline"
             android:exported="true"
diff --git a/res/drawable/tile_icon_layer_trace.xml b/res/drawable/tile_icon_layer_trace.xml
new file mode 100644
index 0000000..21dafd3
--- /dev/null
+++ b/res/drawable/tile_icon_layer_trace.xml
@@ -0,0 +1,29 @@
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
+    <path
+        android:pathData="M11.709,11.712 L7.061,8.098 6.039,8.893l5.676,4.415 5.676,-4.415 -1.028,-0.801zM11.716,10.11 L16.357,6.496 17.392,5.695 11.716,1.281 6.039,5.695 7.067,6.496Z"
+        android:fillColor="#FFFFFFFF"/>
+    <path
+        android:pathData="m20.27,15.235c0,0.82 -0.671,1.491 -1.491,1.491 -0.134,0 -0.261,-0.015 -0.38,-0.052l-2.654,2.646C15.782,19.439 15.797,19.573 15.797,19.708c0,0.82 -0.671,1.491 -1.491,1.491 -0.82,0 -1.491,-0.671 -1.491,-1.491 0,-0.134 0.015,-0.268 0.052,-0.388L10.966,17.419C10.847,17.456 10.713,17.471 10.579,17.471 10.444,17.471 10.31,17.456 10.191,17.419L6.799,20.818C6.836,20.938 6.851,21.064 6.851,21.199 6.851,22.019 6.18,22.689 5.36,22.689 4.54,22.689 3.869,22.019 3.869,21.199c0,-0.82 0.671,-1.491 1.491,-1.491 0.134,0 0.261,0.015 0.38,0.052L9.14,16.368C9.103,16.249 9.088,16.114 9.088,15.98 9.088,15.16 9.759,14.489 10.579,14.489c0.82,0 1.491,0.671 1.491,1.491 0,0.134 -0.015,0.268 -0.052,0.388l1.901,1.901C14.038,18.232 14.172,18.217 14.306,18.217c0.134,0 0.268,0.015 0.388,0.052L17.34,15.615C17.303,15.496 17.288,15.369 17.288,15.235c0,-0.82 0.671,-1.491 1.491,-1.491 0.82,0 1.491,0.671 1.491,1.491z"
+        android:fillColor="#FFFFFFFF"/>
+</vector>
+
diff --git a/res/drawable/tile_icon_window_trace.xml b/res/drawable/tile_icon_window_trace.xml
new file mode 100644
index 0000000..2563049
--- /dev/null
+++ b/res/drawable/tile_icon_window_trace.xml
@@ -0,0 +1,29 @@
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
+    <path
+        android:pathData="M17.115,1.535L9.984,1.535C9.38,1.535 8.887,2.029 8.887,2.632L8.887,5.923L7.241,5.923C6.638,5.923 6.144,6.417 6.144,7.021l0,5.486c0,0.603 0.494,1.097 1.097,1.097l7.131,0c0.603,0 1.097,-0.494 1.097,-1.097l0,-3.291l1.646,0c0.603,0 1.097,-0.494 1.097,-1.097L18.212,2.632C18.212,2.029 17.718,1.535 17.115,1.535ZM14.372,12.506L7.241,12.506l0,-4.388l7.131,0zM17.115,8.118L15.469,8.118L15.469,7.021C15.469,6.417 14.976,5.923 14.372,5.923L9.984,5.923L9.984,3.729l7.131,0z"
+        android:fillColor="#FFFFFFFF"/>
+    <path
+        android:pathData="m20.27,15.235c0,0.82 -0.671,1.491 -1.491,1.491 -0.134,0 -0.261,-0.015 -0.38,-0.052l-2.654,2.646C15.782,19.439 15.797,19.573 15.797,19.708c0,0.82 -0.671,1.491 -1.491,1.491 -0.82,0 -1.491,-0.671 -1.491,-1.491 0,-0.134 0.015,-0.268 0.052,-0.388L10.966,17.419C10.847,17.456 10.713,17.471 10.579,17.471 10.444,17.471 10.31,17.456 10.191,17.419L6.799,20.818C6.836,20.938 6.851,21.064 6.851,21.199 6.851,22.019 6.18,22.689 5.36,22.689 4.54,22.689 3.869,22.019 3.869,21.199c0,-0.82 0.671,-1.491 1.491,-1.491 0.134,0 0.261,0.015 0.38,0.052L9.14,16.368C9.103,16.249 9.088,16.114 9.088,15.98 9.088,15.16 9.759,14.489 10.579,14.489c0.82,0 1.491,0.671 1.491,1.491 0,0.134 -0.015,0.268 -0.052,0.388l1.901,1.901C14.038,18.232 14.172,18.217 14.306,18.217c0.134,0 0.268,0.015 0.388,0.052L17.34,15.615C17.303,15.496 17.288,15.369 17.288,15.235c0,-0.82 0.671,-1.491 1.491,-1.491 0.82,0 1.491,0.671 1.491,1.491z"
+        android:fillColor="#FFFFFFFF"/>
+</vector>
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8c7b6f4..242e36e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8683,6 +8683,12 @@
     <!-- [CHAR LIMIT=60] Name of dev option to enable extra quick settings tiles -->
     <string name="quick_settings_developer_tiles">Quick settings developer tiles</string>
 
+    <!-- [CHAR LIMIT=25] Title of developer tile to toggle window trace -->
+    <string name="window_trace_quick_settings_title">Window Trace</string>
+
+    <!-- [CHAR LIMIT=25] Title of developer tile to toggle layer trace -->
+    <string name="layer_trace_quick_settings_title">Layer Trace</string>
+
     <!-- [CHAR LIMIT=60] Title of work profile setting page -->
     <string name="managed_profile_settings_title">Work profile settings</string>
     <!-- [CHAR LIMIT=60] The preference title for enabling cross-profile remote contact search -->
diff --git a/src/com/android/settings/development/qstile/DevelopmentTiles.java b/src/com/android/settings/development/qstile/DevelopmentTiles.java
index bc3fcb5..fea6588 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 @@
             } 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 0000000..8c2ed35
--- /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 09e48d3..8522b99 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 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 965c959..0000000
--- 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 a5cfa22..32768b6 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 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 0000000..594b96c
--- /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 0000000..3c4d9ba
--- /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 0000000..6e42fea
--- /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;
+    }
+}