Merge "Isolate tiling clip state from snapshot" into jb-mr2-dev
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 14bcc0d..3d9b2ae 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -41,7 +41,7 @@
     StatusBarNotification[] getActiveNotifications(String callingPkg);
     StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count);
 
-    void registerListener(in INotificationListener listener, int userid);
+    void registerListener(in INotificationListener listener, String pkg, int userid);
     void unregisterListener(in INotificationListener listener, int userid);
 }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d251ca2..19283be 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4039,6 +4039,14 @@
         public static final String SCREENSAVER_DEFAULT_COMPONENT = "screensaver_default_component";
 
         /**
+         * Name of a package that the current user has explicitly allowed to see all of that
+         * user's notifications.
+         *
+         * @hide
+         */
+        public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
+
+        /**
          * This are the settings to be backed up.
          *
          * NOTE: Settings are backed up and restored in the order they appear
@@ -4791,6 +4799,14 @@
                 "wifi_scan_always_enabled";
 
        /**
+        * Setting to indicate whether the user should be notified that scans are still
+        * available when Wi-Fi is turned off
+        * @hide
+        */
+       public static final String WIFI_NOTIFY_SCAN_ALWAYS_AVAILABLE =
+                "wifi_notify_scan_always_enabled";
+
+       /**
         * Used to save the Wifi_ON state prior to tethering.
         * This state will be checked to restore Wifi after
         * the user turns off tethering.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e497f1b..532c3ef 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2989,6 +2989,15 @@
     <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the message of that notification. -->
     <string name="low_internal_storage_view_text">Some system functions may not work</string>
 
+    <!-- [CHAR LIMIT=NONE] Stub notification title for an app running a service that has provided
+         a bad bad notification for itself. -->
+    <string name="app_running_notification_title"><xliff:g id="app_name">%1$s</xliff:g>
+        running</string>
+    <!-- [CHAR LIMIT=NONE] Stub notification text for an app running a service that has provided
+         a bad bad notification for itself. -->
+    <string name="app_running_notification_text"><xliff:g id="app_name">%1$s</xliff:g>
+        is currently running</string>
+
     <!-- Preference framework strings. -->
     <string name="ok">OK</string>
     <!-- Preference framework strings. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9ec33bb..80e77dd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -330,6 +330,8 @@
   <java-symbol type="string" name="addToDictionary" />
   <java-symbol type="string" name="action_bar_home_description" />
   <java-symbol type="string" name="action_bar_up_description" />
+  <java-symbol type="string" name="app_running_notification_title" />
+  <java-symbol type="string" name="app_running_notification_text" />
   <java-symbol type="string" name="delete" />
   <java-symbol type="string" name="deleteText" />
   <java-symbol type="string" name="ellipsis_two_dots" />
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index dc3a4e2..57d1a4f 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -310,6 +310,7 @@
             fontRenderer->flush();
             textureCache.flush();
             pathCache.clear();
+            tasks.stop();
             // fall through
         case kFlushMode_Layers:
             layerCache.clear();
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 490c22a0..07c4207 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -347,14 +347,15 @@
 // Paths
 ///////////////////////////////////////////////////////////////////////////////
 
-void PathCache::remove(SkPath* path) {
+void PathCache::remove(const path_pair_t& pair) {
     Vector<PathDescription> pathsToRemove;
     LruCache<PathDescription, PathTexture*>::Iterator i(mCache);
 
     while (i.next()) {
         const PathDescription& key = i.key();
         if (key.type == kShapePath &&
-                (key.shape.path.mPath == path || key.shape.path.mPath == path->getSourcePath())) {
+                (key.shape.path.mPath == pair.getFirst() ||
+                        key.shape.path.mPath == pair.getSecond())) {
             pathsToRemove.push(key);
         }
     }
@@ -366,7 +367,7 @@
 
 void PathCache::removeDeferred(SkPath* path) {
     Mutex::Autolock l(mLock);
-    mGarbage.push(path);
+    mGarbage.push(path_pair_t(path, const_cast<SkPath*>(path->getSourcePath())));
 }
 
 void PathCache::clearGarbage() {
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index e6d92df..1467231 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -26,6 +26,7 @@
 #include "Debug.h"
 #include "Properties.h"
 #include "Texture.h"
+#include "utils/Pair.h"
 
 class SkBitmap;
 class SkCanvas;
@@ -215,10 +216,6 @@
     PathTexture* get(SkPath* path, SkPaint* paint);
 
     /**
-     * Removes an entry.
-     */
-    void remove(SkPath* path);
-    /**
      * Removes the specified path. This is meant to be called from threads
      * that are not the EGL context thread.
      */
