Merge "Import translations. DO NOT MERGE" into lmp-dev
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 07b3633..21525bc 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -813,14 +813,12 @@
      */
     public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
 
-
     /**
-     * {@link #extras} key: Bitmap representing the profile badge to be shown with the
-     * notification.
+     * {@link #extras} key: the user that built the notification.
      *
      * @hide
      */
-    public static final String EXTRA_PROFILE_BADGE = "android.profileBadge";
+    public static final String EXTRA_ORIGINATING_USERID = "android.originatingUserId";
 
     /**
      * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification should not be
@@ -1870,7 +1868,11 @@
         private final NotificationColorUtil mColorUtil;
         private ArrayList<String> mPeople;
         private int mColor = COLOR_DEFAULT;
-        private Bitmap mProfileBadge;
+
+        /**
+         * The user that built the notification originally.
+         */
+        private int mOriginatingUserId;
 
         /**
          * Contains extras related to rebuilding during the build phase.
@@ -2579,8 +2581,10 @@
         }
 
         private Bitmap getProfileBadge() {
+            // Note: This assumes that the current user can read the profile badge of the
+            // originating user.
             UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-            Drawable badge = userManager.getBadgeForUser(android.os.Process.myUserHandle());
+            Drawable badge = userManager.getBadgeForUser(new UserHandle(mOriginatingUserId));
             if (badge == null) {
                 return null;
             }
@@ -2594,6 +2598,7 @@
         }
 
         private RemoteViews applyStandardTemplate(int resId, boolean fitIn1U) {
+            Bitmap profileIcon = getProfileBadge();
             RemoteViews contentView = new BuilderRemoteViews(mContext.getPackageName(), resId);
             boolean showLine3 = false;
             boolean showLine2 = false;
@@ -2601,8 +2606,8 @@
             if (mPriority < PRIORITY_LOW) {
                 // TODO: Low priority presentation
             }
-            if (mProfileBadge != null) {
-                contentView.setImageViewBitmap(R.id.profile_icon, mProfileBadge);
+            if (profileIcon != null) {
+                contentView.setImageViewBitmap(R.id.profile_icon, profileIcon);
                 contentView.setViewVisibility(R.id.profile_icon, View.VISIBLE);
             } else {
                 contentView.setViewVisibility(R.id.profile_icon, View.GONE);
@@ -2927,6 +2932,7 @@
          */
         public void populateExtras(Bundle extras) {
             // Store original information used in the construction of this object
+            extras.putInt(EXTRA_ORIGINATING_USERID, mOriginatingUserId);
             extras.putString(EXTRA_REBUILD_CONTEXT_PACKAGE, mContext.getPackageName());
             extras.putCharSequence(EXTRA_TITLE, mContentTitle);
             extras.putCharSequence(EXTRA_TEXT, mContentText);
@@ -2944,9 +2950,6 @@
             if (!mPeople.isEmpty()) {
                 extras.putStringArray(EXTRA_PEOPLE, mPeople.toArray(new String[mPeople.size()]));
             }
-            if (mProfileBadge != null) {
-                extras.putParcelable(EXTRA_PROFILE_BADGE, mProfileBadge);
-            }
             // NOTE: If you're adding new extras also update restoreFromNotification().
         }
 
@@ -3157,6 +3160,7 @@
 
             // Extras.
             Bundle extras = n.extras;
+            mOriginatingUserId = extras.getInt(EXTRA_ORIGINATING_USERID);
             mContentTitle = extras.getCharSequence(EXTRA_TITLE);
             mContentText = extras.getCharSequence(EXTRA_TEXT);
             mSubText = extras.getCharSequence(EXTRA_SUB_TEXT);
@@ -3174,9 +3178,6 @@
                 mPeople.clear();
                 Collections.addAll(mPeople, extras.getStringArray(EXTRA_PEOPLE));
             }
