diff options
23 files changed, 349 insertions, 66 deletions
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 14bcc0d7fba4..3d9b2ae83518 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -41,7 +41,7 @@ interface INotificationManager 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 d251ca25e068..19283be3897f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4039,6 +4039,14 @@ public final class Settings { 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 @@ public final class Settings { "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/java/android/security/IKeystoreService.java b/core/java/android/security/IKeystoreService.java index 2ae3c6415d20..a890d9bdcd84 100644 --- a/core/java/android/security/IKeystoreService.java +++ b/core/java/android/security/IKeystoreService.java @@ -407,15 +407,18 @@ public interface IKeystoreService extends IInterface { } @Override - public int migrate(String name, int targetUid) throws RemoteException { + public int duplicate(String srcKey, int srcUid, String destKey, int destUid) + throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(name); - _data.writeInt(targetUid); - mRemote.transact(Stub.TRANSACTION_migrate, _data, _reply, 0); + _data.writeString(srcKey); + _data.writeInt(srcUid); + _data.writeString(destKey); + _data.writeInt(destUid); + mRemote.transact(Stub.TRANSACTION_duplicate, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); } finally { @@ -448,7 +451,7 @@ public interface IKeystoreService extends IInterface { static final int TRANSACTION_grant = IBinder.FIRST_CALL_TRANSACTION + 17; static final int TRANSACTION_ungrant = IBinder.FIRST_CALL_TRANSACTION + 18; static final int TRANSACTION_getmtime = IBinder.FIRST_CALL_TRANSACTION + 19; - static final int TRANSACTION_migrate = IBinder.FIRST_CALL_TRANSACTION + 20; + static final int TRANSACTION_duplicate = IBinder.FIRST_CALL_TRANSACTION + 20; /** * Cast an IBinder object into an IKeystoreService interface, generating @@ -534,5 +537,6 @@ public interface IKeystoreService extends IInterface { public long getmtime(String name) throws RemoteException; - public int migrate(String name, int targetUid) throws RemoteException; + public int duplicate(String srcKey, int srcUid, String destKey, int destUid) + throws RemoteException; } diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index ef0ae03a371f..9f810af3ecc8 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2033,14 +2033,8 @@ =============================================================== --> <eat-comment /> - <public type="attr" name="mipMap" id="0x010103cd" /> - <public type="attr" name="mirrorForRtl" id="0x010103ce" /> - - <!-- =============================================================== - Resources added in version 19 of the platform - =============================================================== --> - <eat-comment /> - + <public type="attr" name="mipMap" /> + <public type="attr" name="mirrorForRtl" /> <public type="attr" name="windowOverscan" /> <public type="attr" name="requiredForAllUsers" /> <public type="style" name="Theme.NoTitleBar.Overscan" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index e497f1baa823..a2d45708e919 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. --> @@ -4040,7 +4049,7 @@ <!-- Message shown in dialog when user is attempting to set the music volume above the recommended maximum level for headphones --> <string name="safe_media_volume_warning" product="default"> - "Raise volume above safe level?\nListening at high volume for long periods may damage your hearing." + "Raise volume above recommended level?\nListening at high volume for long periods may damage your hearing." </string> <!-- Text spoken when the user is performing a gesture that will enable accessibility. [CHAR LIMIT=none] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 9ec33bb4bb46..80e77dd6e187 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/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 4dc0beb6cf9e..12c0ed8b217f 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -287,9 +287,9 @@ public class KeyStore { } } - public boolean migrate(String key, int uid) { + public boolean duplicate(String srcKey, int srcUid, String destKey, int destUid) { try { - return mBinder.migrate(key, uid) == NO_ERROR; + return mBinder.duplicate(srcKey, srcUid, destKey, destUid) == NO_ERROR; } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); return false; diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java index 8f8ee9215764..1de1eaf5bd50 100644 --- a/keystore/tests/src/android/security/KeyStoreTest.java +++ b/keystore/tests/src/android/security/KeyStoreTest.java @@ -553,7 +553,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { mKeyStore.ungrant(TEST_KEYNAME, 0)); } - public void testMigrate_grantedUid_Wifi_Success() throws Exception { + public void testDuplicate_grantedUid_Wifi_Success() throws Exception { assertTrue(mKeyStore.password(TEST_PASSWD)); assertFalse(mKeyStore.contains(TEST_KEYNAME)); @@ -563,13 +563,35 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { assertTrue(mKeyStore.contains(TEST_KEYNAME)); assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID)); - assertTrue(mKeyStore.migrate(TEST_KEYNAME, Process.WIFI_UID)); + // source doesn't exist + assertFalse(mKeyStore.duplicate(TEST_KEYNAME1, -1, TEST_KEYNAME1, Process.WIFI_UID)); + assertFalse(mKeyStore.contains(TEST_KEYNAME1, Process.WIFI_UID)); - assertFalse(mKeyStore.contains(TEST_KEYNAME)); - assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID)); + // Copy from current UID to granted UID + assertTrue(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME1, Process.WIFI_UID)); + assertTrue(mKeyStore.contains(TEST_KEYNAME)); + assertFalse(mKeyStore.contains(TEST_KEYNAME1)); + assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID)); + assertTrue(mKeyStore.contains(TEST_KEYNAME1, Process.WIFI_UID)); + assertFalse(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME1, Process.WIFI_UID)); + + // Copy from granted UID to same granted UID + assertTrue(mKeyStore.duplicate(TEST_KEYNAME1, Process.WIFI_UID, TEST_KEYNAME2, + Process.WIFI_UID)); + assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID)); + assertTrue(mKeyStore.contains(TEST_KEYNAME1, Process.WIFI_UID)); + assertTrue(mKeyStore.contains(TEST_KEYNAME2, Process.WIFI_UID)); + assertFalse(mKeyStore.duplicate(TEST_KEYNAME1, Process.WIFI_UID, TEST_KEYNAME2, + Process.WIFI_UID)); + + assertTrue(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME2, -1)); + assertTrue(mKeyStore.contains(TEST_KEYNAME)); + assertFalse(mKeyStore.contains(TEST_KEYNAME1)); + assertTrue(mKeyStore.contains(TEST_KEYNAME2)); + assertFalse(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME2, -1)); } - public void testMigrate_ungrantedUid_Bluetooth_Failure() throws Exception { + public void testDuplicate_ungrantedUid_Bluetooth_Failure() throws Exception { assertTrue(mKeyStore.password(TEST_PASSWD)); assertFalse(mKeyStore.contains(TEST_KEYNAME)); @@ -579,7 +601,9 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { assertTrue(mKeyStore.contains(TEST_KEYNAME)); assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID)); - assertFalse(mKeyStore.migrate(TEST_KEYNAME, Process.BLUETOOTH_UID)); + assertFalse(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME2, Process.BLUETOOTH_UID)); + assertFalse(mKeyStore.duplicate(TEST_KEYNAME, Process.BLUETOOTH_UID, TEST_KEYNAME2, + Process.BLUETOOTH_UID)); assertTrue(mKeyStore.contains(TEST_KEYNAME)); assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID)); diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index dc3a4e2dc8cb..57d1a4f27a55 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -310,6 +310,7 @@ void Caches::flush(FlushMode mode) { fontRenderer->flush(); textureCache.flush(); pathCache.clear(); + tasks.stop(); // fall through case kFlushMode_Layers: layerCache.clear(); diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index 55ddd17f5cb6..d985ad00e849 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -351,9 +351,9 @@ void DisplayList::outputViewProperties(const int level) { level * 2, "", mTransformMatrix, MATRIX_ARGS(mTransformMatrix)); } } - if (mAlpha < 1 && !mCaching) { - if (!mHasOverlappingRendering) { - ALOGD("%*sSetAlpha %.2f", level * 2, "", mAlpha); + if (mAlpha < 1) { + if (mCaching || !mHasOverlappingRendering) { + ALOGD("%*sScaleAlpha %.2f", level * 2, "", mAlpha); } else { int flags = SkCanvas::kHasAlphaLayer_SaveFlag; if (mClipChildren) { diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index bc28d655aec4..2cf7183360d1 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -193,6 +193,7 @@ status_t OpenGLRenderer::prepareDirty(float left, float top, mSaveCount = 1; mSnapshot->setClip(left, top, right, bottom); + mTilingClip.set(left, top, right, bottom); mDirtyClip = true; updateLayers(); @@ -206,8 +207,7 @@ status_t OpenGLRenderer::prepareDirty(float left, float top, // invoked during the frame mSuppressTiling = mCaches.hasRegisteredFunctors(); - mTilingSnapshot = mSnapshot; - startTiling(mTilingSnapshot, true); + startTiling(mSnapshot, true); debugOverdraw(true, true); @@ -252,9 +252,9 @@ void OpenGLRenderer::syncState() { void OpenGLRenderer::startTiling(const sp<Snapshot>& s, bool opaque) { if (!mSuppressTiling) { - Rect* clip = mTilingSnapshot->clipRect; + Rect* clip = &mTilingClip; if (s->flags & Snapshot::kFlagFboTarget) { - clip = &s->layer->clipRect; + clip = &(s->layer->clipRect); } startTiling(*clip, s->height, opaque); @@ -480,10 +480,10 @@ void OpenGLRenderer::debugOverdraw(bool enable, bool clear) { void OpenGLRenderer::renderOverdraw() { if (mCaches.debugOverdraw && getTargetFbo() == 0) { - const Rect* clip = mTilingSnapshot->clipRect; + const Rect* clip = &mTilingClip; mCaches.enableScissor(); - mCaches.setScissor(clip->left, mTilingSnapshot->height - clip->bottom, + mCaches.setScissor(clip->left, mFirstSnapshot->height - clip->bottom, clip->right - clip->left, clip->bottom - clip->top); mCaches.stencil.enableDebugTest(2); diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 71bd6bb124f0..7bb93957bc03 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -947,7 +947,7 @@ private: // Current state sp<Snapshot> mSnapshot; // State used to define the clipping region - sp<Snapshot> mTilingSnapshot; + Rect mTilingClip; // Used to draw textured quads TextureVertex mMeshVertices[4]; diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp index 490c22a00553..07c420721fa5 100644 --- a/libs/hwui/PathCache.cpp +++ b/libs/hwui/PathCache.cpp @@ -347,14 +347,15 @@ void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) { // 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::remove(SkPath* path) { 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 e6d92df0b06c..146723162fa5 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 @@ public: 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 @@ public: 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 @@ private: } /** + * 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 @@ private: bool mDebugEnabled; sp<PathProcessor> mProcessor; - Vector<SkPath*> mGarbage; + + Vector<path_pair_t> mGarbage; mutable Mutex mLock; }; // class PathCache diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp index 923913e864a9..d26ee3884433 100644 --- a/libs/hwui/Snapshot.cpp +++ b/libs/hwui/Snapshot.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#define LOG_TAG "OpenGLRenderer" + #include "Snapshot.h" #include <SkCanvas.h> @@ -199,5 +201,14 @@ bool Snapshot::isIgnored() const { return invisible || empty; } +void Snapshot::dump() const { + ALOGD("Snapshot %p, flags %x, prev %p, height %d, ignored %d, hasComplexClip %d", + this, flags, previous.get(), height, isIgnored(), clipRegion && !clipRegion->isEmpty()); + ALOGD(" ClipRect (at %p) %.1f %.1f %.1f %.1f", + clipRect, clipRect->left, clipRect->top, clipRect->right, clipRect->bottom); + ALOGD(" Transform (at %p):", transform); + transform->dump(); +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index ffd47295b618..cc6d0cda003e 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -228,6 +228,8 @@ public: */ float alpha; + void dump() const; + private: void ensureClipRegion(); void copyClipRectFromRegion(); diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp index ce6c8c04483d..c8bfd9c88165 100644 --- a/libs/hwui/thread/TaskManager.cpp +++ b/libs/hwui/thread/TaskManager.cpp @@ -48,6 +48,12 @@ bool TaskManager::canRunTasks() const { 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 bc86062819cc..477314bbbb00 100644 --- a/libs/hwui/thread/TaskManager.h +++ b/libs/hwui/thread/TaskManager.h @@ -47,6 +47,12 @@ public: */ 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 000000000000..172606a4d586 --- /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 9e036d1124ec..5cf1c285f2db 100644 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -50,12 +50,11 @@ import android.os.Binder; 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 @@ public class NotificationManagerService extends INotificationManager.Stub final Context mContext; final IActivityManager mAm; + final UserManager mUserManager; final IBinder mForegroundToken = new Binder(); private WorkerHandler mHandler; @@ -164,6 +164,7 @@ public class NotificationManagerService extends INotificationManager.Stub 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 @@ public class NotificationManagerService extends INotificationManager.Stub 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 class NotificationManagerService extends INotificationManager.Stub } 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 class NotificationManagerService extends INotificationManager.Stub 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 class NotificationManagerService extends INotificationManager.Stub } 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 class NotificationManagerService extends INotificationManager.Stub } 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 @@ public class NotificationManagerService extends INotificationManager.Stub 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 @@ public class NotificationManagerService extends INotificationManager.Stub @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 @@ public class NotificationManagerService extends INotificationManager.Stub } 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 @@ public class NotificationManagerService extends INotificationManager.Stub 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 @@ public class NotificationManagerService extends INotificationManager.Stub 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 @@ public class NotificationManagerService extends INotificationManager.Stub 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 @@ public class NotificationManagerService extends INotificationManager.Stub 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 1ac6bdfb850f..8ff1c7d5fba7 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 @@ class ServiceRecord extends Binder { } 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 c46f2174593e..9dde58f75975 100644 --- a/services/java/com/android/server/wifi/WifiService.java +++ b/services/java/com/android/server/wifi/WifiService.java @@ -396,6 +396,18 @@ public final class WifiService extends IWifiManager.Stub { 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 f6543104ddd7..0c0a144ae234 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -408,6 +408,15 @@ public class WifiManager { "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. |