OSDN Git Service

Adding a customizable development mode tile
authorSunny Goyal <sunnygoyal@google.com>
Wed, 2 Nov 2016 18:20:17 +0000 (11:20 -0700)
committerSunny Goyal <sunnygoyal@google.com>
Thu, 5 Jan 2017 18:16:18 +0000 (10:16 -0800)
User can configure the active state for development mode
which would allow enabling multiple options at once

Test: make -j40 RunSettingsRoboTests
Change-Id: I545b790f8c7097945f39ca003e5985b23cb53636

13 files changed:
AndroidManifest.xml
res/drawable/tile_icon_graphics.xml [deleted file]
res/drawable/tile_icon_show_layout.xml [deleted file]
res/layout/development_tile_config_header.xml [new file with mode: 0644]
res/values/strings.xml
res/xml/development_tile_prefs.xml [new file with mode: 0644]
src/com/android/settings/SettingsActivity.java
src/com/android/settings/qstile/DevelopmentModeTile.java [new file with mode: 0644]
src/com/android/settings/qstile/DevelopmentTileConfigActivity.java [new file with mode: 0644]
src/com/android/settings/qstile/DevelopmentTiles.java [deleted file]
tests/robotests/assets/grandfather_not_implementing_indexable
tests/robotests/src/com/android/settings/qstile/DevelopmentModeTileTest.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/testutils/shadow/ShadowTileService.java [new file with mode: 0644]

index f4a972b..d15f7a4 100644 (file)
 
         <!-- Quick Settings tiles for Developer Options -->
         <service
-                android:name=".qstile.DevelopmentTiles$ShowLayout"
-                android:label="@string/debug_layout"
-                android:icon="@drawable/tile_icon_show_layout"
-                android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
-                android:enabled="false">
+            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">
             <intent-filter>
                 <action android:name="android.service.quicksettings.action.QS_TILE" />
             </intent-filter>
         </service>
-        <service
-                android:name=".qstile.DevelopmentTiles$GPUProfiling"
-                android:label="@string/track_frame_time"
-                android:icon="@drawable/tile_icon_graphics"
-                android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
-                android:enabled="false">
+        <activity
+            android:name=".qstile.DevelopmentTileConfigActivity"
+            android:excludeFromRecents="true"
+            android:launchMode="singleInstance"
+            android:label="@string/developer_tile">
             <intent-filter>
-                <action android:name="android.service.quicksettings.action.QS_TILE" />
+                <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
             </intent-filter>
-        </service>
+        </activity>
 
         <activity android:name=".HelpTrampoline"
             android:exported="true"