-            if (extras.containsKey(EXTRA_PROFILE_BADGE)) {
-                mProfileBadge = extras.getParcelable(EXTRA_PROFILE_BADGE);
-            }
         }
 
         /**
@@ -3192,7 +3193,7 @@
          * object.
          */
         public Notification build() {
-            mProfileBadge = getProfileBadge();
+            mOriginatingUserId = mContext.getUserId();
 
             Notification n = buildUnstyled();
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 969c0db..7901379 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -35,6 +35,8 @@
     void setHardKeyboardStatus(boolean available, boolean enabled);
     void setWindowState(int window, int state);
     void buzzBeepBlinked();
+    void notificationLightOff();
+    void notificationLightPulse(int argb, int millisOn, int millisOff);
 
     void showRecentApps(boolean triggeredFromAltTab);
     void hideRecentApps(boolean triggeredFromAltTab);
diff --git a/packages/PrintSpooler/res/values-hi/strings.xml b/packages/PrintSpooler/res/values-hi/strings.xml
index 52a018f..581995a 100644
--- a/packages/PrintSpooler/res/values-hi/strings.xml
+++ b/packages/PrintSpooler/res/values-hi/strings.xml
@@ -35,7 +35,7 @@
     <string name="generating_print_job" msgid="3119608742651698916">"प्रिंट कार्य जनरेट हो रहा है"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"PDF के रूप में सहेजें"</string>
     <string name="all_printers" msgid="5018829726861876202">"सभी प्रिंटर..."</string>
-    <string name="print_dialog" msgid="32628687461331979">"प्रिंट संवाद"</string>
+    <string name="print_dialog" msgid="32628687461331979">"प्रिंट डॉयलॉग"</string>
     <string name="current_page_template" msgid="1386638343571771292">"<xliff:g id="CURRENT_PAGE">%1$d</xliff:g> /<xliff:g id="PAGE_COUNT">%2$d</xliff:g>"</string>
     <string name="search" msgid="5421724265322228497">"खोजें"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"सभी प्रिंटर"</string>
diff --git a/packages/SystemUI/res/drawable/ic_qs_vpn.xml b/packages/SystemUI/res/drawable/ic_qs_vpn.xml
new file mode 100644
index 0000000..e9141ef
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_vpn.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2014 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="12.0dp"
+        android:height="12.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+
+    <path
+        android:fillColor="#4DFFFFFF"
+        android:pathData="M22.000000,4.000000L22.000000,3.500000C22.000000,2.100000 20.900000,1.000000 19.500000,1.000000C18.100000,1.000000 17.000000,2.100000 17.000000,3.500000L17.000000,4.000000c-0.600000,0.000000 -1.000000,0.400000 -1.000000,1.000000l0.000000,4.000000c0.000000,0.600000 0.400000,1.000000 1.000000,1.000000l5.000000,0.000000c0.600000,0.000000 1.000000,-0.400000 1.000000,-1.000000L23.000000,5.000000C23.000000,4.400000 22.600000,4.000000 22.000000,4.000000zM21.200001,4.000000l-3.400000,0.000000L17.800001,3.500000c0.000000,-0.900000 0.800000,-1.700000 1.700000,-1.700000c0.900000,0.000000 1.700000,0.800000 1.700000,1.700000L21.200003,4.000000zM18.900000,12.000000c0.000000,0.300000 0.100000,0.700000 0.100000,1.000000c0.000000,2.100000 -0.800000,4.000000 -2.100000,5.400000c-0.300000,-0.800000 -1.000000,-1.400000 -1.900000,-1.400000l-1.000000,0.000000l0.000000,-3.000000c0.000000,-0.600000 -0.400000,-1.000000 -1.000000,-1.000000L7.000000,13.000000l0.000000,-2.000000l2.000000,0.000000c0.600000,0.000000 1.000000,-0.400000 1.000000,-1.000000L10.000000,8.000000l2.000000,0.000000c1.100000,0.000000 2.000000,-0.900000 2.000000,-2.000000L14.000000,3.500000C13.100000,3.200000 12.000000,3.000000 11.000000,3.000000C5.500000,3.000000 1.000000,7.500000 1.000000,13.000000c0.000000,5.500000 4.500000,10.000000 10.000000,10.000000c5.500000,0.000000 10.000000,-4.500000 10.000000,-10.000000c0.000000,-0.300000 0.000000,-0.700000 -0.100000,-1.000000L18.900000,12.000000zM10.000000,20.900000c-3.900000,-0.500000 -7.000000,-3.900000 -7.000000,-7.900000c0.000000,-0.600000 0.100000,-1.200000 0.200000,-1.800000L8.000000,16.000000l0.000000,1.000000c0.000000,1.100000 0.900000,2.000000 2.000000,2.000000L10.000000,20.900000z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml
new file mode 100644
index 0000000..53baf74
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_footer.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clickable="true"
+    android:paddingBottom="@dimen/qs_tile_padding_top"
+    android:paddingTop="@dimen/qs_tile_padding_top" >
+
+    <TextView
+        android:id="@+id/footer_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:textSize="@dimen/qs_tile_text_size" />
+
+    <ImageView
+        android:id="@+id/footer_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_marginEnd="8dp"
+        android:layout_toStartOf="@id/footer_text"
+        android:contentDescription="@null"
+        android:src="@drawable/ic_qs_vpn"
+        android:visibility="invisible" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 230f4af..a4576bb 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -162,11 +162,17 @@
     <!-- Doze: does this device support STATE_DOZE and STATE_DOZE_SUSPEND?  -->
     <bool name="doze_display_state_supported">false</bool>
 
-    <!-- Doze: should the significant motion sensor be used as a tease signal? -->
-    <bool name="doze_tease_on_significant_motion">false</bool>
+    <!-- Doze: should the significant motion sensor be used as a pulse signal? -->
+    <bool name="doze_pulse_on_significant_motion">false</bool>
 
-    <!-- Doze: maximum brightness to use when teasing -->
-    <integer name="doze_tease_brightness">80</integer>
+    <!-- Doze: maximum brightness to use when pulsing -->
+    <integer name="doze_pulse_brightness">40</integer>
+
+    <!-- Doze: number of pulses when doing multiple pulses in quick succession -->
+    <integer name="doze_multipulse_count">3</integer>
+
+    <!-- Doze: interval between pulses when following the notification light -->
+    <integer name="doze_notification_pulse_interval">30000</integer>
 
     <!-- Volume: time to delay dismissing the volume panel after a click is performed -->
     <integer name="volume_panel_dismiss_delay">200</integer>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 296cdad..83a9a81 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -741,4 +741,38 @@
 
     <!-- Text which is shown in the notification shade when there are no notifications. [CHAR LIMIT=30] -->
     <string name="empty_shade_text">No notifications</string>
+
+    <!-- Footer device owned text [CHAR LIMIT=50] -->
+    <string name="device_owned_footer">Device may be monitored</string>
+
+    <!-- Footer vpn present text [CHAR LIMIT=50] -->
+    <string name="vpn_footer">Network may be monitored</string>
+
+    <!-- Monitoring dialog title for device owned devices [CHAR LIMIT=35] -->
+    <string name="monitoring_title_device_owned">Device monitoring</string>
+
+    <!-- Monitoring dialog title for normal devices  [CHAR LIMIT=35]-->
+    <string name="monitoring_title">Network monitoring</string>
+
+    <!-- Monitoring dialog open app button [CHAR LIMIT=30] -->
+    <string name="open_app">Open app</string>
+
+    <!-- Monitoring dialog disconnect vpn button [CHAR LIMIT=30] -->
+    <string name="disconnect_vpn">Disconnect VPN</string>
+
+    <!-- Monitoring dialog device owner body text [CHAR LIMIT=300] -->
+    <string name="monitoring_description_device_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator can monitor your network activity, including emails, apps and secure websites.\n\nFor more information, contact your administrator.</string>
+
+    <!-- Monitoring dialog non-legacy VPN text [CHAR LIMIT=300] -->
+    <string name="monitoring_description_vpn">You gave \"<xliff:g id="application">%1$s</xliff:g>\" permission to set up a VPN connection.\n\nThis app can monitor your network activity, including emails, apps and secure websites.</string>
+
+    <!-- Monitoring dialog legacy VPN text [CHAR LIMIT=300] -->
+    <string name="monitoring_description_legacy_vpn">You\'re connected to a VPN (\"<xliff:g id="application">%1$s</xliff:g>\").\n\nYour VPN service provider can monitor your network activity including emails, apps, and secure websites.</string>
+
+    <!-- Monitoring dialog non-legacy VPN with device owner text [CHAR LIMIT=300] -->
+    <string name="monitoring_description_vpn_device_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites. For more information, contact your administrator.\n\nAlso, you gave \"<xliff:g id="application">%2$s</xliff:g>\" permission to set up a VPN connection. This app can monitor network activity too.</string>
+
+    <!-- Monitoring dialog legacy VPN with device owner text [CHAR LIMIT=300] -->
+    <string name="monitoring_description_legacy_vpn_device_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites. For more information, contact your administrator.\n\nAlso, you\'re connected to a VPN (\"<xliff:g id="application">%2$s</xliff:g>\"). Your VPN service provider can monitor network activity too.</string>
+
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 13c15f5..943a294 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -19,6 +19,8 @@
 import static android.os.PowerManager.BRIGHTNESS_OFF;
 import static android.os.PowerManager.BRIGHTNESS_ON;
 
+import android.app.AlarmManager;
+import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -42,12 +44,16 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Date;
 
 public class DozeService extends DreamService {
     private static final String TAG = "DozeService";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final String TEASE_ACTION = "com.android.systemui.doze.tease";
+    private static final String ACTION_BASE = "com.android.systemui.doze";
+    private static final String PULSE_ACTION = ACTION_BASE + ".pulse";
+    private static final String NOTIFICATION_PULSE_ACTION = ACTION_BASE + ".notification_pulse";
+    private static final String EXTRA_PULSES = "pulses";
 
     private final String mTag = String.format(TAG + ".%08x", hashCode());
     private final Context mContext = this;
@@ -58,13 +64,18 @@
     private Sensor mSigMotionSensor;
     private PowerManager mPowerManager;
     private PowerManager.WakeLock mWakeLock;
+    private AlarmManager mAlarmManager;
     private int mMaxBrightness;
     private boolean mDreaming;
-    private boolean mTeaseReceiverRegistered;
+    private boolean mBroadcastReceiverRegistered;
     private boolean mSigMotionConfigured;
     private boolean mSigMotionEnabled;
     private boolean mDisplayStateSupported;
     private int mDisplayStateWhenOn;
+    private boolean mNotificationLightOn;
+    private PendingIntent mNotificationPulseIntent;
+    private int mMultipulseCount;
+    private int mNotificationPulseInterval;
 
     public DozeService() {
         if (DEBUG) Log.d(mTag, "new DozeService()");
@@ -75,12 +86,15 @@
     protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) {
         super.dumpOnHandler(fd, pw, args);
         pw.print("  mDreaming: "); pw.println(mDreaming);
-        pw.print("  mTeaseReceiverRegistered: "); pw.println(mTeaseReceiverRegistered);
+        pw.print("  mBroadcastReceiverRegistered: "); pw.println(mBroadcastReceiverRegistered);
         pw.print("  mSigMotionSensor: "); pw.println(mSigMotionSensor);
         pw.print("  mSigMotionConfigured: "); pw.println(mSigMotionConfigured);
         pw.print("  mSigMotionEnabled: "); pw.println(mSigMotionEnabled);
         pw.print("  mMaxBrightness: "); pw.println(mMaxBrightness);
         pw.print("  mDisplayStateSupported: "); pw.println(mDisplayStateSupported);
+        pw.print("  mNotificationLightOn: "); pw.println(mNotificationLightOn);
+        pw.print("  mMultipulseCount: "); pw.println(mMultipulseCount);
+        pw.print("  mNotificationPulseInterval: "); pw.println(mNotificationPulseInterval);
     }
 
     @Override
@@ -99,14 +113,21 @@
         mSigMotionSensor = mSensors.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
+        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
         final Resources res = mContext.getResources();
-        mSigMotionConfigured = SystemProperties.getBoolean("doze.tease.sigmotion",
-                res.getBoolean(R.bool.doze_tease_on_significant_motion));
+        mSigMotionConfigured = SystemProperties.getBoolean("doze.pulse.sigmotion",
+                res.getBoolean(R.bool.doze_pulse_on_significant_motion));
         mDisplayStateSupported = SystemProperties.getBoolean("doze.display.supported",
                 res.getBoolean(R.bool.doze_display_state_supported));
-        mMaxBrightness = MathUtils.constrain(res.getInteger(R.integer.doze_tease_brightness),
+        mMaxBrightness = MathUtils.constrain(res.getInteger(R.integer.doze_pulse_brightness),
                 BRIGHTNESS_OFF, BRIGHTNESS_ON);
-
+        mNotificationPulseIntent = PendingIntent.getBroadcast(mContext, 0,
+                new Intent(NOTIFICATION_PULSE_ACTION).setPackage(getPackageName()),
+                PendingIntent.FLAG_CANCEL_CURRENT);
+        mMultipulseCount = SystemProperties.getInt("doze.multipulses",
+                res.getInteger(R.integer.doze_multipulse_count));
+        mNotificationPulseInterval = SystemProperties.getInt("doze.notification.pulse",
+                res.getInteger(R.integer.doze_notification_pulse_interval));
         mDisplayStateWhenOn = mDisplayStateSupported ? Display.STATE_DOZE : Display.STATE_ON;
         setDozeScreenState(mDisplayStateWhenOn);
     }
@@ -122,7 +143,7 @@
         super.onDreamingStarted();
         if (DEBUG) Log.d(mTag, "onDreamingStarted canDoze=" + canDoze());
         mDreaming = true;
-        listenForTeaseSignals(true);
+        listenForPulseSignals(true);
         requestDoze();
     }
 
@@ -160,7 +181,7 @@
         if (mWakeLock.isHeld()) {
             mWakeLock.release();
         }
-        listenForTeaseSignals(false);
+        listenForPulseSignals(false);
         stopDozing();
         dozingStopped();
     }
