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