1 /* 2 * Copyright (C) 2020 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 17 package com.android.test.notificationtrampoline; 18 19 import android.annotation.SuppressLint; 20 import android.app.Activity; 21 import android.app.Notification; 22 import android.app.NotificationChannel; 23 import android.app.NotificationManager; 24 import android.app.PendingIntent; 25 import android.app.Service; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.os.Binder; 31 import android.os.Bundle; 32 import android.os.Handler; 33 import android.os.IBinder; 34 import android.os.Message; 35 import android.os.Messenger; 36 import android.os.RemoteException; 37 import android.util.ArraySet; 38 39 import androidx.annotation.Nullable; 40 41 import java.lang.ref.WeakReference; 42 import java.util.Set; 43 import java.util.stream.Stream; 44 45 /** 46 * This is a bound service used in conjunction with trampoline tests in NotificationManagerTest. 47 */ 48 public class NotificationTrampolineTestService extends Service { 49 private static final String TAG = "TrampolineTestService"; 50 private static final String NOTIFICATION_CHANNEL_ID = "cts/" + TAG; 51 private static final String EXTRA_CALLBACK = "callback"; 52 private static final String EXTRA_ACTIVITY_REF = "activity_ref"; 53 private static final String RECEIVER_ACTION = ".TRAMPOLINE"; 54 private static final int MESSAGE_BROADCAST_NOTIFICATION = 1; 55 private static final int MESSAGE_SERVICE_NOTIFICATION = 2; 56 private static final int MESSAGE_CLICK_NOTIFICATION = 3; 57 private static final int TEST_MESSAGE_BROADCAST_RECEIVED = 1; 58 private static final int TEST_MESSAGE_SERVICE_STARTED = 2; 59 private static final int TEST_MESSAGE_ACTIVITY_STARTED = 3; 60 private static final int TEST_MESSAGE_NOTIFICATION_CLICKED = 4; 61 private static final int PI_FLAGS = 62 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE; 63 64 private final Handler mHandler = new ServiceHandler(); 65 private final ActivityReference mActivityRef = new ActivityReference(); 66 private final Set<Integer> mPostedNotifications = new ArraySet<>(); 67 private NotificationManager mNotificationManager; 68 private Messenger mMessenger; 69 private BroadcastReceiver mReceiver; 70 private Messenger mCallback; 71 private String mReceiverAction; 72 73 @Override onCreate()74 public void onCreate() { 75 mNotificationManager = getSystemService(NotificationManager.class); 76 mMessenger = new Messenger(mHandler); 77 mReceiverAction = getPackageName() + RECEIVER_ACTION; 78 } 79 80 @Nullable 81 @Override onBind(Intent intent)82 public IBinder onBind(Intent intent) { 83 return mMessenger.getBinder(); 84 } 85 86 @Override onDestroy()87 public void onDestroy() { 88 if (mReceiver != null) { 89 unregisterReceiver(mReceiver); 90 } 91 WeakReference<Activity> activityRef = mActivityRef.activity; 92 Activity activity = (activityRef != null) ? activityRef.get() : null; 93 if (activity != null) { 94 activity.finish(); 95 } 96 for (int notificationId : mPostedNotifications) { 97 mNotificationManager.cancel(notificationId); 98 } 99 mHandler.removeCallbacksAndMessages(null); 100 } 101 102 /** Suppressing since all messages are short-lived and we clear the queue on exit. */ 103 @SuppressLint("HandlerLeak") 104 private class ServiceHandler extends Handler { 105 @Override handleMessage(Message message)106 public void handleMessage(Message message) { 107 Context context = NotificationTrampolineTestService.this; 108 mCallback = (Messenger) message.obj; 109 int notificationId = message.arg1; 110 switch (message.what) { 111 case MESSAGE_BROADCAST_NOTIFICATION: { 112 mReceiver = new BroadcastReceiver() { 113 @Override 114 public void onReceive(Context context, Intent broadcastIntent) { 115 sendMessageToTest(mCallback, TEST_MESSAGE_BROADCAST_RECEIVED, true); 116 startTargetActivity(); 117 } 118 }; 119 registerReceiver(mReceiver, new IntentFilter(mReceiverAction)); 120 Intent intent = new Intent(mReceiverAction); 121 postNotification(notificationId, 122 PendingIntent.getBroadcast(context, 0, intent, PI_FLAGS)); 123 break; 124 } 125 case MESSAGE_SERVICE_NOTIFICATION: { 126 // We use this service to act as the trampoline since the bound lifecycle (which 127 // is as long as the test is being executed) outlives the started (used by the 128 // trampoline) in this case. 129 Intent intent = new Intent(context, NotificationTrampolineTestService.class); 130 postNotification(notificationId, 131 PendingIntent.getService(context, 0, intent, PI_FLAGS)); 132 break; 133 } 134 case MESSAGE_CLICK_NOTIFICATION: { 135 PendingIntent intent = Stream 136 .of(mNotificationManager.getActiveNotifications()) 137 .filter(sb -> sb.getId() == notificationId) 138 .map(sb -> sb.getNotification().contentIntent) 139 .findFirst() 140 .orElse(null); 141 if (intent != null) { 142 try { 143 intent.send(); 144 } catch (PendingIntent.CanceledException e) { 145 throw new IllegalStateException("Notification PI cancelled", e); 146 } 147 } 148 sendMessageToTest(mCallback, TEST_MESSAGE_NOTIFICATION_CLICKED, intent != null); 149 break; 150 } 151 default: 152 throw new AssertionError("Unknown message " + message.what); 153 } 154 } 155 } 156 157 @Override onStartCommand(Intent serviceIntent, int flags, int startId)158 public int onStartCommand(Intent serviceIntent, int flags, int startId) { 159 sendMessageToTest(mCallback, TEST_MESSAGE_SERVICE_STARTED, true); 160 startTargetActivity(); 161 stopSelf(startId); 162 return START_REDELIVER_INTENT; 163 } 164 postNotification(int notificationId, PendingIntent intent)165 private void postNotification(int notificationId, PendingIntent intent) { 166 Notification notification = 167 new Notification.Builder(this, NOTIFICATION_CHANNEL_ID) 168 .setSmallIcon(android.R.drawable.ic_info) 169 .setContentIntent(intent) 170 .build(); 171 NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, 172 NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT); 173 mNotificationManager.createNotificationChannel(notificationChannel); 174 mNotificationManager.notify(notificationId, notification); 175 mPostedNotifications.add(notificationId); 176 } 177 startTargetActivity()178 private void startTargetActivity() { 179 Intent intent = new Intent(this, TargetActivity.class); 180 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 181 Bundle extras = new Bundle(); 182 extras.putParcelable(EXTRA_CALLBACK, mCallback); 183 extras.putBinder(EXTRA_ACTIVITY_REF, mActivityRef); 184 intent.putExtras(extras); 185 startActivity(intent); 186 } 187 sendMessageToTest(Messenger callback, int message, boolean success)188 private static void sendMessageToTest(Messenger callback, int message, boolean success) { 189 try { 190 callback.send(Message.obtain(null, message, success ? 0 : 1, 0)); 191 } catch (RemoteException e) { 192 throw new IllegalStateException( 193 "Couldn't send message " + message + " to test process", e); 194 } 195 } 196 197 /** 198 * A holder object that extends from Binder just so I can send it around using startActivity() 199 * and avoid using static state. Works since the communication is local. 200 */ 201 private static class ActivityReference extends Binder { 202 public WeakReference<Activity> activity; 203 } 204 205 public static class TargetActivity extends Activity { 206 @Override onResume()207 protected void onResume() { 208 super.onResume(); 209 Messenger callback = getIntent().getParcelableExtra(EXTRA_CALLBACK); 210 IBinder activityRef = getIntent().getExtras().getBinder(EXTRA_ACTIVITY_REF); 211 if (activityRef instanceof ActivityReference) { 212 ((ActivityReference) activityRef).activity = new WeakReference<>(this); 213 } 214 sendMessageToTest(callback, TEST_MESSAGE_ACTIVITY_STARTED, true); 215 } 216 } 217 } 218