@@ -187,9 +208,17 @@
         }
     }
 
-    private void requestTease() {
+    private void requestMultipulse() {
+        requestPulse(mMultipulseCount);
+    }
+
+    private void requestPulse() {
+        requestPulse(1);
+    }
+
+    private void requestPulse(int pulses) {
         if (mHost != null) {
-            mHost.requestTease(this);
+            mHost.requestPulse(pulses, this);
         }
     }
 
@@ -199,10 +228,10 @@
         }
     }
 
-    private void listenForTeaseSignals(boolean listen) {
-        if (DEBUG) Log.d(mTag, "listenForTeaseSignals: " + listen);
+    private void listenForPulseSignals(boolean listen) {
+        if (DEBUG) Log.d(mTag, "listenForPulseSignals: " + listen);
         listenForSignificantMotion(listen);
-        listenForBroadcast(listen);
+        listenForBroadcasts(listen);
         listenForNotifications(listen);
     }
 
@@ -216,15 +245,17 @@
         }
     }
 
-    private void listenForBroadcast(boolean listen) {
+    private void listenForBroadcasts(boolean listen) {
         if (listen) {
-            mContext.registerReceiver(mTeaseReceiver, new IntentFilter(TEASE_ACTION));
-            mTeaseReceiverRegistered = true;
+            final IntentFilter filter = new IntentFilter(PULSE_ACTION);
+            filter.addAction(NOTIFICATION_PULSE_ACTION);
+            mContext.registerReceiver(mBroadcastReceiver, filter);
+            mBroadcastReceiverRegistered = true;
         } else {
-            if (mTeaseReceiverRegistered) {
-                mContext.unregisterReceiver(mTeaseReceiver);
+            if (mBroadcastReceiverRegistered) {
+                mContext.unregisterReceiver(mBroadcastReceiver);
             }
-            mTeaseReceiverRegistered = false;
+            mBroadcastReceiverRegistered = false;
         }
     }
 
@@ -237,6 +268,15 @@
         }
     }
 
