Move tuner enable point to QS

Long clicking on settings gear in QS causes it to accelarate and
spin, when released it will open and enable the tuner.  On first
opening there will be a warning dialog.

Also add way to disable tuner from tuner screen. This way it can
be removed from its former home in Developer Settings.  All tuner
settings are reset when disabled to match developer settings
behavior.

Bug: 22462605
Change-Id: Ie700a6a15e6c3caccf7cd1885da73328e6fac6ab
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6e5dc3f..fea7f94 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -405,5 +405,13 @@
             android:exported="true"
             android:singleUser="true"
             android:permission="android.permission.BIND_DREAM_SERVICE" />
+
+        <receiver
+            android:name=".tuner.TunerService$ClearReceiver"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.systemui.action.CLEAR_TUNER" />
+            </intent-filter>
+        </receiver>
     </application>
 </manifest>
diff --git a/packages/SystemUI/res/drawable-nodpi/tuner.xml b/packages/SystemUI/res/drawable-nodpi/tuner.xml
index e27423f..0596aa4 100644
--- a/packages/SystemUI/res/drawable-nodpi/tuner.xml
+++ b/packages/SystemUI/res/drawable-nodpi/tuner.xml
@@ -1,7 +1,7 @@
 <!--
-   Copyright (C) 2015 The Android Open Source Project
+    Copyright (C) 2015 The Android Open Source Project
 
-   Licensed under the Apache License, Version 2.0 (the "License");
+    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
 
@@ -14,14 +14,11 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="48.0dp"
-        android:height="48.0dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
     <path
-        android:fillColor="#FF000000"
-        android:pathData="M29.9,24.8c0.0,-0.3 0.1,-0.5 0.1,-0.8s0.0,-0.5 -0.1,-0.8l1.7,-1.3c0.2,-0.1 0.2,-0.3 0.1,-0.5l-1.6,-2.8c-0.1,-0.2 -0.3,-0.2 -0.5,-0.2l-2.0,0.8c-0.4,-0.3 -0.9,-0.6 -1.4,-0.8L26.0,16.3c0.0,-0.2 -0.2,-0.3 -0.4,-0.3l-3.2,0.0c-0.2,0.0 -0.4,0.1 -0.4,0.3l-0.3,2.1c-0.5,0.2 -0.9,0.5 -1.4,0.8l-2.0,-0.8c-0.2,-0.1 -0.4,0.0 -0.5,0.2l-1.6,2.8c-0.1,0.2 -0.1,0.4 0.1,0.5l1.7,1.3c0.0,0.3 -0.1,0.5 -0.1,0.8s0.0,0.5 0.1,0.8l-1.7,1.3c-0.2,0.1 -0.2,0.3 -0.1,0.5l1.6,2.8c0.1,0.2 0.3,0.2 0.5,0.2l2.0,-0.8c0.4,0.3 0.9,0.6 1.4,0.8l0.3,2.1c0.0,0.2 0.2,0.3 0.4,0.3l3.2,0.0c0.2,0.0 0.4,-0.1 0.4,-0.3l0.3,-2.1c0.5,-0.2 0.9,-0.5 1.4,-0.8l2.0,0.8c0.2,0.1 0.4,0.0 0.5,-0.2l1.6,-2.8c0.1,-0.2 0.1,-0.4 -0.1,-0.5L29.9,24.8zM24.0,26.8c-1.5,0.0 -2.8,-1.3 -2.8,-2.8s1.3,-2.8 2.8,-2.8s2.8,1.3 2.8,2.8S25.5,26.8 24.0,26.8z"/>
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M18.0,38.0c-0.6,0.0 -1.0,-0.4 -1.0,-1.0s0.4,-1.0 1.0,-1.0s1.0,0.4 1.0,1.0S18.6,38.0 18.0,38.0zM24.0,38.0c-0.6,0.0 -1.0,-0.4 -1.0,-1.0s0.4,-1.0 1.0,-1.0s1.0,0.4 1.0,1.0S24.6,38.0 24.0,38.0zM30.0,38.0c-0.6,0.0 -1.0,-0.4 -1.0,-1.0s0.4,-1.0 1.0,-1.0s1.0,0.4 1.0,1.0S30.6,38.0 30.0,38.0zM42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,28.0c0.0,2.2 1.8,4.0 4.0,4.0l36.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0zM42.0,34.0L6.0,34.0L6.0,14.0l36.0,0.0L42.0,34.0zM9.0,12.0L7.0,12.0l0.0,-2.0l2.0,0.0L9.0,12.0zM13.0,12.0l-2.0,0.0l0.0,-2.0l2.0,0.0L13.0,12.0zM17.0,12.0l-2.0,0.0l0.0,-2.0l2.0,0.0L17.0,12.0z"/>
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M22.7,19.0l-9.1,-9.1c0.9,-2.0 0.4,-5.0 -1.5,-6.9 -2.0,-2.0 -5.0,-2.4 -7.4,-1.3L9.0,6.0 6.0,9.0 1.6,4.7C0.4,7.0 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1.0,0.4 1.4,0.0l2.3,-2.3c0.5,-0.4 0.5,-1.0 0.1,-1.4z"/>
 </vector>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
