diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6aebfbbb027..cca450a4df5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4392,6 +4392,20 @@
android:value="true"/>
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index dab3719198c..a1c2d2925e8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -9960,6 +9960,9 @@
Disable automatic revocation of adb authorizations for systems that have not reconnected within the default (7 days) or user-configured (minimum 1 day) amount of time.
+
+ Winscope Trace
+
Sensors Off
diff --git a/src/com/android/settings/development/qstile/DevelopmentTiles.java b/src/com/android/settings/development/qstile/DevelopmentTiles.java
index cf0d4d1847f..a5bbd4a4231 100644
--- a/src/com/android/settings/development/qstile/DevelopmentTiles.java
+++ b/src/com/android/settings/development/qstile/DevelopmentTiles.java
@@ -29,7 +29,9 @@ import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
import android.net.Uri;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
+import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -43,7 +45,10 @@ import android.view.ThreadedRenderer;
import android.view.WindowManagerGlobal;
import android.widget.Toast;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.app.LocalePicker;
+import com.android.internal.inputmethod.ImeTracing;
import com.android.internal.statusbar.IStatusBarService;
import com.android.settings.development.WirelessDebuggingPreferenceController;
import com.android.settings.overlay.FeatureFactory;
@@ -198,6 +203,173 @@ public abstract class DevelopmentTiles extends TileService {
}
}
+ /**
+ * Tile to toggle Winscope trace which consists of Window and Layer traces.
+ */
+ public static class WinscopeTrace extends DevelopmentTiles {
+ @VisibleForTesting
+ static final int SURFACE_FLINGER_LAYER_TRACE_CONTROL_CODE = 1025;
+ @VisibleForTesting
+ static final int SURFACE_FLINGER_LAYER_TRACE_STATUS_CODE = 1026;
+ private static final String VIEW_CAPTURE_ENABLED = "view_capture_enabled";
+ private IBinder mSurfaceFlinger;
+ private IWindowManager mWindowManager;
+ private ImeTracing mImeTracing;
+ private Toast mToast;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mWindowManager = WindowManagerGlobal.getWindowManagerService();
+ mSurfaceFlinger = ServiceManager.getService("SurfaceFlinger");
+ mImeTracing = ImeTracing.getInstance();
+ Context context = getApplicationContext();
+ CharSequence text = "Trace files written to /data/misc/wmtrace";
+ mToast = Toast.makeText(context, text, Toast.LENGTH_LONG);
+ }
+
+ private boolean isWindowTraceEnabled() {
+ try {
+ return mWindowManager.isWindowTraceEnabled();
+ } catch (RemoteException e) {
+ Log.e(TAG,
+ "Could not get window trace status, defaulting to false." + e.toString());
+ }
+ return false;
+ }
+
+ private boolean isLayerTraceEnabled() {
+ boolean layerTraceEnabled = 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 */);
+ layerTraceEnabled = 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 layerTraceEnabled;
+ }
+
+ private boolean isSystemUiTracingEnabled() {
+ try {
+ final IStatusBarService statusBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.checkService(Context.STATUS_BAR_SERVICE));
+ if (statusBarService != null) {
+ return statusBarService.isTracing();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not get system ui tracing status." + e.toString());
+ }
+ return false;
+ }
+
+ private boolean isImeTraceEnabled() {
+ return mImeTracing.isEnabled();
+ }
+
+ private boolean isViewCaptureEnabled() {
+ // Add null checking to avoid test case failure.
+ if (getApplicationContext() != null) {
+ return Settings.Global.getInt(getApplicationContext().getContentResolver(),
+ VIEW_CAPTURE_ENABLED, 0) != 0;
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean isEnabled() {
+ return isWindowTraceEnabled() || isLayerTraceEnabled() || isSystemUiTracingEnabled()
+ || isImeTraceEnabled() || isViewCaptureEnabled();
+ }
+
+ private void setWindowTraceEnabled(boolean isEnabled) {
+ try {
+ if (isEnabled) {
+ mWindowManager.startWindowTrace();
+ } else {
+ mWindowManager.stopWindowTrace();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not set window trace status." + e.toString());
+ }
+ }
+
+ private void setLayerTraceEnabled(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();
+ }
+ }
+ }
+
+ private void setSystemUiTracing(boolean isEnabled) {
+ try {
+ final IStatusBarService statusBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.checkService(Context.STATUS_BAR_SERVICE));
+ if (statusBarService != null) {
+ if (isEnabled) {
+ statusBarService.startTracing();
+ } else {
+ statusBarService.stopTracing();
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not set system ui tracing." + e.toString());
+ }
+ }
+
+ private void setImeTraceEnabled(boolean isEnabled) {
+ if (isEnabled) {
+ mImeTracing.startImeTrace();
+ } else {
+ mImeTracing.stopImeTrace();
+ }
+ }
+
+ private void setViewCaptureEnabled(boolean isEnabled) {
+ // Add null checking to avoid test case failure.
+ if (getApplicationContext() != null) {
+ Settings.Global.putInt(getApplicationContext()
+ .getContentResolver(), VIEW_CAPTURE_ENABLED, isEnabled ? 1 : 0);
+ }
+ }
+
+ @Override
+ protected void setIsEnabled(boolean isEnabled) {
+ setWindowTraceEnabled(isEnabled);
+ setLayerTraceEnabled(isEnabled);
+ setSystemUiTracing(isEnabled);
+ setImeTraceEnabled(isEnabled);
+ setViewCaptureEnabled(isEnabled);
+ if (!isEnabled) {
+ mToast.show();
+ }
+ }
+ }
+
/**
* Tile to toggle sensors off to control camera, mic, and sensors managed by the SensorManager.
*/
diff --git a/tests/robotests/src/com/android/settings/development/qstile/WinscopeTraceTest.java b/tests/robotests/src/com/android/settings/development/qstile/WinscopeTraceTest.java
new file mode 100644
index 00000000000..ad495c740f3
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/qstile/WinscopeTraceTest.java
@@ -0,0 +1,259 @@
+/*
+ * 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.WinscopeTrace.SURFACE_FLINGER_LAYER_TRACE_CONTROL_CODE;
+import static com.android.settings.development.qstile.DevelopmentTiles.WinscopeTrace.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.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.IBinder;
+import android.os.RemoteException;
+import android.view.IWindowManager;
+import android.widget.Toast;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.inputmethod.ImeTracing;
+import com.android.settings.testutils.shadow.ShadowParcel;
+
+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.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+@RunWith(RobolectricTestRunner.class)
+public class WinscopeTraceTest {
+
+ @Mock
+ private IWindowManager mWindowManager;
+ @Mock
+ private ImeTracing mImeTracing;
+ @Mock
+ private IBinder mSurfaceFlinger;
+ @Mock
+ private Toast mToast;
+
+ private DevelopmentTiles.WinscopeTrace mWinscopeTrace;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mWinscopeTrace = spy(new DevelopmentTiles.WinscopeTrace());
+ doReturn(ApplicationProvider.getApplicationContext()).when(
+ mWinscopeTrace).getApplicationContext();
+
+ ReflectionHelpers.setField(mWinscopeTrace, "mWindowManager", mWindowManager);
+ ReflectionHelpers.setField(mWinscopeTrace, "mImeTracing", mImeTracing);
+ ReflectionHelpers.setField(mWinscopeTrace, "mSurfaceFlinger", mSurfaceFlinger);
+ ReflectionHelpers.setField(mWinscopeTrace, "mToast", mToast);
+ }
+
+ @After
+ public void teardown() {
+ verifyNoMoreInteractions(mToast);
+ }
+
+ @Test
+ @Config(shadows = ShadowParcel.class)
+ public void wmReturnsTraceEnabled_shouldReturnEnabled() throws RemoteException {
+ // Assume Surface Trace and Input Method Manager are disabled.
+ ShadowParcel.sReadBoolResult = false;
+ doReturn(true).when(mWindowManager).isWindowTraceEnabled();
+ assertThat(mWinscopeTrace.isEnabled()).isTrue();
+ }
+
+ @Test
+ @Config(shadows = ShadowParcel.class)
+ public void sfReturnsTraceEnabled_shouldReturnEnabled() throws RemoteException {
+ // Assume Window Trace and Input Method Manager are disabled.
+ doReturn(false).when(mWindowManager).isWindowTraceEnabled();
+ doReturn(false).when(mImeTracing).isEnabled();
+ ShadowParcel.sReadBoolResult = true;
+ assertThat(mWinscopeTrace.isEnabled()).isTrue();
+ verify(mSurfaceFlinger)
+ .transact(eq(SURFACE_FLINGER_LAYER_TRACE_STATUS_CODE), any(), any(),
+ eq(0 /* flags */));
+ verifyNoMoreInteractions(mSurfaceFlinger);
+ }
+
+ @Test
+ @Config(shadows = ShadowParcel.class)
+ public void sfAndWmReturnsTraceEnabled_shouldReturnEnabled() throws RemoteException {
+ ShadowParcel.sReadBoolResult = true;
+ doReturn(true).when(mWindowManager).isWindowTraceEnabled();
+ assertThat(mWinscopeTrace.isEnabled()).isTrue();
+ }
+
+ @Test
+ public void wmAndSfAndImmReturnTraceDisabled_shouldReturnDisabled() throws RemoteException {
+ ShadowParcel.sReadBoolResult = false;
+ doReturn(false).when(mWindowManager).isWindowTraceEnabled();
+ doReturn(false).when(mImeTracing).isEnabled();
+ assertThat(mWinscopeTrace.isEnabled()).isFalse();
+ verify(mSurfaceFlinger)
+ .transact(eq(SURFACE_FLINGER_LAYER_TRACE_STATUS_CODE), any(), any(),
+ eq(0 /* flags */));
+ verifyNoMoreInteractions(mSurfaceFlinger);
+ }
+
+ @Test
+ public void wmAndSfReturnTraceDisabled_immReturnsTraceEnabled_shouldReturnEnabled()
+ throws RemoteException {
+ ShadowParcel.sReadBoolResult = false;
+ doReturn(false).when(mWindowManager).isWindowTraceEnabled();
+ doReturn(true).when(mImeTracing).isEnabled();
+ assertThat(mWinscopeTrace.isEnabled()).isTrue();
+ verify(mSurfaceFlinger)
+ .transact(eq(SURFACE_FLINGER_LAYER_TRACE_STATUS_CODE), any(), any(),
+ eq(0 /* flags */));
+ verifyNoMoreInteractions(mSurfaceFlinger);
+ }
+
+ @Test
+ @Config(shadows = ShadowParcel.class)
+ public void immReturnsTraceEnabled_shouldReturnEnabled() throws RemoteException {
+ // Assume Window Manager and Surface Trace are disabled.
+ ShadowParcel.sReadBoolResult = false;
+ doReturn(true).when(mImeTracing).isEnabled();
+ assertThat(mWinscopeTrace.isEnabled()).isTrue();
+ }
+
+ @Test
+ @Config(shadows = ShadowParcel.class)
+ public void immReturnsTraceDisabled_shouldReturnDisabled() throws RemoteException {
+ // Assume Window Manager and Surface Trace are disabled.
+ ShadowParcel.sReadBoolResult = false;
+ doReturn(false).when(mImeTracing).isEnabled();
+ assertThat(mWinscopeTrace.isEnabled()).isFalse();
+ }
+
+ @Test
+ @Config(shadows = ShadowParcel.class)
+ public void wmThrowsRemoteExAndSfReturnsTraceDisabled_shouldReturnDisabled()
+ throws RemoteException {
+ ShadowParcel.sReadBoolResult = false;
+ doThrow(new RemoteException("Unknown"))
+ .when(mWindowManager).isWindowTraceEnabled();
+ assertThat(mWinscopeTrace.isEnabled()).isFalse();
+ }
+
+ @Test
+ public void sfUnavailableAndWmAndImmReturnTraceDisabled_shouldReturnDisabled()
+ throws RemoteException {
+ doReturn(false).when(mWindowManager).isWindowTraceEnabled();
+ doReturn(false).when(mImeTracing).isEnabled();
+ ReflectionHelpers.setField(mWinscopeTrace, "mSurfaceFlinger", null);
+ assertThat(mWinscopeTrace.isEnabled()).isFalse();
+ }
+
+ @Test
+ public void setIsEnableTrue_shouldEnableWindowTrace() throws RemoteException {
+ mWinscopeTrace.setIsEnabled(true);
+ verify(mWindowManager).startWindowTrace();
+ verifyNoMoreInteractions(mWindowManager);
+ }
+
+ @Test
+ public void setIsEnableTrue_shouldEnableImeTrace() throws RemoteException {
+ mWinscopeTrace.setIsEnabled(true);
+ verify(mImeTracing).startImeTrace();
+ verifyNoMoreInteractions(mImeTracing);
+ }
+
+ @Test
+ @Config(shadows = ShadowParcel.class)
+ public void setIsEnableTrue_shouldEnableLayerTrace() throws RemoteException {
+ mWinscopeTrace.setIsEnabled(true);
+ assertThat(ShadowParcel.sWriteIntResult).isEqualTo(1);
+ verify(mSurfaceFlinger)
+ .transact(eq(SURFACE_FLINGER_LAYER_TRACE_CONTROL_CODE), any(), isNull(),
+ eq(0 /* flags */));
+ verifyNoMoreInteractions(mSurfaceFlinger);
+ }
+
+ @Test
+ @Config(shadows = ShadowParcel.class)
+ public void setIsEnableFalse_shouldDisableWindowTrace() throws RemoteException {
+ mWinscopeTrace.setIsEnabled(false);
+ verify(mWindowManager).stopWindowTrace();
+ verifyNoMoreInteractions(mWindowManager);
+ verify(mToast).show();
+ }
+
+ @Test
+ @Config(shadows = ShadowParcel.class)
+ public void setIsEnableFalse_shouldDisableImeTrace() throws RemoteException {
+ mWinscopeTrace.setIsEnabled(false);
+ verify(mImeTracing).stopImeTrace();
+ verifyNoMoreInteractions(mImeTracing);
+ verify(mToast).show();
+ }
+
+ @Test
+ @Config(shadows = ShadowParcel.class)
+ public void setIsEnableFalse_shouldDisableLayerTrace() throws RemoteException {
+ mWinscopeTrace.setIsEnabled(false);
+ assertThat(ShadowParcel.sWriteIntResult).isEqualTo(0);
+ verify(mSurfaceFlinger)
+ .transact(eq(SURFACE_FLINGER_LAYER_TRACE_CONTROL_CODE), any(), isNull(),
+ eq(0 /* flags */));
+ verifyNoMoreInteractions(mSurfaceFlinger);
+ verify(mToast).show();
+ }
+
+ @Test
+ public void setIsEnableFalse_shouldShowToast() {
+ mWinscopeTrace.setIsEnabled(false);
+ verify(mToast).show();
+ }
+
+ /**
+ * Verify when window manager call throws a remote exception, it is handled without
+ * re-throwing the exception.
+ */
+ @Test
+ public void setIsEnableAndWmThrowsRemoteException_shouldFailGracefully()
+ throws RemoteException {
+ doThrow(new RemoteException("Unknown")).when(mWindowManager).isWindowTraceEnabled();
+ mWinscopeTrace.setIsEnabled(true);
+ }
+
+ /**
+ * Verify is surface flinger is not available not calls are made to it.
+ */
+ @Test
+ public void setIsEnableAndSfUnavailable_shouldFailGracefully() {
+ ReflectionHelpers.setField(mWinscopeTrace, "mSurfaceFlinger", null);
+ mWinscopeTrace.setIsEnabled(true);
+ verifyNoMoreInteractions(mSurfaceFlinger);
+ }
+}