diff --git a/res/drawable/tile_icon_graphics.xml b/res/drawable/tile_icon_graphics.xml
deleted file mode 100644 (file)
index 84a0a5a..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-<!--
-Copyright (C) 2015 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">
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M23,1v22H1V1H23 M24,0H0v24h24V0L24,0z"/>
-    <path
-        android:strokeColor="#FFFFFFFF"
-        android:strokeWidth="0.667"
-        android:pathData="M5,19L1,23"/>
-    <path
-        android:strokeColor="#FFFFFFFF"
-        android:strokeWidth="0.667"
-        android:pathData="M1,1L5,5"/>
-    <path
-        android:strokeColor="#FFFFFFFF"
-        android:strokeWidth="0.667"
-        android:pathData="M19,5L23,1"/>
-    <path
-        android:strokeColor="#FFFFFFFF"
-        android:strokeWidth="0.667"
-        android:pathData="M19,19L23,23"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M14,3.8l-4,0l-0.2,-0.8l4.4,0z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M18.8,5.2v13.5H5.2V5.2H18.8 M19.5,4.5h-15v15h15V4.5L19.5,4.5z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M6.5,9.5h5v11h-5z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M12.5,16.5h5v5h-5z"/>
-</vector>
diff --git a/res/drawable/tile_icon_show_layout.xml b/res/drawable/tile_icon_show_layout.xml
deleted file mode 100644 (file)
index b9b825e..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<!--
-Copyright (C) 2015 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">
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M12,7.2c-3.2,0 -5.9,2 -7,4.8c1.1,2.8 3.8,4.8 7,4.8s5.9,-2 7,-4.8C17.9,9.2 15.2,7.2 12,7.2zM12,15.2c-1.8,0 -3.2,-1.4 -3.2,-3.2s1.4,-3.2 3.2,-3.2s3.2,1.4 3.2,3.2S13.8,15.2 12,15.2zM12,10.1c-1.1,0 -1.9,0.9 -1.9,1.9s0.9,1.9 1.9,1.9s1.9,-0.9 1.9,-1.9S13.1,10.1 12,10.1z"/>
-    <path
-        android:fillColor="#80FFFFFF"
-        android:pathData="M0.0,6.0h1.0v12.0h-1.0z"/>
-    <path
-        android:fillColor="#80FFFFFF"
-        android:pathData="M6.0,0.0h12.0v1.0h-12.0z"/>
-    <path
-        android:fillColor="#80FFFFFF"
-        android:pathData="M23.0,6.0h1.0v12.0h-1.0z"/>
-    <path
-        android:fillColor="#80FFFFFF"
-        android:pathData="M6.0,23.0h12.0v1.0h-12.0z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M1.0,23.0l0.0,-5.0l-1.0,0.0l0.0,6.0l6.0,0.0l0.0,-1.0z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M1.0,1.0l5.0,0.0l0.0,-1.0l-6.0,0.0l0.0,6.0l1.0,0.0z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M18.0,0.0l0.0,1.0l5.0,0.0l0.0,5.0l1.0,0.0l0.0,-6.0z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M23.0,23.0l-5.0,0.0l0.0,1.0l6.0,0.0l0.0,-6.0l-1.0,0.0z"/>
-    <path
-        android:fillColor="#80FFFFFF"
-        android:pathData="M9.5,6.0h5.0v1.0h-5.0z"/>
-    <path
-        android:fillColor="#80FFFFFF"
-        android:pathData="M9.5,17.0h5.0v1.0h-5.0z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M4.5,7.0l5.0,0.0l0.0,-1.0l-6.0,0.0l0.0,6.0l0.0,6.0l6.0,0.0l0.0,-1.0l-5.0,0.0l0.0,-5.0z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M14.5,6.0l0.0,1.0l5.0,0.0l0.0,5.0l0.0,5.0l-5.0,0.0l0.0,1.0l6.0,0.0l0.0,-6.0l0.0,-6.0z"/>
-</vector>
diff --git a/res/layout/development_tile_config_header.xml b/res/layout/development_tile_config_header.xml
new file mode 100644 (file)
index 0000000..5169da2
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="?android:attr/actionBarSize"
+    android:background="@drawable/switchbar_background"
+    android:text="@string/developer_tile_unavailable"
+    android:paddingStart="8dip"
+    android:paddingEnd="8dip"
+    android:singleLine="true"
+    android:ellipsize="marquee"
+    android:marqueeRepeatLimit ="marquee_forever"
+    android:textSize="16sp"
+    android:fontFamily="sans-serif"
+    android:textColor="?android:attr/textColorPrimary"
+    android:gravity="center" />
index 955b8cc..07d1bec 100644 (file)
     <!-- Title of developer options to set the smallest width of the screen [CHAR LIMIT=60]-->
     <string name="developer_smallest_width">Smallest width</string>
 
+    <!-- Title of developer options time [CHAR LIMIT=60]-->
+    <string name="developer_tile">Development mode</string>
+
+    <!-- Message shown when there is no active state for development mode [CHAR LIMIT=NONE]-->
+    <string name="developer_tile_unavailable">No active state selected</string>
+
     <!-- Message shown when there are no premium SMS apps [CHAR LIMIT=NONE] -->
     <string name="premium_sms_none">No installed apps have requested Premium SMS access</string>
 