+    private void rescheduleNotificationPulse() {
+        mAlarmManager.cancel(mNotificationPulseIntent);
+        if (mNotificationLightOn) {
+            final long time = System.currentTimeMillis() + mNotificationPulseInterval;
+            if (DEBUG) Log.d(TAG, "Scheduling pulse for " + new Date(time));
+            mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, time, mNotificationPulseIntent);
+        }
+    }
+
     private static String triggerEventToString(TriggerEvent event) {
         if (event == null) return null;
         final StringBuilder sb = new StringBuilder("TriggerEvent[")
@@ -269,16 +309,23 @@
                     v.vibrate(1000);
                 }
             }
-            requestTease();
+            requestPulse();
             listenForSignificantMotion(true);  // reregister, this sensor only fires once
         }
     };
 
-    private final BroadcastReceiver mTeaseReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (DEBUG) Log.d(mTag, "Received tease intent");
-            requestTease();
+            if (PULSE_ACTION.equals(intent.getAction())) {
+                if (DEBUG) Log.d(mTag, "Received pulse intent");
+                requestPulse(intent.getIntExtra(EXTRA_PULSES, mMultipulseCount));
+            }
+            if (NOTIFICATION_PULSE_ACTION.equals(intent.getAction())) {
+                if (DEBUG) Log.d(mTag, "Received notification pulse intent");
+                requestPulse();
+                rescheduleNotificationPulse();
+            }
         }
     };
 
@@ -288,10 +335,19 @@
             if (DEBUG) Log.d(mTag, "onNewNotifications");
             // noop for now
         }
+
         @Override
         public void onBuzzBeepBlinked() {
             if (DEBUG) Log.d(mTag, "onBuzzBeepBlinked");
-            requestTease();
+            requestMultipulse();
+        }
+
+        @Override
+        public void onNotificationLight(boolean on) {
+            if (DEBUG) Log.d(mTag, "onNotificationLight on=" + on);
+            if (mNotificationLightOn == on) return;
+            mNotificationLightOn = on;
+            rescheduleNotificationPulse();
         }
     };
 
@@ -299,12 +355,13 @@
         void addCallback(Callback callback);
         void removeCallback(Callback callback);
         void requestDoze(DozeService dozeService);
-        void requestTease(DozeService dozeService);
+        void requestPulse(int pulses, DozeService dozeService);
         void dozingStopped(DozeService dozeService);
 
         public interface Callback {
             void onNewNotifications();
             void onBuzzBeepBlinked();
+            void onNotificationLight(boolean on);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
new file mode 100644
index 0000000..f04a7b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2014 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.qs;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.SecurityController.VpnCallback;
+
+public class QSFooter implements OnClickListener, DialogInterface.OnClickListener {
+    protected static final String TAG = "QSFooter";
+    protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final View mRootView;
+    private final TextView mFooterText;
+    private final ImageView mFooterIcon;
+    private final Context mContext;
+    private final Callback mCallback = new Callback();
+
+    private SecurityController mSecurityController;
+    private AlertDialog mDialog;
+    private QSTileHost mHost;
+    private Handler mHandler;
+
+    public QSFooter(QSPanel qsPanel, Context context) {
+        mRootView = LayoutInflater.from(context)
+                .inflate(R.layout.quick_settings_footer, qsPanel, false);
+        mRootView.setOnClickListener(this);
+        mFooterText = (TextView) mRootView.findViewById(R.id.footer_text);
+        mFooterIcon = (ImageView) mRootView.findViewById(R.id.footer_icon);
+        mContext = context;
+    }
+
+    public void setHost(QSTileHost host) {
+        mHost = host;
+        mSecurityController = host.getSecurityController();
+        mHandler = new H(host.getLooper());
+    }
+
+    public void setListening(boolean listening) {
+        if (listening) {
+            mSecurityController.addCallback(mCallback);
+        } else {
+            mSecurityController.removeCallback(mCallback);
+        }
+    }
+
+    public View getView() {
+        return mRootView;
+    }
+
+    public boolean hasFooter() {
+        return mRootView.getVisibility() != View.GONE;
+    }
+
+    @Override
+    public void onClick(View v) {
+        mHandler.sendEmptyMessage(H.CLICK);
+    }
+
+    private void handleClick() {
+        mHost.collapsePanels();
+        // TODO: Delay dialog creation until after panels are collapsed.
+        createDialog();
+    }
+
+    public void refreshState() {
+        mHandler.sendEmptyMessage(H.REFRESH_STATE);
+    }
+
+    private void handleRefreshState() {
+        if (mSecurityController.hasDeviceOwner()) {
+            mFooterText.setText(R.string.device_owned_footer);
+            mRootView.setVisibility(View.VISIBLE);
+            mFooterIcon.setVisibility(View.INVISIBLE);
+        } else if (mSecurityController.isVpnEnabled()) {
+            mFooterText.setText(R.string.vpn_footer);
+            mRootView.setVisibility(View.VISIBLE);
+            mFooterIcon.setVisibility(View.VISIBLE);
+        } else {
+            mRootView.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == DialogInterface.BUTTON_NEGATIVE) {
+            if (mSecurityController.isLegacyVpn()) {
+                mSecurityController.disconnectFromLegacyVpn();
+            } else {
+                mSecurityController.openVpnApp();
+            }
+        }
+    }
+
+    private void createDialog() {
+        mDialog = new SystemUIDialog(mContext);
+        mDialog.setTitle(getTitle());
+        mDialog.setMessage(getMessage());
+        mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
+        if (mSecurityController.isVpnEnabled()) {
+            mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getNegativeButton(), this);
+        }
+        mDialog.show();
+    }
+
+    private String getNegativeButton() {
+        if (mSecurityController.isLegacyVpn()) {
+            return mContext.getString(R.string.disconnect_vpn);
+        } else {
+            return mContext.getString(R.string.open_app);
+        }
+    }
+
+    private String getPositiveButton() {
+        return mContext.getString(R.string.quick_settings_done);
+    }
+
+    private String getMessage() {
+        if (mSecurityController.hasDeviceOwner()) {
+            if (mSecurityController.isVpnEnabled()) {
+                if (mSecurityController.isLegacyVpn()) {
+                    return mContext.getString(
+                            R.string.monitoring_description_legacy_vpn_device_owned,
+                            mSecurityController.getDeviceOwnerName(),
+                            mSecurityController.getLegacyVpnName());
+                } else {
+                    return mContext.getString(R.string.monitoring_description_vpn_device_owned,
+                            mSecurityController.getDeviceOwnerName(),
+                            mSecurityController.getVpnApp());
+                }
+            } else {
+                return mContext.getString(R.string.monitoring_description_device_owned,
+                        mSecurityController.getDeviceOwnerName());
+            }
+        } else {
+            if (mSecurityController.isLegacyVpn()) {
+                return mContext.getString(R.string.monitoring_description_legacy_vpn,
+                        mSecurityController.getLegacyVpnName());
+
+            } else {
+                return mContext.getString(R.string.monitoring_description_vpn,
+                        mSecurityController.getVpnApp());
+            }
+        }
+    }
+
+    private int getTitle() {
+        if (mSecurityController.hasDeviceOwner()) {
+            return R.string.monitoring_title_device_owned;
+        }
+        return R.string.monitoring_title;
+    }
+
+    private class Callback implements VpnCallback {
+        @Override
+        public void onVpnStateChanged() {
+            refreshState();
+        }
+    }
+
+    private class H extends Handler {
+        private static final int CLICK = 0;
+        private static final int REFRESH_STATE = 1;
+
+        private H(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            String name = null;
+            try {
+                if (msg.what == REFRESH_STATE) {
+                    name = "handleRefreshState";
+                    handleRefreshState();
+                } else if (msg.what == CLICK) {
+                    name = "handleClick";
+                    handleClick();
+                }
+            } catch (Throwable t) {
+                final String error = "Error in " + name;
+                Log.w(TAG, error, t);
+                mHost.warn(error, t);
+            }
+        }
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 1173053..59f3b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -67,6 +67,8 @@
     private BrightnessController mBrightnessController;
     private QSTileHost mHost;
 
+    private QSFooter mFooter;
+
     public QSPanel(Context context) {
         this(context, null);
     }
@@ -83,8 +85,10 @@
         mDetail.setClickable(true);
         mBrightnessView = LayoutInflater.from(context).inflate(
                 R.layout.quick_settings_brightness_dialog, this, false);
+        mFooter = new QSFooter(this, context);
         addView(mDetail);
         addView(mBrightnessView);
+        addView(mFooter.getView());
         mClipper = new QSDetailClipper(mDetail);
         updateResources();
 
@@ -106,6 +110,7 @@
 
     public void setHost(QSTileHost host) {
         mHost = host;
+        mFooter.setHost(host);
     }
 
     public QSTileHost getHost() {
@@ -144,6 +149,7 @@
         for (TileRecord r : mRecords) {
             r.tile.setListening(mListening);
         }
+        mFooter.setListening(mListening);
         if (mListening) {
             refreshAllTiles();
         }
@@ -158,6 +164,7 @@
         for (TileRecord r : mRecords) {
             r.tile.refreshState();
         }
+        mFooter.refreshState();
     }
 
     public void showDetailAdapter(boolean show, DetailAdapter adapter) {
@@ -287,6 +294,7 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final int width = MeasureSpec.getSize(widthMeasureSpec);
         mBrightnessView.measure(exactly(width), MeasureSpec.UNSPECIFIED);
+        mFooter.getView().measure(exactly(width), MeasureSpec.UNSPECIFIED);
         int r = -1;
         int c = -1;
         int rows = 0;
@@ -315,6 +323,9 @@
             record.tileView.measure(exactly(cw), exactly(ch));
         }
         int h = rows == 0 ? mBrightnessView.getHeight() : (getRowTop(rows) + mPanelPaddingBottom);
+        if (mFooter.hasFooter()) {
+            h += mFooter.getView().getHeight();
+        }
         mDetail.measure(exactly(width), exactly(h));
         setMeasuredDimension(width, h);
     }
@@ -341,6 +352,11 @@
         }
         final int dh = Math.max(mDetail.getMeasuredHeight(), getMeasuredHeight());
         mDetail.layout(0, 0, mDetail.getMeasuredWidth(), dh);
