diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b0d490270b3..6b92d9ccdc6 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3467,25 +3467,24 @@
+ android:name=".qstile.DevelopmentModeTile"
+ android:label="@string/developer_tile"
+ android:icon="@drawable/ic_settings_development"
+ android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
+ android:enabled="true">
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
diff --git a/res/drawable/tile_icon_show_layout.xml b/res/drawable/tile_icon_show_layout.xml
deleted file mode 100644
index b9b825e01a6..00000000000
--- a/res/drawable/tile_icon_show_layout.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/res/layout/development_tile_config_header.xml b/res/layout/development_tile_config_header.xml
new file mode 100644
index 00000000000..5169da23c05
--- /dev/null
+++ b/res/layout/development_tile_config_header.xml
@@ -0,0 +1,32 @@
+
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9da4894af06..a6d9f1dd80c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -7680,6 +7680,12 @@
Smallest width
+
+ Development mode
+
+
+ No active state selected
+
No installed apps have requested Premium SMS access
diff --git a/res/xml/development_tile_prefs.xml b/res/xml/development_tile_prefs.xml
new file mode 100644
index 00000000000..26f1c24e051
--- /dev/null
+++ b/res/xml/development_tile_prefs.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index b9be118d7c9..179e3522145 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -61,7 +61,7 @@ import com.android.settings.dashboard.DashboardFeatureProvider;
import com.android.settings.dashboard.DashboardSummary;
import com.android.settings.dashboard.SearchResultsSummary;
import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.qstile.DevelopmentTiles;
+import com.android.settings.qstile.DevelopmentModeTile;
import com.android.settings.search.DynamicIndexableContentMonitor;
import com.android.settings.search.Index;
import com.android.settings.search2.SearchFeatureProvider;
@@ -994,7 +994,7 @@ public class SettingsActivity extends SettingsDrawerActivity
showDev, isAdmin, pm);
// Reveal development-only quick settings tiles
- DevelopmentTiles.setTilesEnabled(this, showDev);
+ setTileEnabled(new ComponentName(this, DevelopmentModeTile.class), showDev);
if (UserHandle.MU_ENABLED && !isAdmin) {
// When on restricted users, disable all extra categories (but only the settings ones).
diff --git a/src/com/android/settings/qstile/DevelopmentModeTile.java b/src/com/android/settings/qstile/DevelopmentModeTile.java
new file mode 100644
index 00000000000..578ffd8f291
--- /dev/null
+++ b/src/com/android/settings/qstile/DevelopmentModeTile.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2016 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.qstile;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.TileService;
+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.DevelopmentSettings;
+
+import java.util.Map;
+
+public class DevelopmentModeTile extends TileService {
+
+ static final String SHARED_PREFERENCES_NAME = "development_mode_tile_settings";
+
+ private static final String SHOW_TOUCHES_KEY = "show_touches";
+ private static final String POINTER_LOCATION_KEY = "pointer_location";
+ private static final String DEBUG_LAYOUT_KEY = "debug_layout";
+ private static final String FORCE_RTL_LAYOUT_KEY = "force_rtl_layout_all_locales";
+ private static final String WINDOW_ANIMATION_SCALE_KEY = "window_animation_scale";
+ private static final String TRANSITION_ANIMATION_SCALE_KEY = "transition_animation_scale";
+ private static final String ANIMATOR_DURATION_SCALE_KEY = "animator_duration_scale";
+ private static final String SHOW_HW_SCREEN_UPDATES_KEY = "show_hw_screen_udpates";
+ private static final String SHOW_HW_LAYERS_UPDATES_KEY = "show_hw_layers_udpates";
+ private static final String DEBUG_HW_OVERDRAW_KEY = "debug_hw_overdraw";
+ private static final String TRACK_FRAME_TIME_KEY = "track_frame_time";
+
+ private DevModeProperties mProps = new DevModeProperties();
+
+ @Override
+ public void onStartListening() {
+ super.onStartListening();
+ refresh();
+ }
+
+ public void refresh() {
+ mProps.refreshState(this);
+ getQsTile().setState(mProps.isSet ? (mProps.allMatch
+ ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE) : Tile.STATE_UNAVAILABLE);
+ getQsTile().updateTile();
+ }
+
+ @Override
+ public void onClick() {
+ if (getQsTile().getState() == Tile.STATE_UNAVAILABLE) {
+ startActivityAndCollapse(new Intent(this, DevelopmentTileConfigActivity.class));
+ return;
+ }
+
+ boolean active = getQsTile().getState() == Tile.STATE_INACTIVE;
+ Map values =
+ getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE).getAll();
+ ContentResolver cr = getContentResolver();
+ for (Property prop : mProps.mSysProps) {
+ Object expected = values.get(prop.prefKey);
+ String value = active && !prop.isDefault(expected) ? expected.toString() : "false";
+ SystemProperties.set(prop.key, value);
+ }
+ for (Property prop : mProps.mSysSettings) {
+ boolean expectedTrue = active && !prop.isDefault(values.get(prop.prefKey));
+ Settings.System.putInt(cr, prop.key, expectedTrue ? 1 : 0);
+ }
+
+ boolean expectedGlobPropTrue = active &&
+ !mProps.mGlobProp.isDefault(values.get(mProps.mGlobProp.prefKey));
+ Settings.Global.putInt(cr, mProps.mGlobProp.key, expectedGlobPropTrue ? 1 : 0);
+ SystemProperties.set(mProps.mGlobProp.key, expectedGlobPropTrue ? "1" : "0");
+ LocalePicker.updateLocales(getResources().getConfiguration().getLocales());
+
+ IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ try {
+ // Update the various animation scale values to expected values or 1. mProps.mAnimScales
+ // is an ordered array, where the index corresponds to the individual property.
+ for (int i = 0; i < mProps.mAnimScales.length; i++) {
+ Object expected = values.get(mProps.mAnimScales[i]);
+ float expectedFloat = active && expected != null ?
+ Float.parseFloat(expected.toString()) : 1;
+ wm.setAnimationScale(i, expectedFloat);
+ }
+ } catch (RemoteException e) { }
+
+ new DevelopmentSettings.SystemPropPoker().execute(); // Settings app magic
+ refresh();
+ }
+
+ static class DevModeProperties {
+
+ private final Property[] mSysProps = new Property[] {
+ new Property(View.DEBUG_LAYOUT_PROPERTY, DEBUG_LAYOUT_KEY),
+ new Property(ThreadedRenderer.DEBUG_DIRTY_REGIONS_PROPERTY,
+ SHOW_HW_SCREEN_UPDATES_KEY),
+ new Property(ThreadedRenderer.DEBUG_SHOW_LAYERS_UPDATES_PROPERTY,
+ SHOW_HW_LAYERS_UPDATES_KEY),
+ new Property(ThreadedRenderer.DEBUG_OVERDRAW_PROPERTY, DEBUG_HW_OVERDRAW_KEY),
+ new Property(ThreadedRenderer.PROFILE_PROPERTY, TRACK_FRAME_TIME_KEY),
+ };
+
+ private final Property[] mSysSettings = new Property[] {
+ new Property(Settings.System.SHOW_TOUCHES, SHOW_TOUCHES_KEY),
+ new Property(Settings.System.POINTER_LOCATION, POINTER_LOCATION_KEY),
+ };
+
+ private final Property mGlobProp =
+ new Property(Settings.Global.DEVELOPMENT_FORCE_RTL, FORCE_RTL_LAYOUT_KEY);
+
+ private final String[] mAnimScales = new String[] {
+ WINDOW_ANIMATION_SCALE_KEY,
+ TRANSITION_ANIMATION_SCALE_KEY,
+ ANIMATOR_DURATION_SCALE_KEY
+ };
+
+ /**
+ * True is the values of all the properties corresponds to the expected values. Updated when
+ * {@link #refreshState(Context)} is called.
+ */
+ public boolean allMatch;
+ /**
+ * True is at least one property has a non-default expected value. Updated when
+ * {@link #refreshState(Context)} is called. Not that if all properties have default
+ * expected value, then active and non-active state will be the same.
+ */
+ public boolean isSet;
+
+ public void refreshState(Context context) {
+ Map values =
+ context.getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE).getAll();
+ allMatch = true;
+ // True if there is at least one non-default value.
+ isSet = false;
+
+ for (Property prop : mSysProps) {
+ Object expected = values.get(prop.prefKey);
+ String actual = SystemProperties.get(prop.key);
+ allMatch &= prop.isDefault(expected)
+ ? prop.isDefault(actual) : expected.toString().equals(actual);
+ isSet |= !prop.isDefault(expected);
+ }
+
+ ContentResolver cr = context.getContentResolver();
+ for (Property prop : mSysSettings) {
+ boolean expectedTrue = !prop.isDefault(values.get(prop.prefKey));
+ isSet |= expectedTrue;
+ allMatch &= expectedTrue == (Settings.System.getInt(cr, prop.key, 0) != 0);
+ }
+
+ boolean expectedGlopPropTrue = !mGlobProp.isDefault(values.get(mGlobProp.prefKey));
+ isSet |= expectedGlopPropTrue;
+ allMatch &= expectedGlopPropTrue == (Settings.Global.getInt(cr, mGlobProp.key, 0) != 0);
+
+ IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ try {
+ for (int i = 0; i < mAnimScales.length; i++) {
+ Object expected = values.get(mAnimScales[i]);
+ float expectedFloat = expected == null
+ ? 1 : Float.parseFloat(expected.toString());
+ isSet |= expectedFloat != 1;
+ allMatch &= expectedFloat == wm.getAnimationScale(i);
+ }
+ } catch (RemoteException e) { }
+ }
+ }
+
+ private static class Property {
+ final String key;
+ final String prefKey;
+
+ Property(String key, String prefKey) {
+ this.key = key;
+ this.prefKey = prefKey;
+ }
+
+ boolean isDefault(Object value) {
+ if (value == null) {
+ return true;
+ }
+ String str = value.toString();
+ return str.equals("") || str.equals("false");
+ }
+ }
+}
diff --git a/src/com/android/settings/qstile/DevelopmentTileConfigActivity.java b/src/com/android/settings/qstile/DevelopmentTileConfigActivity.java
new file mode 100644
index 00000000000..cc63026645e
--- /dev/null
+++ b/src/com/android/settings/qstile/DevelopmentTileConfigActivity.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2016 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.qstile;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.core.instrumentation.Instrumentable;
+import com.android.settings.widget.SwitchBar;
+
+public class DevelopmentTileConfigActivity extends SettingsActivity {
+
+ @Override
+ public Intent getIntent() {
+ Intent modIntent = new Intent(super.getIntent())
+ .putExtra(EXTRA_SHOW_FRAGMENT, DevelopmentTileConfigFragment.class.getName())
+ .putExtra(EXTRA_HIDE_DRAWER, true);
+ return modIntent;
+ }
+
+ @Override
+ protected boolean isValidFragment(String fragmentName) {
+ return (DevelopmentTileConfigFragment.class.getName().equals(fragmentName));
+ }
+
+ public static class DevelopmentTileConfigFragment extends SettingsPreferenceFragment implements
+ SharedPreferences.OnSharedPreferenceChangeListener {
+
+ private DevelopmentModeTile.DevModeProperties mProps =
+ new DevelopmentModeTile.DevModeProperties();
+
+ private SwitchBar mSwitchBar;
+ private View mDisabledMessage;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ getPreferenceManager()
+ .setSharedPreferencesName(DevelopmentModeTile.SHARED_PREFERENCES_NAME);
+ addPreferencesFromResource(R.xml.development_tile_prefs);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ mDisabledMessage = setPinnedHeaderView(R.layout.development_tile_config_header);
+ refreshHeader();
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mSwitchBar = ((SettingsActivity) getActivity()).getSwitchBar();
+ mSwitchBar.setEnabled(false);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return Instrumentable.METRICS_CATEGORY_UNKNOWN;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshHeader();
+ getPreferenceManager().getSharedPreferences()
+ .registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getPreferenceManager().getSharedPreferences()
+ .unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ refreshHeader();
+ }
+
+ private void refreshHeader() {
+ if (mSwitchBar != null && mDisabledMessage != null) {
+ mProps.refreshState(getActivity());
+ if (mProps.isSet) {
+ mSwitchBar.show();
+ mDisabledMessage.setVisibility(View.GONE);
+ } else {
+ mSwitchBar.hide();
+ mDisabledMessage.setVisibility(View.VISIBLE);
+ }
+ mSwitchBar.setChecked(mProps.allMatch);
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/qstile/DevelopmentTiles.java b/src/com/android/settings/qstile/DevelopmentTiles.java
deleted file mode 100644
index 0d8e7dbec73..00000000000
--- a/src/com/android/settings/qstile/DevelopmentTiles.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2016 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.qstile;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.SystemProperties;
-import android.service.quicksettings.Tile;
-import android.service.quicksettings.TileService;
-import android.view.ThreadedRenderer;
-import android.view.View;
-import com.android.settings.DevelopmentSettings;
-
-public class DevelopmentTiles {
- // List of components that need to be enabled when developer tools are turned on
- static final Class[] TILE_CLASSES = new Class[] {
- ShowLayout.class,
- GPUProfiling.class,
- };
- public static void setTilesEnabled(Context context, boolean enable) {
- final PackageManager pm = context.getPackageManager();
- for (Class cls : TILE_CLASSES) {
- pm.setComponentEnabledSetting(new ComponentName(context, cls),
- enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
- : PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
- PackageManager.DONT_KILL_APP);
- }
- }
-
- /**
- * Tile to control the "Show layout bounds" developer setting
- */
- public static class ShowLayout extends TileService {
- @Override
- public void onStartListening() {
- super.onStartListening();
- refresh();
- }
-
- public void refresh() {
- final boolean enabled = SystemProperties.getBoolean(View.DEBUG_LAYOUT_PROPERTY, false);
- getQsTile().setState(enabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
- getQsTile().updateTile();
- }
-
- @Override
- public void onClick() {
- SystemProperties.set(View.DEBUG_LAYOUT_PROPERTY,
- getQsTile().getState() == Tile.STATE_INACTIVE ? "true" : "false");
- new DevelopmentSettings.SystemPropPoker().execute(); // Settings app magic
- refresh();
- }
- }
-
- /**
- * Tile to control the "GPU profiling" developer setting
- */
- public static class GPUProfiling extends TileService {
- @Override
- public void onStartListening() {
- super.onStartListening();
- refresh();
- }
-
- public void refresh() {
- final String value = SystemProperties.get(ThreadedRenderer.PROFILE_PROPERTY);
- getQsTile().setState(value.equals("visual_bars")
- ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
- getQsTile().updateTile();
- }
-
- @Override
- public void onClick() {
- SystemProperties.set(ThreadedRenderer.PROFILE_PROPERTY,
- getQsTile().getState() == Tile.STATE_INACTIVE ? "visual_bars" : "");
- new DevelopmentSettings.SystemPropPoker().execute(); // Settings app magic
- refresh();
- }
- }
-}
\ No newline at end of file
diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable
index 0c539d813d4..81adf8b7ccb 100644
--- a/tests/robotests/assets/grandfather_not_implementing_indexable
+++ b/tests/robotests/assets/grandfather_not_implementing_indexable
@@ -88,4 +88,5 @@ com.android.settings.notification.NotificationAccessSettings
com.android.settings.notification.ZenModeSettings
com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment
com.android.settings.applications.ConvertToFbe
-com.android.settings.localepicker.LocaleListEditor
\ No newline at end of file
+com.android.settings.localepicker.LocaleListEditor
+com.android.settings.qstile.DevelopmentTileConfigActivity$DevelopmentTileConfigFragment
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/qstile/DevelopmentModeTileTest.java b/tests/robotests/src/com/android/settings/qstile/DevelopmentModeTileTest.java
new file mode 100644
index 00000000000..a30bd47ca2f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/qstile/DevelopmentModeTileTest.java
@@ -0,0 +1,75 @@
+package com.android.settings.qstile;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.service.quicksettings.Tile;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.shadow.ShadowTileService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+import org.robolectric.internal.ShadowExtractor;
+import org.robolectric.util.ReflectionHelpers;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+ shadows = ShadowTileService.class)
+public class DevelopmentModeTileTest {
+
+ @Mock private Tile mTile;
+ @Mock private DevelopmentModeTile.DevModeProperties mProps;
+
+ private DevelopmentModeTile mDevelopmentModeTile;
+ private ShadowTileService mShadowTileService;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mDevelopmentModeTile = Robolectric.buildService(DevelopmentModeTile.class).get();
+
+ ReflectionHelpers.setField(mDevelopmentModeTile, "mProps", mProps);
+ mShadowTileService = (ShadowTileService) ShadowExtractor.extract(mDevelopmentModeTile);
+ mShadowTileService.setTile(mTile);
+ }
+
+ @Test
+ public void refresh() {
+ verifyRefreshState(false, true, Tile.STATE_UNAVAILABLE);
+ verifyRefreshState(false, false, Tile.STATE_UNAVAILABLE);
+ verifyRefreshState(true, false, Tile.STATE_INACTIVE);
+ verifyRefreshState(true, true, Tile.STATE_ACTIVE);
+ }
+
+ @Test
+ public void onClick_startSetting() {
+ when(mTile.getState()).thenReturn(Tile.STATE_UNAVAILABLE);
+ mDevelopmentModeTile.onClick();
+
+ Intent intent = mShadowTileService.getNextStartedActivity();
+ assertEquals(DevelopmentTileConfigActivity.class.getName(),
+ intent.getComponent().getClassName());
+ }
+
+ private void verifyRefreshState(boolean isSet, boolean allMatch, int expectedState) {
+ reset(mProps, mTile);
+
+ mProps.isSet = isSet;
+ mProps.allMatch = allMatch;
+ mDevelopmentModeTile.refresh();
+
+ verify(mProps).refreshState(eq(mDevelopmentModeTile));
+ verify(mTile).setState(eq(expectedState));
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowTileService.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowTileService.java
new file mode 100644
index 00000000000..2270ff33834
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowTileService.java
@@ -0,0 +1,38 @@
+package com.android.settings.testutils.shadow;
+
+import android.content.Intent;
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.TileService;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.shadows.ShadowService;
+
+/**
+ * Shadow for {@link TileService}.
+ */
+@Implements(TileService.class)
+public class ShadowTileService extends ShadowService {
+
+ @RealObject TileService realService;
+
+ private Tile mTile;
+
+ public void __constructor__() { }
+
+ @Implementation
+ public final Tile getQsTile() {
+ return mTile;
+ }
+
+ @Implementation
+ public final void startActivityAndCollapse(Intent intent) {
+ realService.startActivity(intent);
+ }
+
+ // Non-Android setter.
+ public void setTile(Tile tile) {
+ mTile = tile;
+ }
+}