index 7262ed2..8c8a3dd 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
@@ -44,14 +44,31 @@
             android:scaleType="centerInside"/>
     </com.android.systemui.statusbar.phone.MultiUserSwitch>
 
-    <com.android.keyguard.AlphaOptimizedImageButton android:id="@+id/settings_button"
-        style="@android:style/Widget.Material.Button.Borderless"
-        android:layout_toStartOf="@id/multi_user_switch"
+    <com.android.keyguard.AlphaOptimizedLinearLayout
+        android:id="@+id/settings_button_container"
         android:layout_width="48dp"
         android:layout_height="@dimen/status_bar_header_height"
-        android:background="@drawable/ripple_drawable"
-        android:src="@drawable/ic_settings"
-        android:contentDescription="@string/accessibility_desc_settings" />
+        android:paddingStart="12dp"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layout_toStartOf="@id/multi_user_switch">
+
+        <com.android.systemui.statusbar.phone.SettingsButton android:id="@+id/settings_button"
+            style="@android:style/Widget.Material.Button.Borderless"
+            android:layout_width="24dp"
+            android:layout_height="@dimen/status_bar_header_height"
+            android:background="@drawable/ripple_drawable"
+            android:src="@drawable/ic_settings"
+            android:contentDescription="@string/accessibility_desc_settings" />
+        <com.android.systemui.statusbar.AlphaOptimizedImageView android:id="@+id/tuner_icon"
+            android:layout_width="12dp"
+            android:layout_height="@dimen/status_bar_header_height"
+            android:tint="#4DFFFFFF"
+            android:tintMode="src_in"
+            android:visibility="invisible"
+            android:src="@drawable/tuner" />
+
+    </com.android.keyguard.AlphaOptimizedLinearLayout>
 
     <LinearLayout android:id="@+id/system_icons_super_container"
         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fcf7e3e..3eac84f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1032,7 +1032,7 @@
     <string name="volume_stream_vibrate_dnd" translatable="false">%s vibrate — Priority only</string>
 
     <!-- Name of special SystemUI debug settings -->
-    <string name="system_ui_tuner">System UI tuner</string>
+    <string name="system_ui_tuner">System UI Tuner</string>
 
     <!-- Preference to show/hide embedded battery percentage [CHAR LIMIT=50] -->
     <string name="show_battery_percentage">Show embedded battery percentage</string>
@@ -1099,4 +1099,25 @@
     <!-- Accessibility label for managed profile icon (not shown on screen) [CHAR LIMIT=NONE] -->
     <string name="accessibility_managed_profile">Work profile</string>
 