+        if (mFooter.hasFooter()) {
+            View footer = mFooter.getView();
+            footer.layout(0, getMeasuredHeight() - footer.getMeasuredHeight(),
+                    footer.getMeasuredWidth(), getMeasuredHeight());
+        }
     }
 
     private int getRowTop(int row) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index a82c907..9107790 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -55,6 +55,8 @@
     private static final int MSG_SHOW_RECENT_APPS           = 14 << MSG_SHIFT;
     private static final int MSG_HIDE_RECENT_APPS           = 15 << MSG_SHIFT;
     private static final int MSG_BUZZ_BEEP_BLINKED          = 16 << MSG_SHIFT;
+    private static final int MSG_NOTIFICATION_LIGHT_OFF     = 17 << MSG_SHIFT;
+    private static final int MSG_NOTIFICATION_LIGHT_PULSE   = 18 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -95,6 +97,8 @@
         public void hideSearchPanel();
         public void setWindowState(int window, int state);
         public void buzzBeepBlinked();
+        public void notificationLightOff();
+        public void notificationLightPulse(int argb, int onMillis, int offMillis);
     }
 
     public CommandQueue(Callbacks callbacks, StatusBarIconList list) {
@@ -230,6 +234,19 @@
         }
     }
 
+    public void notificationLightOff() {
+        synchronized (mList) {
+            mHandler.sendEmptyMessage(MSG_NOTIFICATION_LIGHT_OFF);
+        }
+    }
+
+    public void notificationLightPulse(int argb, int onMillis, int offMillis) {
+        synchronized (mList) {
+            mHandler.obtainMessage(MSG_NOTIFICATION_LIGHT_PULSE, onMillis, offMillis, argb)
+                    .sendToTarget();
+        }
+    }
+
     private final class H extends Handler {
         public void handleMessage(Message msg) {
             final int what = msg.what & MSG_MASK;
@@ -306,7 +323,12 @@
                 case MSG_BUZZ_BEEP_BLINKED:
                     mCallbacks.buzzBeepBlinked();
                     break;
-
+                case MSG_NOTIFICATION_LIGHT_OFF:
+                    mCallbacks.notificationLightOff();
+                    break;
+                case MSG_NOTIFICATION_LIGHT_PULSE:
+                    mCallbacks.notificationLightPulse((Integer) msg.obj, msg.arg1, msg.arg2);
+                    break;
             }
         }
     }
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 fe9ea50..99b0259 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -22,7 +22,6 @@
 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.StatusBarManager.windowStateToString;
-import static com.android.keyguard.KeyguardHostView.OnDismissAction;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
@@ -106,6 +105,7 @@
 import android.widget.TextView;
 
 import com.android.internal.statusbar.StatusBarIcon;
+import com.android.keyguard.KeyguardHostView.OnDismissAction;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.DemoMode;
 import com.android.systemui.EventLogTags;
@@ -129,20 +129,21 @@
 import com.android.systemui.statusbar.SpeedBumpView;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.policy.NextAlarmController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
 import com.android.systemui.statusbar.policy.CastControllerImpl;
 import com.android.systemui.statusbar.policy.FlashlightController;
 import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
+import com.android.systemui.statusbar.policy.HotspotControllerImpl;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
-import com.android.systemui.statusbar.policy.HotspotControllerImpl;
-import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.LocationControllerImpl;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl;
+import com.android.systemui.statusbar.policy.NextAlarmController;
 import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