@@ -251,6 +248,8 @@
             float& left, float& top, float& offset, uint32_t& width, uint32_t& height);
 
 private:
+    typedef Pair<SkPath*, SkPath*> path_pair_t;
+
     PathTexture* addTexture(const PathDescription& entry,
             const SkPath *path, const SkPaint* paint);
     PathTexture* addTexture(const PathDescription& entry, SkBitmap* bitmap);
@@ -261,6 +260,12 @@
     }
 
     /**
+     * Removes an entry.
+     * The pair must define first=path, second=sourcePath
+     */
+    void remove(const path_pair_t& pair);
+
+    /**
      * Ensures there is enough space in the cache for a texture of the specified
      * dimensions.
      */
@@ -318,7 +323,8 @@
     bool mDebugEnabled;
 
     sp<PathProcessor> mProcessor;
-    Vector<SkPath*> mGarbage;
+
+    Vector<path_pair_t> mGarbage;
     mutable Mutex mLock;
 }; // class PathCache
 
diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp
index ce6c8c0..c8bfd9c 100644
--- a/libs/hwui/thread/TaskManager.cpp
+++ b/libs/hwui/thread/TaskManager.cpp
@@ -48,6 +48,12 @@
     return mThreads.size() > 0;
 }
 
+void TaskManager::stop() {
+    for (size_t i = 0; i < mThreads.size(); i++) {
+        mThreads[i]->exit();
+    }
+}
+
 bool TaskManager::addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor) {
     if (mThreads.size() > 0) {
         TaskWrapper wrapper(task, processor);
diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h
index bc86062..477314b 100644
--- a/libs/hwui/thread/TaskManager.h
+++ b/libs/hwui/thread/TaskManager.h
@@ -47,6 +47,12 @@
      */
     bool canRunTasks() const;
 
+    /**
+     * Stops all allocated threads. Adding tasks will start
+     * the threads again as necessary.
+     */
+    void stop();
+
 private:
     template <typename T>
     friend class TaskProcessor;
diff --git a/libs/hwui/utils/Pair.h b/libs/hwui/utils/Pair.h
new file mode 100644
index 0000000..172606a
--- /dev/null
+++ b/libs/hwui/utils/Pair.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#ifndef ANDROID_HWUI_PAIR_H
+#define ANDROID_HWUI_PAIR_H
+
+namespace android {
+namespace uirenderer {
+
+template <typename F, typename S>
+struct Pair {
+    F first;
+    S second;
+
+    Pair() { }
+    Pair(const Pair& o) : first(o.first), second(o.second) { }
+    Pair(const F& f, const S& s) : first(f), second(s)  { }
+
+    inline const F& getFirst() const {
+        return first;
+    }
+
+    inline const S& getSecond() const {
+        return second;
+    }
+};
+
+}; // namespace uirenderer
+
+template <typename F, typename S>
+struct trait_trivial_ctor< uirenderer::Pair<F, S> >
+{ enum { value = aggregate_traits<F, S>::has_trivial_ctor }; };
+template <typename F, typename S>
+struct trait_trivial_dtor< uirenderer::Pair<F, S> >
+{ enum { value = aggregate_traits<F, S>::has_trivial_dtor }; };
+template <typename F, typename S>
+struct trait_trivial_copy< uirenderer::Pair<F, S> >
+{ enum { value = aggregate_traits<F, S>::has_trivial_copy }; };
+template <typename F, typename S>
+struct trait_trivial_move< uirenderer::Pair<F, S> >
+{ enum { value = aggregate_traits<F, S>::has_trivial_move }; };
+
+}; // namespace android
+
+#endif // ANDROID_HWUI_PAIR_H
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 9e036d1..5cf1c28 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -50,12 +50,11 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.Vibrator;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
@@ -124,6 +123,7 @@
 
     final Context mContext;
     final IActivityManager mAm;
+    final UserManager mUserManager;
     final IBinder mForegroundToken = new Binder();
 
     private WorkerHandler mHandler;
@@ -164,6 +164,7 @@
     private final AppOpsManager mAppOps;
 
     private ArrayList<NotificationListenerInfo> mListeners = new ArrayList<NotificationListenerInfo>();
+    private ArrayList<String> mEnabledListenersForCurrentUser = new ArrayList<String>();
 
     // Notification control database. For now just contains disabled packages.
     private AtomicFile mPolicyFile;
@@ -180,20 +181,27 @@
 
     private class NotificationListenerInfo implements DeathRecipient {
         INotificationListener listener;
+        String pkg;
         int userid;
-        public NotificationListenerInfo(INotificationListener listener, int userid) {
+        boolean isSystem;
+
+        public NotificationListenerInfo(INotificationListener listener, String pkg, int userid,
+                boolean isSystem) {
             this.listener = listener;
+            this.pkg = pkg;
             this.userid = userid;
+            this.isSystem = isSystem;
         }
 
-        boolean userMatches(StatusBarNotification sbn) {
+        boolean enabledAndUserMatches(StatusBarNotification sbn) {
+            final int nid = sbn.getUserId();
+            if (!(isSystem || isEnabledForUser(nid))) return false;
             if (this.userid == UserHandle.USER_ALL) return true;
-            int nid = sbn.getUserId();
             return (nid == UserHandle.USER_ALL || nid == this.userid);
         }
 
         public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
-            if (!userMatches(sbn)) return;
+            if (!enabledAndUserMatches(sbn)) return;
             try {
                 listener.onNotificationPosted(sbn);
             } catch (RemoteException ex) {
@@ -202,7 +210,7 @@
         }
 
         public void notifyRemovedIfUserMatch(StatusBarNotification sbn) {
-            if (!userMatches(sbn)) return;
+            if (!enabledAndUserMatches(sbn)) return;
             try {
                 listener.onNotificationRemoved(sbn);
             } catch (RemoteException ex) {
@@ -214,6 +222,14 @@
         public void binderDied() {
             unregisterListener(this.listener, this.userid);
         }
+
+        /** convenience method for looking in mEnabledListenersForCurrentUser */
+        public boolean isEnabledForUser(int userid) {
+            for (int i=0; i<mEnabledListenersForCurrentUser.size(); i++) {
+                if (this.pkg.equals(mEnabledListenersForCurrentUser.get(i))) return true;
+            }
+            return false;
+        }
     }
 
     private static class Archive {
@@ -413,12 +429,14 @@
     }
 
     public StatusBarNotification[] getActiveNotifications(String callingPkg) {
+        // enforce() will ensure the calling uid has the correct permission
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS,
                 "NotificationManagerService.getActiveNotifications");
 
         StatusBarNotification[] tmp = null;
         int uid = Binder.getCallingUid();
 
+        // noteOp will check to make sure the callingPkg matches the uid
         if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
                 == AppOpsManager.MODE_ALLOWED) {
             synchronized (mNotificationList) {
@@ -433,12 +451,14 @@
     }
 
     public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count) {
+        // enforce() will ensure the calling uid has the correct permission
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS,
                 "NotificationManagerService.getHistoricalNotifications");
 
         StatusBarNotification[] tmp = null;
         int uid = Binder.getCallingUid();
 
+        // noteOp will check to make sure the callingPkg matches the uid
         if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
                 == AppOpsManager.MODE_ALLOWED) {
             synchronized (mArchive) {
@@ -448,12 +468,27 @@
         return tmp;
     }
 
+    boolean packageCanTapNotificationsForUser(final int uid, final String pkg) {
+        // Make sure the package and uid match, and that the package is allowed access
+        return (AppOpsManager.MODE_ALLOWED
+            == mAppOps.checkOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, pkg));
+    }
+
     @Override
-    public void registerListener(final INotificationListener listener, final int userid) {
-        checkCallerIsSystem();
+    public void registerListener(final INotificationListener listener,
+            final String pkg, final int userid) {
+        // ensure system or allowed pkg
+        int uid = Binder.getCallingUid();
+        boolean isSystem = (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0);
+        if (!(isSystem || packageCanTapNotificationsForUser(uid, pkg))) {
+            throw new SecurityException("Package " + pkg
+                    + " may not listen for notifications");
+        }
+
         synchronized (mNotificationList) {
             try {
-                NotificationListenerInfo info = new NotificationListenerInfo(listener, userid);
+                NotificationListenerInfo info
+                        = new NotificationListenerInfo(listener, pkg, userid, isSystem);
                 listener.asBinder().linkToDeath(info, 0);
                 mListeners.add(info);
             } catch (RemoteException e) {
@@ -464,7 +499,9 @@
 
     @Override
     public void unregisterListener(INotificationListener listener, int userid) {
-        checkCallerIsSystem();
+        // no need to check permissions; if your listener binder is in the list,
+        // that's proof that you had permission to add it in the first place
+
         synchronized (mNotificationList) {
             final int N = mListeners.size();
             for (int i=N-1; i>=0; i--) {
@@ -740,37 +777,67 @@
             } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
                 // turn off LED when user passes through lock screen
                 mNotificationLight.turnOff();
+            } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
+                // reload per-user settings
+                mSettingsObserver.update(null);
             }
         }
     };
 
     class SettingsObserver extends ContentObserver {
+        private final Uri NOTIFICATION_LIGHT_PULSE_URI
+                = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
+
+        private final Uri ENABLED_NOTIFICATION_LISTENERS_URI
+                = Settings.System.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+
         SettingsObserver(Handler handler) {
             super(handler);
         }
 
         void observe() {
             ContentResolver resolver = mContext.getContentResolver();
-            resolver.registerContentObserver(Settings.System.getUriFor(
-                    Settings.System.NOTIFICATION_LIGHT_PULSE), false, this);
-            update();
+            resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
+                    false, this);
+            resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI,
+                    false, this);
+            update(null);
         }
 
-        @Override public void onChange(boolean selfChange) {
-            update();
+        @Override public void onChange(boolean selfChange, Uri uri) {
+            update(uri);
         }
 
-        public void update() {
+        public void update(Uri uri) {
             ContentResolver resolver = mContext.getContentResolver();
-            boolean pulseEnabled = Settings.System.getInt(resolver,
-                        Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0;
-            if (mNotificationPulseEnabled != pulseEnabled) {
-                mNotificationPulseEnabled = pulseEnabled;
-                updateNotificationPulse();
+            if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
+                boolean pulseEnabled = Settings.System.getInt(resolver,
+                            Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0;
+                if (mNotificationPulseEnabled != pulseEnabled) {
+                    mNotificationPulseEnabled = pulseEnabled;
+                    updateNotificationPulse();
+                }
+            }
+            if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) {
+                String pkglist = Settings.Secure.getString(
+                        mContext.getContentResolver(),
+                        Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+                mEnabledListenersForCurrentUser.clear();
+                if (pkglist != null) {
+                    String[] pkgs = pkglist.split(";");
+                    for (int i=0; i<pkgs.length; i++) {
+                        final String pkg = pkgs[i];
+                        if (pkg != null && ! "".equals(pkg)) {
+                            mEnabledListenersForCurrentUser.add(pkgs[i]);
+                        }
+                    }
+                }
             }
         }
     }
 
+    private SettingsObserver mSettingsObserver;
+
     static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) {
         int[] ar = r.getIntArray(resid);
         if (ar == null) {
@@ -791,6 +858,7 @@
         mContext = context;
         mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
         mAm = ActivityManagerNative.getDefault();
+        mUserManager = (UserManager)context.getSystemService(Context.USER_SERVICE);
         mToastQueue = new ArrayList<ToastRecord>();
         mHandler = new WorkerHandler();
 
@@ -838,6 +906,7 @@
         filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
         filter.addAction(Intent.ACTION_USER_PRESENT);
         filter.addAction(Intent.ACTION_USER_STOPPED);
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
         mContext.registerReceiver(mIntentReceiver, filter);
         IntentFilter pkgFilter = new IntentFilter();
         pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -849,8 +918,8 @@
         IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
         mContext.registerReceiver(mIntentReceiver, sdFilter);
 
-        SettingsObserver observer = new SettingsObserver(mHandler);
-        observer.observe();
+        mSettingsObserver = new SettingsObserver(mHandler);
+        mSettingsObserver.observe();
     }
 
     /**
@@ -1706,6 +1775,18 @@
 
         pw.println("Current Notification Manager state:");
 
+        pw.print("  Enabled listeners: [");
+        for (String pkg : mEnabledListenersForCurrentUser) {
+            pw.print(" " + pkg);
+        }
+        pw.println(" ]");
+
+        pw.println("  Live listeners:");
+        for (NotificationListenerInfo info : mListeners) {
+            pw.println("    " + info.pkg + " (user " + info.userid + "): " + info.listener
+                + (info.isSystem?" SYSTEM":""));
+        }
+
         int N;
 
         synchronized (mToastQueue) {
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index 1ac6bdf..8ff1c7d 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -16,6 +16,9 @@
 
 package com.android.server.am;
 
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.provider.Settings;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.server.NotificationManagerService;
 
@@ -369,6 +372,44 @@
                     }
                     try {
                         if (foregroundNoti.icon == 0) {
+                            // It is not correct for the caller to supply a notification
+                            // icon, but this used to be able to slip through, so for
+                            // those dirty apps give it the app's icon.
+                            foregroundNoti.icon = appInfo.icon;
+                            if (foregroundNoti.contentView == null) {
+                                // In this case the app may not have specified a
+                                // content view...  so we'll give them something to show.
+                                CharSequence appName = appInfo.loadLabel(
+                                        ams.mContext.getPackageManager());
+                                if (appName == null) {
+                                    appName = appInfo.packageName;
+                                }
+                                Context ctx = null;
+                                try {
+                                    ctx = ams.mContext.createPackageContext(
+                                            appInfo.packageName, 0);
+                                    Intent runningIntent = new Intent(
+                                            Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+                                    runningIntent.setData(Uri.fromParts("package",
+                                            appInfo.packageName, null));
+                                    PendingIntent pi = PendingIntent.getActivity(ams.mContext, 0,
+                                            runningIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+                                    foregroundNoti.setLatestEventInfo(ctx,
+                                            ams.mContext.getString(
+                                                    com.android.internal.R.string
+                                                            .app_running_notification_title,
+                                                    appName),
+                                            ams.mContext.getString(
+                                                    com.android.internal.R.string
+                                                            .app_running_notification_text,
+                                                    appName),
+                                            pi);
+                                } catch (PackageManager.NameNotFoundException e) {
+                                    foregroundNoti.icon = 0;
+                                }
+                            }
+                        }
+                        if (foregroundNoti.icon == 0) {
                             // Notifications whose icon is 0 are defined to not show
                             // a notification, silently ignoring it.  We don't want to
                             // just ignore it, we want to prevent the service from
diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java
index c46f217..9dde58f7 100644
--- a/services/java/com/android/server/wifi/WifiService.java
+++ b/services/java/com/android/server/wifi/WifiService.java
@@ -396,6 +396,18 @@
 
         long ident = Binder.clearCallingIdentity();
         try {
+
+            /* Turning off Wi-Fi when scans are still available */
+            if (!enable && isScanningAlwaysAvailable()) {
+                /* This can be changed by user in the app to not be notified again */
+                boolean notifyUser = (Settings.Global.getInt(mContext.getContentResolver(),
+                        Settings.Global.WIFI_NOTIFY_SCAN_ALWAYS_AVAILABLE, 1) == 1);
+                if (notifyUser) {
+                    Intent intent = new Intent(WifiManager.ACTION_NOTIFY_SCAN_ALWAYS_AVAILABLE);
+                    mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
+                }
+            }
+
             if (! mSettingsStore.handleWifiToggled(enable)) {
                 // Nothing to do if wifi cannot be toggled
                 return true;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index f654310..0c0a144 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -408,6 +408,15 @@
             "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
 
     /**
+     * Activity Action: Show a system activity that notifies the user that
+     * scanning is still available when Wi-Fi is turned off
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_NOTIFY_SCAN_ALWAYS_AVAILABLE =
+            "android.net.wifi.action.NOTIFY_SCAN_ALWAYS_AVAILABLE";
+
+    /**
      * Activity Action: Pick a Wi-Fi network to connect to.
      * <p>Input: Nothing.
      * <p>Output: Nothing.