+    <!-- Title of warning when entering System UI tuner for first time [CHAR LIMIT=NONE] -->
+    <string name="tuner_warning_title">Fun for some but not for all</string>
+
+    <!-- Warning for users entering the System UI tuner for the first time [CHAR LIMIT=NONE]-->
+    <string name="tuner_warning">System UI Tuner gives you extra ways to tweak and customize the Android user interface. These experimental features may change, break, or disappear in future releases. Proceed with caution.</string>
+
+    <!-- Warning for users entering the System UI tuner [CHAR LIMIT=NONE]-->
+    <string name="tuner_persistent_warning">These experimental features may change, break, or disappear in future releases. Proceed with caution.</string>
+
+    <!-- Generic "got it" acceptance of dialog or cling [CHAR LIMIT=NONE] -->
+    <string name="got_it">Got it</string>
+
+    <!-- Toast describing tuner has been enabled [CHAR LIMIT=NONE] -->
+    <string name="tuner_toast">Congrats! System UI Tuner has been added to Settings</string>
+
+    <!-- Option to remove the tuner from settings [CHAR LIMIT=NONE] -->
+    <string name="remove_from_settings">Remove from Settings</string>
+
+    <!-- Dialog asking if the tuner should really be removed from settings [CHAR LIMIT=NONE]-->
+    <string name="remove_from_settings_prompt">Remove System UI Tuner from Settings and stop using all of its features?"</string>
+
 </resources>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 8c1acc3..3a41c3c 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -76,4 +76,8 @@
         android:key="demo_mode"
         android:title="@string/demo_mode" />
 
+    <Preference
+        android:summary="@string/tuner_persistent_warning"
+        android:selectable="false" />
+
 </PreferenceScreen>