+import com.android.systemui.statusbar.policy.SecurityControllerImpl;
+import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -212,6 +213,7 @@
 
     // These are no longer handled by the policy, because we need custom strategies for them
     BluetoothControllerImpl mBluetoothController;
+    SecurityControllerImpl mSecurityController;
     BatteryController mBatteryController;
     LocationControllerImpl mLocationController;
     NetworkControllerImpl mNetworkController;
@@ -736,6 +738,7 @@
         mNetworkController = new NetworkControllerImpl(mContext);
         mHotspotController = new HotspotControllerImpl(mContext);
         mBluetoothController = new BluetoothControllerImpl(mContext);
+        mSecurityController = new SecurityControllerImpl(mContext);
         if (mContext.getResources().getBoolean(R.bool.config_showRotationLock)) {
             mRotationLockController = new RotationLockControllerImpl(mContext);
         }
@@ -797,7 +800,8 @@
                     mBluetoothController, mLocationController, mRotationLockController,
                     mNetworkController, mZenModeController, mHotspotController,
                     mCastController, mFlashlightController,
-                    mUserSwitcherController, mKeyguardMonitor);
+                    mUserSwitcherController, mKeyguardMonitor,
+                    mSecurityController);
             mQSPanel.setHost(qsh);
             for (QSTile<?> tile : qsh.getTiles()) {
                 mQSPanel.addTile(tile);
@@ -2332,6 +2336,20 @@
         }
     }
 
+    @Override
+    public void notificationLightOff() {
+        if (mDozeServiceHost != null) {
+            mDozeServiceHost.fireNotificationLight(false);
+        }
+    }
+
+    @Override
+    public void notificationLightPulse(int argb, int onMillis, int offMillis) {
+        if (mDozeServiceHost != null) {
+            mDozeServiceHost.fireNotificationLight(true);
+        }
+    }
+
     @Override // CommandQueue
     public void setSystemUiVisibility(int vis, int mask) {
         final int oldVal = mSystemUiVisibility;
@@ -2776,6 +2794,9 @@
         if (mNextAlarmController != null) {
             mNextAlarmController.dump(fd, pw, args);
         }
+        if (mSecurityController != null) {
+            mSecurityController.dump(fd, pw, args);
+        }
     }
 
     private String hunStateToString(Entry entry) {
@@ -3792,6 +3813,12 @@
             }
         }
 
