1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v4.app; 18 19import android.app.Notification; 20import android.app.Service; 21import android.content.Intent; 22import android.os.Build; 23import android.os.IBinder; 24import android.os.RemoteException; 25 26/** 27 * Abstract service to receive side channel notifications sent from 28 * {@link android.support.v4.app.NotificationManagerCompat}. 29 * 30 * <p>To receive side channel notifications, extend this service and register it in your 31 * android manifest with an intent filter for the BIND_NOTIFICATION_SIDE_CHANNEL action. 32 * Note: you must also have an enabled 33 * {@link android.service.notification.NotificationListenerService} within your package. 34 * 35 * <p>Example AndroidManifest.xml addition: 36 * <pre> 37 * <service android:name="com.example.NotificationSideChannelService"> 38 * <intent-filter> 39 * <action android:name="android.support.BIND_NOTIFICATION_SIDE_CHANNEL" /> 40 * </intent-filter> 41 * </service></pre> 42 * 43 */ 44public abstract class NotificationCompatSideChannelService extends Service { 45 @Override 46 public IBinder onBind(Intent intent) { 47 if (intent.getAction().equals(NotificationManagerCompat.ACTION_BIND_SIDE_CHANNEL)) { 48 // Block side channel service connections if the current sdk has no need for 49 // side channeling. 50 if (Build.VERSION.SDK_INT > NotificationManagerCompat.MAX_SIDE_CHANNEL_SDK_VERSION) { 51 return null; 52 } 53 return new NotificationSideChannelStub(); 54 } 55 return null; 56 } 57 58 /** 59 * Handle a side-channeled notification being posted. 60 */ 61 public abstract void notify(String packageName, int id, String tag, Notification notification); 62 63 /** 64 * Handle a side-channelled notification being cancelled. 65 */ 66 public abstract void cancel(String packageName, int id, String tag); 67 68 /** 69 * Handle the side-channelled cancelling of all notifications for a package. 70 */ 71 public abstract void cancelAll(String packageName); 72 73 private class NotificationSideChannelStub extends INotificationSideChannel.Stub { 74 @Override 75 public void notify(String packageName, int id, String tag, Notification notification) 76 throws RemoteException { 77 checkPermission(getCallingUid(), packageName); 78 long idToken = clearCallingIdentity(); 79 try { 80 NotificationCompatSideChannelService.this.notify(packageName, id, tag, notification); 81 } finally { 82 restoreCallingIdentity(idToken); 83 } 84 } 85 86 @Override 87 public void cancel(String packageName, int id, String tag) throws RemoteException { 88 checkPermission(getCallingUid(), packageName); 89 long idToken = clearCallingIdentity(); 90 try { 91 NotificationCompatSideChannelService.this.cancel(packageName, id, tag); 92 } finally { 93 restoreCallingIdentity(idToken); 94 } 95 } 96 97 @Override 98 public void cancelAll(String packageName) { 99 checkPermission(getCallingUid(), packageName); 100 long idToken = clearCallingIdentity(); 101 try { 102 NotificationCompatSideChannelService.this.cancelAll(packageName); 103 } finally { 104 restoreCallingIdentity(idToken); 105 } 106 } 107 } 108 109 private void checkPermission(int callingUid, String packageName) { 110 for (String validPackage : getPackageManager().getPackagesForUid(callingUid)) { 111 if (validPackage.equals(packageName)) { 112 return; 113 } 114 } 115 throw new SecurityException("NotificationSideChannelService: Uid " + callingUid 116 + " is not authorized for package " + packageName); 117 } 118} 119