diff --git a/packages/SystemUI/src/com/android/systemui/DemoMode.java b/packages/SystemUI/src/com/android/systemui/DemoMode.java
index d406f5b..11996d0 100644
--- a/packages/SystemUI/src/com/android/systemui/DemoMode.java
+++ b/packages/SystemUI/src/com/android/systemui/DemoMode.java
@@ -20,6 +20,8 @@
 
 public interface DemoMode {
 
+    public static final String DEMO_MODE_ALLOWED = "sysui_demo_allowed";
+
     void dispatchDemoCommand(String command, Bundle args);
 
     public static final String ACTION_DEMO = "com.android.systemui.demo";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index d829299..42e35a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -3211,7 +3211,7 @@
     public void dispatchDemoCommand(String command, Bundle args) {
         if (!mDemoModeAllowed) {
             mDemoModeAllowed = Settings.Global.getInt(mContext.getContentResolver(),
-                    "sysui_demo_allowed", 0) != 0;
+                    DEMO_MODE_ALLOWED, 0) != 0;
         }
         if (!mDemoModeAllowed) return;
         if (command.equals(COMMAND_ENTER)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
new file mode 100644
index 0000000..a1e9ece
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
@@ -0,0 +1,174 @@
+/*
+ * 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.
+ */
+package com.android.systemui.statusbar.phone;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+
+import com.android.keyguard.AlphaOptimizedImageButton;
+
+public class SettingsButton extends AlphaOptimizedImageButton {
+
+    private static final long LONG_PRESS_LENGTH = 1000;
+    private static final long ACCEL_LENGTH = 750;
+    private static final long FULL_SPEED_LENGTH = 375;
+    private static final long RUN_DURATION = 350;
+
+    private boolean mUpToSpeed;
+    private ObjectAnimator mAnimator;
+
+    private float mSlop;
+
+    public SettingsButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+    }
+
+    public boolean isAnimating() {
+        return mAnimator != null && mAnimator.isRunning();
+    }
+
+    public boolean isTunerClick() {
+        return mUpToSpeed;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                postDelayed(mLongPressCallback, LONG_PRESS_LENGTH);
+                break;
+            case MotionEvent.ACTION_UP:
+                if (mUpToSpeed) {
+                    startExitAnimation();
+                } else {
+                    cancelLongClick();
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                cancelLongClick();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                float x = event.getX();
+                float y = event.getY();
+                if ((x < -mSlop) || (y < -mSlop) || (x > getWidth() + mSlop)
+                        || (y > getHeight() + mSlop)) {
+                    cancelLongClick();
+                }
+                break;
+        }
+        return super.onTouchEvent(event);
+    }
+
+    private void cancelLongClick() {
+        cancelAnimation();
+        mUpToSpeed = false;
+        removeCallbacks(mLongPressCallback);
+    }
+
+    private void cancelAnimation() {
+        if (mAnimator != null) {
+            mAnimator.removeAllListeners();
+            mAnimator.cancel();
+            mAnimator = null;
+        }
+    }
+
+    private void startExitAnimation() {
+        animate()
+                .translationX(((View) getParent().getParent()).getWidth() - getX())
+                .alpha(0)
+                .setDuration(RUN_DURATION)
+                .setInterpolator(AnimationUtils.loadInterpolator(mContext,
+                        android.R.interpolator.accelerate_cubic))
+                .setListener(new AnimatorListener() {
+                    @Override
+                    public void onAnimationStart(Animator animation) {
+                    }
+
+                    @Override
+                    public void onAnimationRepeat(Animator animation) {
+                    }
+
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        setAlpha(1f);
+                        setTranslationX(0);
+                        cancelLongClick();
+                    }
+
+                    @Override
+                    public void onAnimationCancel(Animator animation) {
+                    }
+                })
+                .start();
+    }
+
+    protected void startAccelSpin() {
+        cancelAnimation();
+        mAnimator = ObjectAnimator.ofFloat(this, View.ROTATION, 0, 360);
+        mAnimator.setInterpolator(AnimationUtils.loadInterpolator(mContext,
+                android.R.interpolator.accelerate_quad));
+        mAnimator.setDuration(ACCEL_LENGTH);
+        mAnimator.addListener(new AnimatorListener() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+            }
+
+            @Override
+            public void onAnimationRepeat(Animator animation) {
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                startContinuousSpin();
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+            }
+        });
+        mAnimator.start();
+    }
+
+    protected void startContinuousSpin() {
+        cancelAnimation();
+        mUpToSpeed = true;
+        mAnimator = ObjectAnimator.ofFloat(this, View.ROTATION, 0, 360);
+        mAnimator.setInterpolator(AnimationUtils.loadInterpolator(mContext,
+                android.R.interpolator.linear));
+        mAnimator.setDuration(FULL_SPEED_LENGTH);
+        mAnimator.setRepeatCount(Animation.INFINITE);
+        mAnimator.start();
+    }
+
+    private final Runnable mLongPressCallback = new Runnable() {
+        @Override
+        public void run() {
+            startAccelSpin();
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index a81f06e2..5d58cd0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -37,6 +37,7 @@
 import android.widget.RelativeLayout;
 import android.widget.Switch;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.BatteryMeterView;
@@ -48,6 +49,7 @@
 import com.android.systemui.statusbar.policy.NetworkControllerImpl.EmergencyListener;
 import com.android.systemui.statusbar.policy.NextAlarmController;
 import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.tuner.TunerService;
 
 import java.text.NumberFormat;
 
@@ -73,7 +75,8 @@
     private TextView mDateExpanded;
     private LinearLayout mSystemIcons;
     private View mSignalCluster;
-    private View mSettingsButton;
+    private SettingsButton mSettingsButton;
+    private View mSettingsContainer;
     private View mQsDetailHeader;
     private TextView mQsDetailHeaderTitle;
     private Switch mQsDetailHeaderSwitch;
@@ -142,7 +145,8 @@
         mMultiUserAvatar = (ImageView) findViewById(R.id.multi_user_avatar);
         mDateCollapsed = (TextView) findViewById(R.id.date_collapsed);
         mDateExpanded = (TextView) findViewById(R.id.date_expanded);
-        mSettingsButton = findViewById(R.id.settings_button);
+        mSettingsButton = (SettingsButton) findViewById(R.id.settings_button);
+        mSettingsContainer = findViewById(R.id.settings_button_container);
         mSettingsButton.setOnClickListener(this);
         mQsDetailHeader = findViewById(R.id.qs_detail_header);
         mQsDetailHeader.setAlpha(0);
@@ -323,13 +327,15 @@
         mDateCollapsed.setVisibility(mExpanded && mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
         mDateExpanded.setVisibility(mExpanded && mAlarmShowing ? View.INVISIBLE : View.VISIBLE);
         mAlarmStatus.setVisibility(mExpanded && mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
-        mSettingsButton.setVisibility(mExpanded ? View.VISIBLE : View.INVISIBLE);
+        mSettingsContainer.setVisibility(mExpanded ? View.VISIBLE : View.INVISIBLE);
         mQsDetailHeader.setVisibility(mExpanded && mShowingDetail? View.VISIBLE : View.INVISIBLE);
         if (mSignalCluster != null) {
             updateSignalClusterDetachment();
         }
         mEmergencyCallsOnly.setVisibility(mExpanded && mShowEmergencyCallsOnly ? VISIBLE : GONE);
         mBatteryLevel.setVisibility(mExpanded ? View.VISIBLE : View.GONE);
+        mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
+                TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
     }
 
     private void updateSignalClusterDetachment() {
@@ -352,7 +358,7 @@
     private void updateSystemIconsLayoutParams() {
         RelativeLayout.LayoutParams lp = (LayoutParams) mSystemIconsSuperContainer.getLayoutParams();
         int rule = mExpanded
-                ? mSettingsButton.getId()
+                ? mSettingsContainer.getId()
                 : mMultiUserSwitch.getId();
         if (rule != lp.getRules()[RelativeLayout.START_OF]) {
             lp.addRule(RelativeLayout.START_OF, rule);
@@ -495,6 +501,20 @@
     @Override
     public void onClick(View v) {
         if (v == mSettingsButton) {
+            if (mSettingsButton.isTunerClick()) {
+                if (TunerService.isTunerEnabled(mContext)) {
+                    TunerService.showResetRequest(mContext, new Runnable() {
+                        @Override
+                        public void run() {
+                            // Relaunch settings so that the tuner disappears.
+                            startSettingsActivity();
+                        }
+                    });
+                } else {
+                    Toast.makeText(getContext(), R.string.tuner_toast, Toast.LENGTH_LONG).show();
+                    TunerService.setTunerEnabled(mContext, true);
+                }
+            }
             startSettingsActivity();
         } else if (v == mSystemIconsSuperContainer) {
             startBatteryActivity();
@@ -567,10 +587,10 @@
         }
         target.batteryY = mSystemIconsSuperContainer.getTop() + mSystemIconsContainer.getTop();
         target.batteryLevelAlpha = getAlphaForVisibility(mBatteryLevel);
-        target.settingsAlpha = getAlphaForVisibility(mSettingsButton);
+        target.settingsAlpha = getAlphaForVisibility(mSettingsContainer);
         target.settingsTranslation = mExpanded
                 ? 0
-                : mMultiUserSwitch.getLeft() - mSettingsButton.getLeft();
+                : mMultiUserSwitch.getLeft() - mSettingsContainer.getLeft();
         target.signalClusterAlpha = mSignalClusterDetached ? 0f : 1f;
         target.settingsRotation = !mExpanded ? 90f : 0f;
     }
@@ -622,9 +642,11 @@
             mSignalCluster.setTranslationX(0f);
             mSignalCluster.setTranslationY(0f);
         }
-        mSettingsButton.setTranslationY(mSystemIconsSuperContainer.getTranslationY());
-        mSettingsButton.setTranslationX(values.settingsTranslation);
-        mSettingsButton.setRotation(values.settingsRotation);
+        if (!mSettingsButton.isAnimating()) {
+            mSettingsContainer.setTranslationY(mSystemIconsSuperContainer.getTranslationY());
+            mSettingsContainer.setTranslationX(values.settingsTranslation);
+            mSettingsButton.setRotation(values.settingsRotation);
+        }
         applyAlpha(mEmergencyCallsOnly, values.emergencyCallsOnlyAlpha);
         if (!mShowingDetail && !mDetailTransitioning) {
             // Otherwise it needs to stay invisible
@@ -633,7 +655,7 @@
         applyAlpha(mDateCollapsed, values.dateCollapsedAlpha);
         applyAlpha(mDateExpanded, values.dateExpandedAlpha);
         applyAlpha(mBatteryLevel, values.batteryLevelAlpha);
-        applyAlpha(mSettingsButton, values.settingsAlpha);
+        applyAlpha(mSettingsContainer, values.settingsAlpha);
         applyAlpha(mSignalCluster, values.signalClusterAlpha);
         if (!mExpanded) {
             mTime.setScaleX(1f);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index 3f5ca58..41f83f7 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -28,13 +28,13 @@
 import android.preference.PreferenceScreen;
 import android.preference.SwitchPreference;
 import android.provider.Settings;
+import android.view.MenuItem;
 
 import com.android.systemui.DemoMode;
 import com.android.systemui.R;
 
 public class DemoModeFragment extends PreferenceFragment implements OnPreferenceChangeListener {
 
-    private static final String DEMO_MODE_ALLOWED = "sysui_demo_allowed";
     private static final String DEMO_MODE_ON = "sysui_tuner_demo_on";
 
     private static final String[] STATUS_ICONS = {
@@ -75,10 +75,21 @@
         updateDemoModeEnabled();
         updateDemoModeOn();
         ContentResolver contentResolver = getContext().getContentResolver();
-        contentResolver.registerContentObserver(Settings.Global.getUriFor(DEMO_MODE_ALLOWED), false,
-                mDemoModeObserver);
+        contentResolver.registerContentObserver(Settings.Global.getUriFor(
+                DemoMode.DEMO_MODE_ALLOWED), false, mDemoModeObserver);
         contentResolver.registerContentObserver(Settings.Global.getUriFor(DEMO_MODE_ON), false,
                 mDemoModeObserver);
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                getFragmentManager().popBackStack();
+                break;
+        }
+        return super.onOptionsItemSelected(item);
     }
 
     @Override
@@ -89,7 +100,7 @@
 
     private void updateDemoModeEnabled() {
         boolean enabled = Settings.Global.getInt(getContext().getContentResolver(),
-                DEMO_MODE_ALLOWED, 0) != 0;
+                DemoMode.DEMO_MODE_ALLOWED, 0) != 0;
         mEnabledSwitch.setChecked(enabled);
         mOnSwitch.setEnabled(enabled);
     }
@@ -108,7 +119,7 @@
                 mOnSwitch.setChecked(false);
                 stopDemoMode();
             }
-            setGlobal(DEMO_MODE_ALLOWED, newValue == Boolean.TRUE ? 1 : 0);
+            setGlobal(DemoMode.DEMO_MODE_ALLOWED, newValue == Boolean.TRUE ? 1 : 0);
         } else if (preference == mOnSwitch) {
             if (newValue == Boolean.TRUE) {
                 startDemoMode();
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
index a5b244e..8d19891 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
@@ -85,6 +85,9 @@
             case MENU_RESET:
                 mTileHost.reset();
                 break;
+            case android.R.id.home:
+                getFragmentManager().popBackStack();
+                break;
         }
         return super.onOptionsItemSelected(item);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index 4a8c2e4..4bb9250 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -17,7 +17,10 @@
 
 import static com.android.systemui.BatteryMeterView.SHOW_PERCENT_SETTING;
 
+import android.app.AlertDialog;
 import android.app.FragmentTransaction;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Bundle;
@@ -28,7 +31,10 @@
 import android.preference.PreferenceFragment;
 import android.preference.PreferenceGroup;
 import android.preference.SwitchPreference;
+import android.provider.Settings;
 import android.provider.Settings.System;
+import android.view.Menu;
+import android.view.MenuInflater;
 import android.view.MenuItem;
 
 import com.android.systemui.R;
@@ -37,10 +43,16 @@
 
 public class TunerFragment extends PreferenceFragment {
 
+    private static final String TAG = "TunerFragment";
+
     private static final String KEY_QS_TUNER = "qs_tuner";
     private static final String KEY_DEMO_MODE = "demo_mode";
     private static final String KEY_BATTERY_PCT = "battery_pct";
 
+    public static final String SETTING_SEEN_TUNER_WARNING = "seen_tuner_warning";
+
+    private static final int MENU_REMOVE = Menu.FIRST + 1;
+
     private final SettingObserver mSettingObserver = new SettingObserver();
 
     private SwitchPreference mBatteryPct;
@@ -73,6 +85,19 @@
             }
         });
         mBatteryPct = (SwitchPreference) findPreference(KEY_BATTERY_PCT);
+        if (Settings.Secure.getInt(getContext().getContentResolver(), SETTING_SEEN_TUNER_WARNING,
+                0) == 0) {
+            new AlertDialog.Builder(getContext())
+                    .setTitle(R.string.tuner_warning_title)
+                    .setMessage(R.string.tuner_warning)
+                    .setPositiveButton(R.string.got_it, new OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            Settings.Secure.putInt(getContext().getContentResolver(),
+                                    SETTING_SEEN_TUNER_WARNING, 1);
+                        }
+                    }).show();
+        }
     }
 
     @Override