+        public void fireNotificationLight(boolean on) {
+            for (Callback callback : mCallbacks) {
+                callback.onNotificationLight(on);
+            }
+        }
+
         public void fireNewNotifications() {
             for (Callback callback : mCallbacks) {
                 callback.onNewNotifications();
@@ -3816,10 +3843,10 @@
         }
 
         @Override
-        public void requestTease(DozeService dozeService) {
+        public void requestPulse(int pulses, DozeService dozeService) {
             if (dozeService == null) return;
             dozeService.stayAwake(PROCESSING_TIME);
-            mHandler.obtainMessage(H.REQUEST_TEASE, dozeService).sendToTarget();
+            mHandler.obtainMessage(H.REQUEST_PULSE, pulses, 0, dozeService).sendToTarget();
         }
 
         @Override
@@ -3838,9 +3865,9 @@
             mCurrentDozeService.startDozing();
         }
 
-        private void handleRequestTease(DozeService dozeService) {
+        private void handleRequestPulse(int pulses, DozeService dozeService) {
             if (!dozeService.equals(mCurrentDozeService)) return;
-            final long stayAwake = mScrimController.tease();
+            final long stayAwake = mScrimController.pulse(pulses);
             mCurrentDozeService.stayAwake(stayAwake);
         }
 
@@ -3856,15 +3883,15 @@
 
         private final class H extends Handler {
             private static final int REQUEST_DOZE = 1;
-            private static final int REQUEST_TEASE = 2;
+            private static final int REQUEST_PULSE = 2;
             private static final int DOZING_STOPPED = 3;
 
             @Override
             public void handleMessage(Message msg) {
                 if (msg.what == REQUEST_DOZE) {
                     handleRequestDoze((DozeService) msg.obj);
-                } else if (msg.what == REQUEST_TEASE) {
-                    handleRequestTease((DozeService) msg.obj);
+                } else if (msg.what == REQUEST_PULSE) {
+                    handleRequestPulse(msg.arg1, (DozeService) msg.obj);
                 } else if (msg.what == DOZING_STOPPED) {
                     handleDozingStopped((DozeService) msg.obj);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index c2fd24c..8f25fb97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -41,6 +41,7 @@
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.RotationLockController;
 import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 
@@ -65,13 +66,15 @@
     private final FlashlightController mFlashlight;
     private final UserSwitcherController mUserSwitcherController;
     private final KeyguardMonitor mKeyguard;
+    private final SecurityController mSecurity;
 
     public QSTileHost(Context context, PhoneStatusBar statusBar,
             BluetoothController bluetooth, LocationController location,
             RotationLockController rotation, NetworkController network,
             ZenModeController zen, HotspotController hotspot,
             CastController cast, FlashlightController flashlight,
-            UserSwitcherController userSwitcher, KeyguardMonitor keyguard) {
+            UserSwitcherController userSwitcher, KeyguardMonitor keyguard,
+            SecurityController security) {
         mContext = context;
         mStatusBar = statusBar;
         mBluetooth = bluetooth;
@@ -84,6 +87,7 @@
         mFlashlight = flashlight;
         mUserSwitcherController = userSwitcher;
         mKeyguard = keyguard;
+        mSecurity = security;
 
         final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName());
         ht.start();
@@ -189,4 +193,8 @@
     public UserSwitcherController getUserSwitcherController() {
         return mUserSwitcherController;
     }
+
+    public SecurityController getSecurityController() {
+        return mSecurity;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 7bce664..cbd66aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -43,14 +43,13 @@
     private static final long ANIMATION_DURATION = 220;
     private static final int TAG_KEY_ANIM = R.id.scrim;
 
-    private static final int NUM_TEASES = 3;
-    private static final long TEASE_IN_ANIMATION_DURATION = 1000;
-    private static final long TEASE_VISIBLE_DURATION = 2000;
-    private static final long TEASE_OUT_ANIMATION_DURATION = 1000;
-    private static final long TEASE_INVISIBLE_DURATION = 1000;
-    private static final long TEASE_DURATION = TEASE_IN_ANIMATION_DURATION
-            + TEASE_VISIBLE_DURATION + TEASE_OUT_ANIMATION_DURATION + TEASE_INVISIBLE_DURATION;
-    private static final long PRE_TEASE_DELAY = 1000;
+    private static final long PULSE_IN_ANIMATION_DURATION = 1000;
+    private static final long PULSE_VISIBLE_DURATION = 2000;
+    private static final long PULSE_OUT_ANIMATION_DURATION = 1000;
+    private static final long PULSE_INVISIBLE_DURATION = 1000;
+    private static final long PULSE_DURATION = PULSE_IN_ANIMATION_DURATION
+            + PULSE_VISIBLE_DURATION + PULSE_OUT_ANIMATION_DURATION + PULSE_INVISIBLE_DURATION;
+    private static final long PRE_PULSE_DELAY = 1000;
 
     private final View mScrimBehind;
     private final View mScrimInFront;
@@ -70,7 +69,7 @@
     private Runnable mOnAnimationFinished;
     private boolean mAnimationStarted;
     private boolean mDozing;
-    private int mTeasesRemaining;
+    private int mPulsesRemaining;
     private final Interpolator mInterpolator = new DecelerateInterpolator();
 
     public ScrimController(View scrimBehind, View scrimInFront) {
@@ -126,23 +125,23 @@
         if (mDozing == dozing) return;
         mDozing = dozing;
         if (!mDozing) {
-            cancelTeasing();
+            cancelPulsing();
         }
         scheduleUpdate();
     }
 
     /** When dozing, fade screen contents in and out a few times using the front scrim. */
-    public long tease() {
+    public long pulse(int pulses) {
         if (!mDozing) return 0;
-        mTeasesRemaining = NUM_TEASES;
-        mScrimInFront.postDelayed(mTeaseIn, PRE_TEASE_DELAY);
-        return PRE_TEASE_DELAY + NUM_TEASES * TEASE_DURATION;
+        mPulsesRemaining = Math.max(pulses, mPulsesRemaining);
+        mScrimInFront.postDelayed(mPulseIn, PRE_PULSE_DELAY);
+        return PRE_PULSE_DELAY + mPulsesRemaining * PULSE_DURATION;
     }
 
-    private void cancelTeasing() {
-        mTeasesRemaining = 0;
-        mScrimInFront.removeCallbacks(mTeaseIn);
-        mScrimInFront.removeCallbacks(mTeaseOut);
+    private void cancelPulsing() {
+        mPulsesRemaining = 0;
+        mScrimInFront.removeCallbacks(mPulseIn);
+        mScrimInFront.removeCallbacks(mPulseOut);
     }
 
     private void scheduleUpdate() {
@@ -285,49 +284,49 @@
         return true;
     }
 
-    private final Runnable mTeaseIn = new Runnable() {
+    private final Runnable mPulseIn = new Runnable() {
         @Override
         public void run() {
-            if (DEBUG) Log.d(TAG, "Tease in, mDozing=" + mDozing
-                    + " mTeasesRemaining=" + mTeasesRemaining);
-            if (!mDozing || mTeasesRemaining == 0) return;
-            mTeasesRemaining--;
-            mDurationOverride = TEASE_IN_ANIMATION_DURATION;
+            if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing
+                    + " mPulsesRemaining=" + mPulsesRemaining);
+            if (!mDozing || mPulsesRemaining == 0) return;
+            mPulsesRemaining--;
+            mDurationOverride = PULSE_IN_ANIMATION_DURATION;
             mAnimationDelay = 0;
             mAnimateChange = true;
-            mOnAnimationFinished = mTeaseInFinished;
+            mOnAnimationFinished = mPulseInFinished;
             setScrimColor(mScrimInFront, 0);
         }
     };
 
-    private final Runnable mTeaseInFinished = new Runnable() {
+    private final Runnable mPulseInFinished = new Runnable() {
         @Override
         public void run() {
-            if (DEBUG) Log.d(TAG, "Tease in finished, mDozing=" + mDozing);
+            if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
             if (!mDozing) return;
-            mScrimInFront.postDelayed(mTeaseOut, TEASE_VISIBLE_DURATION);
+            mScrimInFront.postDelayed(mPulseOut, PULSE_VISIBLE_DURATION);
         }
     };
 
-    private final Runnable mTeaseOut = new Runnable() {
+    private final Runnable mPulseOut = new Runnable() {
         @Override
         public void run() {
-            if (DEBUG) Log.d(TAG, "Tease in finished, mDozing=" + mDozing);
+            if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
             if (!mDozing) return;
-            mDurationOverride = TEASE_OUT_ANIMATION_DURATION;
+            mDurationOverride = PULSE_OUT_ANIMATION_DURATION;
             mAnimationDelay = 0;
             mAnimateChange = true;
-            mOnAnimationFinished = mTeaseOutFinished;
+            mOnAnimationFinished = mPulseOutFinished;
             setScrimColor(mScrimInFront, 1);
         }
     };
 
-    private final Runnable mTeaseOutFinished = new Runnable() {
+    private final Runnable mPulseOutFinished = new Runnable() {
         @Override
         public void run() {
-            if (DEBUG) Log.d(TAG, "Tease out finished, mTeasesRemaining=" + mTeasesRemaining);
-            if (mTeasesRemaining > 0) {
-                mScrimInFront.postDelayed(mTeaseIn, TEASE_INVISIBLE_DURATION);
+            if (DEBUG) Log.d(TAG, "Pulse out finished, mPulsesRemaining=" + mPulsesRemaining);
+            if (mPulsesRemaining > 0) {
+                mScrimInFront.postDelayed(mPulseIn, PULSE_INVISIBLE_DURATION);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
new file mode 100644
index 0000000..ede8129
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2014 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.policy;
+
+public interface SecurityController {
+
+    boolean hasDeviceOwner();
+    String getDeviceOwnerName();
+    boolean isVpnEnabled();
+    String getVpnApp();
+    boolean isLegacyVpn();
+    String getLegacyVpnName();
+    void openVpnApp();
+    void disconnectFromLegacyVpn();
+
+    void addCallback(VpnCallback callback);
+    void removeCallback(VpnCallback callback);
+
+    public interface VpnCallback {
+        void onVpnStateChanged();
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
new file mode 100644
index 0000000..8e04e5e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2014 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.policy;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.IConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.net.VpnConfig;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public class SecurityControllerImpl implements SecurityController {
+
+    private static final String TAG = "SecurityController";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final NetworkRequest REQUEST = new NetworkRequest.Builder()
+            .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+            .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+            .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+            .build();
+    private final Context mContext;
+    private final ConnectivityManager mConnectivityManager;
+    private final IConnectivityManager mConnectivityService = IConnectivityManager.Stub.asInterface(
+                ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+    private final DevicePolicyManager mDevicePolicyManager;
+    private final ArrayList<VpnCallback> mCallbacks = new ArrayList<VpnCallback>();
+
+    private boolean mIsVpnEnabled;
+    private VpnConfig mVpnConfig;
+    private String mVpnName;
+
+    public SecurityControllerImpl(Context context) {
+        mContext = context;
+        mDevicePolicyManager = (DevicePolicyManager)
+                context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        mConnectivityManager = (ConnectivityManager)
+                context.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        // TODO: re-register network callback on user change.
+        mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("SecurityController state:");
+        pw.print("  mIsVpnEnabled="); pw.println(mIsVpnEnabled);
+        pw.print("  mVpnConfig="); pw.println(mVpnConfig);
+        pw.print("  mVpnName="); pw.println(mVpnName);
+    }
+
+    @Override
+    public boolean hasDeviceOwner() {
+        return !TextUtils.isEmpty(mDevicePolicyManager.getDeviceOwner());
+    }
+
+    @Override
+    public String getDeviceOwnerName() {
+        return mDevicePolicyManager.getDeviceOwnerName();
+    }
+
+    @Override
+    public boolean isVpnEnabled() {
+        // TODO: Remove once using NetworkCallback for updates.
+        updateState();
+
+        return mIsVpnEnabled;
+    }
+
+    @Override
+    public boolean isLegacyVpn() {
+        return mVpnConfig.legacy;
+    }
+
+    @Override
+    public String getVpnApp() {
+        return mVpnName;
+    }
+
+    @Override
+    public String getLegacyVpnName() {
+        return mVpnConfig.session;
+    }
+
+    @Override
+    public void openVpnApp() {
+        Intent i = mContext.getPackageManager().getLaunchIntentForPackage(mVpnConfig.user);
+        if (i != null) {
+            i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mContext.startActivity(i);
+        }
+    }
+
+    @Override
+    public void disconnectFromLegacyVpn() {
+        try {
+            mConnectivityService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
+        } catch (Exception e) {
+            Log.e(TAG, "Unable to disconnect from VPN", e);
+        }
+    }
+
+    @Override
+    public void addCallback(VpnCallback callback) {
+        if (callback == null) return;
+        if (DEBUG) Log.d(TAG, "removeCallback " + callback);
+        mCallbacks.remove(callback);
+    }
+
+    @Override
+    public void removeCallback(VpnCallback callback) {
+        if (callback == null || mCallbacks.contains(callback)) return;
+        if (DEBUG) Log.d(TAG, "addCallback " + callback);
+        mCallbacks.add(callback);
+    }
+
+    private void fireCallbacks() {
+        for (VpnCallback callback : mCallbacks) {
+            callback.onVpnStateChanged();
+        }
+    }
+
+    private void updateState() {
+        try {
+            mVpnConfig = mConnectivityService.getVpnConfig();
+
+            // TODO: Remove once using NetworkCallback for updates.
+            mIsVpnEnabled = mVpnConfig != null;
+
+            if (mVpnConfig != null && !mVpnConfig.legacy) {
+                ApplicationInfo info =
+                        mContext.getPackageManager().getApplicationInfo(mVpnConfig.user, 0);
+                mVpnName = mContext.getPackageManager().getApplicationLabel(info).toString();
+            }
+        } catch (RemoteException | NameNotFoundException e) {
+            Log.w(TAG, "Unable to get current VPN", e);
+        }
+    }
+
+    private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+        public void onCapabilitiesChanged(android.net.Network network,
+                android.net.NetworkCapabilities networkCapabilities) {
+            if (DEBUG) Log.d(TAG, "onCapabilitiesChanged " + networkCapabilities);
+            mIsVpnEnabled = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
+            updateState();
+            fireCallbacks();
+        }
+    };
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index a123bf7..049add5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -99,6 +99,14 @@
     public void buzzBeepBlinked() {
     }
 
+    @Override // CommandQueue
+    public void notificationLightOff() {
+    }
+
+    @Override // CommandQueue
+    public void notificationLightPulse(int argb, int onMillis, int offMillis) {
+    }
+
     @Override
     protected WindowManager.LayoutParams getSearchLayoutParams(
             LayoutParams layoutParams) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 53006f3..822bd5a 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -709,6 +709,7 @@
             } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
                 // turn off LED when user passes through lock screen
                 mNotificationLight.turnOff();
+                mStatusBar.notificationLightOff();
             } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
                 // reload per-user settings
                 mSettingsObserver.update(null);
@@ -1442,7 +1443,8 @@
                         }
                         pw.println("  ");
                     }
-
+                    pw.println("  mUseAttentionLight=" + mUseAttentionLight);
+                    pw.println("  mNotificationPulseEnabled=" + mNotificationPulseEnabled);
                     pw.println("  mSoundNotification=" + mSoundNotification);
                     pw.println("  mVibrateNotification=" + mVibrateNotification);
                     pw.println("  mDisableNotificationAlerts=" + mDisableNotificationAlerts);
@@ -2376,6 +2378,7 @@
         // Don't flash while we are in a call or screen is on
         if (mLedNotification == null || mInCall || mScreenOn) {
             mNotificationLight.turnOff();
+            mStatusBar.notificationLightOff();
         } else {
             final Notification ledno = mLedNotification.sbn.getNotification();
             int ledARGB = ledno.ledARGB;
@@ -2390,6 +2393,7 @@
                 // pulse repeatedly
                 mNotificationLight.setFlashing(ledARGB, Light.LIGHT_FLASH_TIMED,
                         ledOnMS, ledOffMS);
+                mStatusBar.notificationLightPulse(ledARGB, ledOnMS, ledOffMS);
             }
         }
     }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 463f763..c28e0bc 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -21,4 +21,6 @@
 public interface StatusBarManagerInternal {
     void setNotificationDelegate(NotificationDelegate delegate);
     void buzzBeepBlinked();
+    void notificationLightPulse(int argb, int onMillis, int offMillis);
+    void notificationLightOff();
 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index f33943d..263767d 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -107,10 +107,13 @@
      * Private API used by NotificationManagerService.
      */
     private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() {
+        private boolean mNotificationLightOn;
+
         @Override
         public void setNotificationDelegate(NotificationDelegate delegate) {
             mNotificationDelegate = delegate;
         }
+
         @Override
         public void buzzBeepBlinked() {
             if (mBar != null) {
@@ -120,6 +123,30 @@
                 }
             }
         }
+
+        @Override
+        public void notificationLightPulse(int argb, int onMillis, int offMillis) {
+            mNotificationLightOn = true;
+            if (mBar != null) {
+                try {
+                    mBar.notificationLightPulse(argb, onMillis, offMillis);
+                } catch (RemoteException ex) {
+                }
+            }
+        }
+
+        @Override
+        public void notificationLightOff() {
+            if (mNotificationLightOn) {
+                mNotificationLightOn = false;
+                if (mBar != null) {
+                    try {
+                        mBar.notificationLightOff();
+                    } catch (RemoteException ex) {
+                    }
+                }
+            }
+        }
     };
 
     // ================================================================================