diff --git a/res/xml/development_tile_prefs.xml b/res/xml/development_tile_prefs.xml
new file mode 100644 (file)
index 0000000..26f1c24
--- /dev/null
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 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
+  -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <PreferenceCategory
+        android:key="debug_input_category"
+        android:title="@string/debug_input_category">
+
+        <SwitchPreference
+            android:key="show_touches"
+            android:title="@string/show_touches"
+            android:summary="@string/show_touches_summary"/>
+
+        <SwitchPreference
+            android:key="pointer_location"
+            android:title="@string/pointer_location"
+            android:summary="@string/pointer_location_summary"/>
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="debug_drawing_category"
+        android:title="@string/debug_drawing_category">
+
+        <SwitchPreference
+            android:key="debug_layout"
+            android:title="@string/debug_layout"
+            android:summary="@string/debug_layout_summary"/>
+
+        <SwitchPreference
+            android:key="force_rtl_layout_all_locales"
+            android:title="@string/force_rtl_layout_all_locales"
+            android:summary="@string/force_rtl_layout_all_locales_summary"/>
+
+        <ListPreference
+            android:key="window_animation_scale"
+            android:defaultValue="1"
+            android:summary="%s"
+            android:title="@string/window_animation_scale_title"
+            android:entries="@array/window_animation_scale_entries"
+            android:entryValues="@array/window_animation_scale_values" />
+
+        <ListPreference
+            android:key="transition_animation_scale"
+            android:defaultValue="1"
+            android:summary="%s"
+            android:title="@string/transition_animation_scale_title"
+            android:entries="@array/transition_animation_scale_entries"
+            android:entryValues="@array/transition_animation_scale_values" />
+
+        <ListPreference
+            android:key="animator_duration_scale"
+            android:defaultValue="1"
+            android:summary="%s"
+            android:title="@string/animator_duration_scale_title"
+            android:entries="@array/animator_duration_scale_entries"
+            android:entryValues="@array/animator_duration_scale_values" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="debug_hw_drawing_category"
+        android:title="@string/debug_hw_drawing_category">
+
+        <SwitchPreference
+            android:key="show_hw_screen_udpates"
+            android:title="@string/show_hw_screen_updates"
+            android:summary="@string/show_hw_screen_updates_summary"/>
+
+        <SwitchPreference
+            android:key="show_hw_layers_udpates"
+            android:title="@string/show_hw_layers_updates"
+            android:summary="@string/show_hw_layers_updates_summary"/>
+
+        <ListPreference
+            android:key="debug_hw_overdraw"
+            android:defaultValue="false"
+            android:summary="%s"
+            android:title="@string/debug_hw_overdraw"
+            android:entries="@array/debug_hw_overdraw_entries"
+            android:entryValues="@array/debug_hw_overdraw_values" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="debug_monitoring_category"
+        android:title="@string/debug_monitoring_category">
+
+        <ListPreference
+            android:key="track_frame_time"
+            android:defaultValue="false"
+            android:summary="%s"
+            android:title="@string/track_frame_time"
+            android:entries="@array/track_frame_time_entries"
+            android:entryValues="@array/track_frame_time_values" />
+
+    </PreferenceCategory>
+
+</PreferenceScreen>
index b9be118..179e352 100644 (file)
@@ -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 (file)
index 0000000..578ffd8
--- /dev/null
@@ -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<String, ?> 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<String, ?> 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 (file)
index 0000000..cc63026
--- /dev/null
@@ -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 (file)
index 0d8e7db..0000000
+++ /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
index 0c539d8..81adf8b 100644 (file)
@@ -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 (file)
index 0000000..a30bd47
--- /dev/null
@@ -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 (file)
index 0000000..2270ff3
--- /dev/null
@@ -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;
+    }
+}