@@ -120,11 +145,24 @@
     }
 
     @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        menu.add(Menu.NONE, MENU_REMOVE, Menu.NONE, R.string.remove_from_settings);
+    }
+
+    @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case android.R.id.home:
                 getActivity().finish();
                 return true;
+            case MENU_REMOVE:
+                TunerService.showResetRequest(getContext(), new Runnable() {
+                    @Override
+                    public void run() {
+                        getActivity().finish();
+                    }
+                });
+                return true;
         }
         return super.onOptionsItemSelected(item);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index de5aaf6..d3f33ab 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -16,8 +16,15 @@
 package com.android.systemui.tuner;
 
 import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
@@ -25,9 +32,13 @@
 import android.provider.Settings;
 import android.util.ArrayMap;
 
+import com.android.systemui.BatteryMeterView;
+import com.android.systemui.DemoMode;
+import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -36,6 +47,8 @@
 
 public class TunerService extends SystemUI {
 
+    public static final String ACTION_CLEAR = "com.android.systemui.action.CLEAR_TUNER";
+
     private final Observer mObserver = new Observer();
     // Map of Uris we listen on to their settings keys.
     private final ArrayMap<Uri, String> mListeningUris = new ArrayMap<>();
@@ -118,6 +131,19 @@
         }
     }
 
