Listener in createNotificationChannel

Necessary for when this will eventually trigger an Activity.
New unit test file for NotificationServiceManager.

Test: runtest systemui-notification (cts tests in separate CL)
Change-Id: I8f3e8e34ddcebb1acb9ddd84bffc68affb4b6e89
This commit is contained in:
Geoffrey Pitsch
2016-11-22 11:12:11 -05:00
parent 8d86417c8e
commit e75a66e87a
9 changed files with 218 additions and 28 deletions

View File

@@ -79,6 +79,7 @@ LOCAL_SRC_FILES += \
core/java/android/app/IBackupAgent.aidl \
core/java/android/app/IEphemeralResolver.aidl \
core/java/android/app/IInstrumentationWatcher.aidl \
core/java/android/app/IOnNotificationChannelCreatedListener.aidl \
core/java/android/app/INotificationManager.aidl \
core/java/android/app/IProcessObserver.aidl \
core/java/android/app/ISearchManager.aidl \

View File

@@ -5387,7 +5387,7 @@ package android.app {
method public void cancel(int);
method public void cancel(java.lang.String, int);
method public void cancelAll();
method public void createNotificationChannel(android.app.NotificationChannel);
method public void createNotificationChannel(android.app.NotificationChannel, android.app.NotificationManager.OnNotificationChannelCreatedListener, android.os.Handler);
method public void deleteNotificationChannel(java.lang.String);
method public android.service.notification.StatusBarNotification[] getActiveNotifications();
method public android.app.AutomaticZenRule getAutomaticZenRule(java.lang.String);
@@ -5421,6 +5421,10 @@ package android.app {
field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
}
public static abstract interface NotificationManager.OnNotificationChannelCreatedListener {
method public abstract void onNotificationChannelCreated(android.app.NotificationChannel);
}
public static class NotificationManager.Policy implements android.os.Parcelable {
ctor public NotificationManager.Policy(int, int, int);
ctor public NotificationManager.Policy(int, int, int, int);

View File

@@ -5554,7 +5554,7 @@ package android.app {
method public void cancel(int);
method public void cancel(java.lang.String, int);
method public void cancelAll();
method public void createNotificationChannel(android.app.NotificationChannel);
method public void createNotificationChannel(android.app.NotificationChannel, android.app.NotificationManager.OnNotificationChannelCreatedListener, android.os.Handler);
method public void deleteNotificationChannel(java.lang.String);
method public android.service.notification.StatusBarNotification[] getActiveNotifications();
method public android.app.AutomaticZenRule getAutomaticZenRule(java.lang.String);
@@ -5588,6 +5588,10 @@ package android.app {
field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
}
public static abstract interface NotificationManager.OnNotificationChannelCreatedListener {
method public abstract void onNotificationChannelCreated(android.app.NotificationChannel);
}
public static class NotificationManager.Policy implements android.os.Parcelable {
ctor public NotificationManager.Policy(int, int, int);
ctor public NotificationManager.Policy(int, int, int, int);

View File

@@ -5397,7 +5397,7 @@ package android.app {
method public void cancel(int);
method public void cancel(java.lang.String, int);
method public void cancelAll();
method public void createNotificationChannel(android.app.NotificationChannel);
method public void createNotificationChannel(android.app.NotificationChannel, android.app.NotificationManager.OnNotificationChannelCreatedListener, android.os.Handler);
method public void deleteNotificationChannel(java.lang.String);
method public android.service.notification.StatusBarNotification[] getActiveNotifications();
method public android.app.AutomaticZenRule getAutomaticZenRule(java.lang.String);
@@ -5432,6 +5432,10 @@ package android.app {
field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
}
public static abstract interface NotificationManager.OnNotificationChannelCreatedListener {
method public abstract void onNotificationChannelCreated(android.app.NotificationChannel);
}
public static class NotificationManager.Policy implements android.os.Parcelable {
ctor public NotificationManager.Policy(int, int, int);
ctor public NotificationManager.Policy(int, int, int, int);

View File

@@ -2,21 +2,22 @@
**
** Copyright 2007, 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
** 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
** 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
** 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 android.app;
import android.app.IOnNotificationChannelCreatedListener;
import android.app.ITransientNotification;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -58,7 +59,8 @@ interface INotificationManager
int getImportance(String pkg, int uid);
int getPackageImportance(String pkg);
void createNotificationChannel(String pkg, in NotificationChannel channel);
void createNotificationChannel(String pkg, in NotificationChannel channel,
in IOnNotificationChannelCreatedListener listener);
void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel);
NotificationChannel getNotificationChannel(String pkg, String channelId);
NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId);

View File

@@ -0,0 +1,25 @@
/*
**
** Copyright 2016, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
package android.app;
import android.app.NotificationChannel;
/** {@hide} */
oneway interface IOnNotificationChannelCreatedListener {
void onNotificationChannelCreated(in NotificationChannel channel);
}

View File

@@ -18,6 +18,7 @@ package android.app;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.TestApi;
import android.app.Notification.Builder;
@@ -30,6 +31,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
@@ -379,12 +381,42 @@ public class NotificationManager
}
/**
* Creates a notification channel that notifications can be posted to.
* Listener passed to {@link NotificationManager#createNotificationChannel} to notify
* caller of result.
*/
public void createNotificationChannel(NotificationChannel channel) {
public interface OnNotificationChannelCreatedListener {
/**
* @param createdChannel NotificationChannel created by the system. Value is null iff an
* exception was thrown during channel creation.
*/
public void onNotificationChannelCreated(NotificationChannel createdChannel);
}
/**
* Creates a notification channel that notifications can be posted to.
*
* @param channel the channel to attempt to create. Note that the created channel may differ
* from this value.
* @param listener Called when operation is finished.
* @param handler The handler to invoke the listener on, or {@code null} to use the main
* handler.
*/
public void createNotificationChannel(
@NonNull NotificationChannel channel,
@NonNull OnNotificationChannelCreatedListener listener,
@Nullable Handler handler) {
INotificationManager service = getService();
try {
service.createNotificationChannel(mContext.getPackageName(), channel);
final Handler actualHandler =
handler != null ? handler : new Handler(Looper.getMainLooper());
service.createNotificationChannel(mContext.getPackageName(), channel,
new IOnNotificationChannelCreatedListener.Stub() {
@Override public void onNotificationChannelCreated(
NotificationChannel channel) {
actualHandler.post(
() -> { listener.onNotificationChannelCreated(channel); });
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -583,7 +615,7 @@ public class NotificationManager
* <p>
* Callers can only update rules that they own. See {@link AutomaticZenRule#getOwner}.
* @param id The id of the rule to update
* @param automaticZenRule the rule to update.
* @param automaticZenRule the rule to update.
* @return Whether the rule was successfully updated.
*/
public boolean updateAutomaticZenRule(String id, AutomaticZenRule automaticZenRule) {

View File

@@ -53,16 +53,17 @@ import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
import android.app.backup.BackupManager;
import android.app.IActivityManager;
import android.app.IOnNotificationChannelCreatedListener;
import android.app.INotificationManager;
import android.app.ITransientNotification;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.backup.BackupManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
@@ -224,6 +225,7 @@ public class NotificationManagerService extends SystemService {
private static final long MIN_PACKAGE_OVERRATE_LOG_INTERVAL = 5000; // milliseconds
private IActivityManager mAm;
private IPackageManager mPackageManager;
AudioManager mAudioManager;
AudioManagerInternal mAudioManagerInternal;
@Nullable StatusBarManagerInternal mStatusBar;
@@ -718,10 +720,10 @@ public class NotificationManagerService extends SystemService {
if (packageChanged) {
// We cancel notifications for packages which have just been disabled
try {
final IPackageManager pm = AppGlobals.getPackageManager();
final int enabled = pm.getApplicationEnabledSetting(pkgName,
final int enabled = mPackageManager.getApplicationEnabledSetting(
pkgName,
changeUserId != UserHandle.USER_ALL ? changeUserId :
UserHandle.USER_SYSTEM);
UserHandle.USER_SYSTEM);
if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|| enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
cancelNotifications = false;
@@ -884,6 +886,7 @@ public class NotificationManagerService extends SystemService {
super(context);
}
// TODO - replace these methods with a single VisibleForTesting constructor
@VisibleForTesting
void setAudioManager(AudioManager audioMananger) {
mAudioManager = audioMananger;
@@ -931,6 +934,17 @@ public class NotificationManagerService extends SystemService {
mFallbackVibrationPattern = vibrationPattern;
}
@VisibleForTesting
void setPackageManager(IPackageManager packageManager) {
mPackageManager = packageManager;
}
// TODO: This probably should not be mocked, it's an implementation detail.
@VisibleForTesting
void setRankingHelper(RankingHelper rankingHelper) {
mRankingHelper = rankingHelper;
}
@Override
public void onStart() {
Resources resources = getContext().getResources();
@@ -940,6 +954,7 @@ public class NotificationManagerService extends SystemService {
DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE);
mAm = ActivityManager.getService();
mPackageManager = AppGlobals.getPackageManager();
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
@@ -1312,6 +1327,11 @@ public class NotificationManagerService extends SystemService {
scheduleInterruptionFilterChanged(interruptionFilter);
}
@VisibleForTesting
INotificationManager getBinderService() {
return INotificationManager.Stub.asInterface(mService);
}
private final IBinder mService = new INotificationManager.Stub() {
// Toasts
// ============================================================================
@@ -1529,13 +1549,15 @@ public class NotificationManagerService extends SystemService {
}
@Override
public void createNotificationChannel(String pkg, NotificationChannel channel) {
public void createNotificationChannel(String pkg, NotificationChannel channel,
IOnNotificationChannelCreatedListener listener) throws RemoteException {
Preconditions.checkNotNull(channel);
Preconditions.checkNotNull(channel.getId());
Preconditions.checkNotNull(channel.getName());
checkCallerIsSystemOrSameApp(pkg);
mRankingHelper.createNotificationChannel(pkg, Binder.getCallingUid(), channel);
savePolicyFile();
listener.onNotificationChannelCreated(channel);
}
@Override
@@ -3906,23 +3928,23 @@ public class NotificationManagerService extends SystemService {
throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid());
}
private static void checkCallerIsSystemOrSameApp(String pkg) {
private void checkCallerIsSystemOrSameApp(String pkg) {
if (isCallerSystem()) {
return;
}
checkCallerIsSameApp(pkg);
}
private static void checkCallerIsSameApp(String pkg) {
private void checkCallerIsSameApp(String pkg) {
final int uid = Binder.getCallingUid();
try {
ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
ApplicationInfo ai = mPackageManager.getApplicationInfo(
pkg, 0, UserHandle.getCallingUserId());
if (ai == null) {
throw new SecurityException("Unknown package " + pkg);
}
if (!UserHandle.isSameApp(ai.uid, uid)) {
throw new SecurityException("Calling uid " + uid + " gave package"
throw new SecurityException("Calling uid " + uid + " gave package "
+ pkg + " which is owned by uid " + ai.uid);
}
} catch (RemoteException re) {
@@ -4009,7 +4031,7 @@ public class NotificationManagerService extends SystemService {
private boolean isPackageSuspendedForUser(String pkg, int uid) {
int userId = UserHandle.getUserId(uid);
try {
return AppGlobals.getPackageManager().isPackageSuspendedForUser(pkg, userId);
return mPackageManager.isPackageSuspendedForUser(pkg, userId);
} catch (RemoteException re) {
throw new SecurityException("Could not talk to package manager service");
} catch (IllegalArgumentException ex) {
@@ -4510,7 +4532,7 @@ public class NotificationManagerService extends SystemService {
}
public String[] getRequestingPackages() throws RemoteException {
final ParceledListSlice list = AppGlobals.getPackageManager()
final ParceledListSlice list = mPackageManager
.getPackagesHoldingPermissions(PERM, 0 /*flags*/,
ActivityManager.getCurrentUser());
final List<PackageInfo> pkgs = list.getList();

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.notification;
import static junit.framework.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.app.INotificationManager;
import android.app.IOnNotificationChannelCreatedListener;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.os.Binder;
import android.os.Handler;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import java.util.concurrent.CountDownLatch;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class NotificationManagerServiceTest {
private NotificationManagerService mNotificationManagerService;
private INotificationManager mBinderService;
@Before
public void setUp() throws Exception {
final Context context = InstrumentationRegistry.getTargetContext();
mNotificationManagerService = new NotificationManagerService(context);
// MockPackageManager - default returns ApplicationInfo with matching calling UID
final IPackageManager mockPackageManager = mock(IPackageManager.class);
final ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = Binder.getCallingUid();
when(mockPackageManager.getApplicationInfo(any(), anyInt(), anyInt()))
.thenReturn(applicationInfo);
mNotificationManagerService.setPackageManager(mockPackageManager);
mNotificationManagerService.setRankingHelper(mock(RankingHelper.class));
mNotificationManagerService.setHandler(new Handler(context.getMainLooper()));
// Tests call directly into the Binder.
mBinderService = mNotificationManagerService.getBinderService();
}
@Test
public void testCreateNotificationChannel_SuccessCallsListener() throws Exception {
final NotificationChannel channel =
new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT);
final CountDownLatch latch = new CountDownLatch(1);
mBinderService.createNotificationChannel("test_pkg", channel,
new IOnNotificationChannelCreatedListener.Stub() {
@Override public void onNotificationChannelCreated(
NotificationChannel channel) {
latch.countDown();
}});
latch.await();
}
@Test
public void testCreateNotificationChannel_FailureDoesNotCallListener() throws Exception {
try {
mBinderService.createNotificationChannel("test_pkg", null,
new IOnNotificationChannelCreatedListener.Stub() {
@Override public void onNotificationChannelCreated(
NotificationChannel channel) {
fail("Listener was triggered from failure.");
}});
fail("Exception should be thrown immediately.");
} catch (NullPointerException e) {
// pass
}
}
}