Merge "Add developer tiles for layer and window trace"

This commit is contained in:
TreeHugger Robot
2018-01-11 21:37:53 +00:00
committed by Android (Google) Code Review
12 changed files with 502 additions and 20 deletions

View File

@@ -3220,6 +3220,26 @@
<action android:name="android.service.quicksettings.action.QS_TILE" /> <action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter> </intent-filter>
</service> </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" <activity android:name=".HelpTrampoline"
android:exported="true" android:exported="true"

View File

@@ -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>

View File

@@ -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>

View File

@@ -8683,6 +8683,12 @@
<!-- [CHAR LIMIT=60] Name of dev option to enable extra quick settings tiles --> <!-- [CHAR LIMIT=60] Name of dev option to enable extra quick settings tiles -->
<string name="quick_settings_developer_tiles">Quick settings developer tiles</string> <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 --> <!-- [CHAR LIMIT=60] Title of work profile setting page -->
<string name="managed_profile_settings_title">Work profile settings</string> <string name="managed_profile_settings_title">Work profile settings</string>
<!-- [CHAR LIMIT=60] The preference title for enabling cross-profile remote contact search --> <!-- [CHAR LIMIT=60] The preference title for enabling cross-profile remote contact search -->

View File

@@ -16,20 +16,27 @@
package com.android.settings.development.qstile; package com.android.settings.development.qstile;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties; import android.os.SystemProperties;
import android.provider.Settings; import android.provider.Settings;
import android.service.quicksettings.Tile; import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService; import android.service.quicksettings.TileService;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.view.IWindowManager; import android.view.IWindowManager;
import android.view.ThreadedRenderer; import android.view.ThreadedRenderer;
import android.view.View; import android.view.View;
import android.view.WindowManagerGlobal; import android.view.WindowManagerGlobal;
import com.android.internal.app.LocalePicker; import com.android.internal.app.LocalePicker;
import com.android.settings.wrapper.IWindowManagerWrapper;
import com.android.settingslib.development.SystemPropPoker; import com.android.settingslib.development.SystemPropPoker;
public abstract class DevelopmentTiles extends TileService { public abstract class DevelopmentTiles extends TileService {
private static final String TAG = "DevelopmentTiles";
protected abstract boolean isEnabled(); protected abstract boolean isEnabled();
@@ -131,4 +138,106 @@ public abstract class DevelopmentTiles extends TileService {
} catch (RemoteException e) { } } 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();
}
}
}
}
} }

View File

@@ -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();
}
}

View File

@@ -37,6 +37,7 @@ import android.support.v7.preference.PreferenceScreen;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.ShadowParcel;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

View File

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

View File

@@ -37,6 +37,7 @@ import android.support.v7.preference.PreferenceScreen;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.ShadowParcel;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

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