+    public void clearAll() {
+        // A couple special cases.
+        Settings.Global.putString(mContentResolver, DemoMode.DEMO_MODE_ALLOWED, null);
+        Settings.System.putString(mContentResolver, BatteryMeterView.SHOW_PERCENT_SETTING, null);
+        Intent intent = new Intent(DemoMode.ACTION_DEMO);
+        intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT);
+        mContext.sendBroadcast(intent);
+
+        for (String key : mTunableLookup.keySet()) {
+            Settings.Secure.putString(mContentResolver, key, null);
+        }
+    }
+
     // Only used in other processes, such as the tuner.
     private static TunerService sInstance;
 
@@ -141,6 +167,44 @@
         return sInstance;
     }
 
+    public static final void showResetRequest(final Context context, final Runnable onDisabled) {
+        SystemUIDialog dialog = new SystemUIDialog(context);
+        dialog.setMessage(R.string.remove_from_settings_prompt);
+        dialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.cancel),
+                (OnClickListener) null);
+        dialog.setButton(DialogInterface.BUTTON_POSITIVE,
+                context.getString(R.string.guest_exit_guest_dialog_remove), new OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                // Tell the tuner (in main SysUI process) to clear all its settings.
+                context.sendBroadcast(new Intent(TunerService.ACTION_CLEAR));
+                // Disable access to tuner.
+                TunerService.setTunerEnabled(context, false);
+                // Make them sit through the warning dialog again.
+                Settings.Secure.putInt(context.getContentResolver(),
+                        TunerFragment.SETTING_SEEN_TUNER_WARNING, 0);
+                if (onDisabled != null) {
+                    onDisabled.run();
+                }
+            }
+        });
+        dialog.show();
+    }
+
+    public static final void setTunerEnabled(Context context, boolean enabled) {
+        context.getPackageManager().setComponentEnabledSetting(
+                new ComponentName(context, TunerActivity.class),
+                enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                        : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                        PackageManager.DONT_KILL_APP);
+    }
+
+    public static final boolean isTunerEnabled(Context context) {
+        return context.getPackageManager().getComponentEnabledSetting(
+                new ComponentName(context, TunerActivity.class))
+                == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+    }
+
     private class Observer extends ContentObserver {
         public Observer() {
             super(new Handler(Looper.getMainLooper()));
@@ -157,4 +221,13 @@
     public interface Tunable {
         void onTuningChanged(String key, String newValue);
     }
+
+    public static class ClearReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (ACTION_CLEAR.equals(intent.getAction())) {
+                get(context).clearAll();
+            }
+        }
+    }
 }