AI2 Component  (Version nb184)
SmsBroadcastReceiver.java
Go to the documentation of this file.
1 // -*- mode: java; c-basic-offset: 2; -*-
2 // Copyright 2011-2020 MIT, All rights reserved
3 // Released under the Apache License, Version 2.0
4 // http://www.apache.org/licenses/LICENSE-2.0
5 
6 /*
7  * This is revised from here: https://github.com/cicada-dev
8  * and was original published under Apache 2.0 license.
9  *
10  */
11 
12 package com.google.appinventor.components.runtime.util;
13 
14 import android.app.Notification;
15 import android.app.NotificationManager;
16 import android.app.PendingIntent;
17 import android.content.BroadcastReceiver;
18 import android.content.Context;
19 import android.content.Intent;
20 import android.telephony.PhoneNumberUtils;
21 import android.telephony.SmsMessage;
22 import android.util.Log;
23 import androidx.core.app.NotificationCompat;
27 import java.util.List;
28 
54 @SuppressWarnings("deprecation")
55 public class SmsBroadcastReceiver extends BroadcastReceiver {
56 
57  public static final String TAG = "SmsBroadcastReceiver";
58  public static final int NOTIFICATION_ID = 8647;
59 
65  @Override
66  public void onReceive(Context context, Intent intent) {
67  Log.i(TAG, "onReceive");
68 
69  // Extract the phone number and message.
70  String phone = getPhoneNumber(intent);
71  String msg = getMessage(intent);
72 
73  Log.i(TAG, "Received " + phone + " : " + msg);
74 
75  // If activity is receiving messages, send the message;
76  // It'll be cached if the app isn't running
77 
78  int receivingEnabled = Texting.isReceivingEnabled(context);
79 
80  // If isReceivingEnabled == 0, then we don't want anything EVER
81  if (receivingEnabled == ComponentConstants.TEXT_RECEIVING_OFF) {
82  Log.i(TAG, context.getApplicationInfo().packageName +
83  " Receiving is not enabled, ignoring message.");
84  return;
85  }
86 
87  // If we get this far, receiving is enabled for either FOREGROUND or ALWAYS
88 
89  if (((receivingEnabled == ComponentConstants.TEXT_RECEIVING_FOREGROUND) ||
90  isRepl(context)) && !Texting.isRunning()) {
91  Log.i(TAG, context.getApplicationInfo().packageName +
92  " Texting isn't running, and either receivingEnabled is FOREGROUND or we are the repl.");
93  return;
94  }
95 
96  // If we get this far, we want the message, either foreground or background
97 
98  Texting.handledReceivedMessage(context, phone, msg);
99  if (Texting.isRunning()) { // We are running in the foreground
100  Log.i(TAG, context.getApplicationInfo().packageName +
101  " App in Foreground, delivering message.");
102  } else {
103  Log.i(TAG, context.getApplicationInfo().packageName +
104  " Texting isn't running, but receivingEnabled == 2, sending notification.");
105  sendNotification(context, phone, msg);
106  }
107 
108  }
109 
115  private String getPhoneNumber(Intent intent) {
116  String phone = "";
117 
118  try {
119  if (intent.getAction().equals("com.google.android.apps.googlevoice.SMS_RECEIVED")) {
120  // For Google Voice, phone and msg are stored in String extras. Pretty them up
121 
122  phone = intent.getExtras().getString(Texting.PHONE_NUMBER_TAG);
123  phone = PhoneNumberUtils.formatNumber(phone);
124 
125  } else if (SdkLevel.getLevel() >= SdkLevel.LEVEL_KITKAT) {
126  // On KitKat or higher, use the convience getMessageFromIntent method.
127  List<SmsMessage> messages = KitkatUtil.getMessagesFromIntent(intent);
128  for (SmsMessage smsMsg : messages) {
129  if (smsMsg != null) {
130  // getOriginatingAddress() can throw a NPE if its wrapped message is null, but there
131  // isn't an API to check whether this is the case.
132  phone = smsMsg.getOriginatingAddress();
134  phone = LollipopUtil.formatNumber(phone);
135  } else {
136  phone = PhoneNumberUtils.formatNumber(phone);
137  }
138  }
139  }
140  } else {
141  // On SDK older than KitKat, we have to manually process the PDUs.
142  Object[] pdus = (Object[]) intent.getExtras().get("pdus");
143  for (Object pdu : pdus) {
144  SmsMessage smsMsg = SmsMessage.createFromPdu((byte[]) pdu);
145  phone = smsMsg.getOriginatingAddress();
146  phone = PhoneNumberUtils.formatNumber(phone);
147  }
148  }
149  } catch(NullPointerException e) {
150  Log.w(TAG, "Unable to retrieve originating address from SmsMessage", e);
151  }
152  return phone;
153  }
154 
160  private String getMessage(Intent intent) {
161  String msg = "";
162 
163  try {
164  if (intent.getAction().equals("com.google.android.apps.googlevoice.SMS_RECEIVED")) {
165  // For Google Voice, msg is stored in String extras.
166 
167  msg = intent.getExtras().getString(Texting.MESSAGE_TAG);
168 
169  } else if (SdkLevel.getLevel() >= SdkLevel.LEVEL_KITKAT) {
170  // On KitKat or higher, use the convience getMessageFromIntent method.
171  StringBuilder sb = new StringBuilder();
172  List<SmsMessage> messages = KitkatUtil.getMessagesFromIntent(intent);
173  for (SmsMessage smsMsg : messages) {
174  if (smsMsg != null) {
175  sb.append(smsMsg.getMessageBody());
176  }
177  }
178  msg = sb.toString();
179  } else {
180  // On SDK older than KitKat, we have to manually process the PDUs.
181  StringBuilder sb = new StringBuilder();
182  Object[] pdus = (Object[]) intent.getExtras().get("pdus");
183  for (Object pdu : pdus) {
184  SmsMessage smsMsg = SmsMessage.createFromPdu((byte[]) pdu);
185  sb.append(smsMsg.getMessageBody());
186  }
187  msg = sb.toString();
188  }
189  } catch(NullPointerException e) {
190  // getMessageBody() can throw a NPE if its wrapped message is null, but there isn't an
191  // API to check whether this is the case.
192  Log.w(TAG, "Unable to retrieve message body from SmsMessage", e);
193  }
194  return msg;
195  }
196 
203  private void sendNotification(Context context, String phone, String msg) {
204  Log.i(TAG, "sendingNotification " + phone + ":" + msg);
205 
206  // Get this app's name
207  String packageName = context.getPackageName();
208  Log.i(TAG, "Package name : " + packageName);
209 
210  Intent newIntent = null;
211 
212  // Will the activity name always be "Screen1"? If not, we need to revise this
213  try {
214  String classname = packageName + ".Screen1";
215  newIntent = new Intent(context, Class.forName(classname));
216  newIntent.setAction(Intent.ACTION_MAIN);
217  newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
218 
219  // These flags seem to work, but maybe there's a way to improve on this?
220  // NEW_TASK: the activity will become a new task on activity stack
221  // SINGLE_TOP: activity won't be launched if already on top of stack
222  newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
223 
224  // Create the Notification
225  PendingIntent activity = PendingIntent.getActivity(context, 0, newIntent, PendingIntent.FLAG_UPDATE_CURRENT);
226  NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
227  Notification note = new NotificationCompat.Builder(context)
228  .setSmallIcon(android.R.drawable.sym_call_incoming)
229  .setTicker(phone + " : " + msg)
230  .setWhen(System.currentTimeMillis())
231  .setAutoCancel(true)
232  .setDefaults(Notification.DEFAULT_SOUND)
233  .setContentTitle("Sms from " + phone)
234  .setContentText(msg)
235  .setContentIntent(activity)
236  .setNumber(Texting.getCachedMsgCount())
237  .build();
238 
239  nm.notify(null, NOTIFICATION_ID, note);
240  Log.i(TAG, "Notification sent, classname: " + classname);
241 
242  } catch (ClassNotFoundException e) {
243  e.printStackTrace();
244  }
245  }
246 
247  private boolean isRepl(Context context) {
248  try {
249  String packageName = context.getPackageName();
250  String classname = packageName + ".Screen1";
251  Class appClass = Class.forName(classname);
252  Class superClass = appClass.getSuperclass(); // This should be either Form or ReplForm
253  if (superClass.equals(ReplForm.class))
254  return true;
255  else
256  return false;
257  } catch (ClassNotFoundException e) {
258  e.printStackTrace();
259  return false; // If we loose, say we are not the repl
260  }
261  }
262 }
com.google.appinventor.components.runtime.util.SdkLevel.LEVEL_LOLLIPOP
static final int LEVEL_LOLLIPOP
Definition: SdkLevel.java:35
com.google.appinventor.components.runtime.ReplForm
Definition: ReplForm.java:62
com.google.appinventor.components.runtime.util.SmsBroadcastReceiver
Definition: SmsBroadcastReceiver.java:55
com.google.appinventor.components.runtime.util.SdkLevel.LEVEL_KITKAT
static final int LEVEL_KITKAT
Definition: SdkLevel.java:34
com.google.appinventor.components.runtime.util.SmsBroadcastReceiver.onReceive
void onReceive(Context context, Intent intent)
Definition: SmsBroadcastReceiver.java:66
com.google.appinventor.components.common.ComponentConstants.TEXT_RECEIVING_FOREGROUND
static final int TEXT_RECEIVING_FOREGROUND
Definition: ComponentConstants.java:67
com.google.appinventor.components
com.google.appinventor.components.runtime.Texting
Definition: Texting.java:169
com.google.appinventor.components.runtime.Texting.PHONE_NUMBER_TAG
static final String PHONE_NUMBER_TAG
Definition: Texting.java:182
com.google.appinventor.components.runtime.util.KitkatUtil
Definition: KitkatUtil.java:24
com.google.appinventor.components.runtime.Texting.handledReceivedMessage
static void handledReceivedMessage(Context context, String phone, String msg)
Definition: Texting.java:727
com.google.appinventor.components.runtime.Texting.isReceivingEnabled
static int isReceivingEnabled(Context context)
Definition: Texting.java:591
com.google.appinventor.components.runtime.util.SdkLevel
Definition: SdkLevel.java:19
com.google.appinventor.components.common.ComponentConstants.TEXT_RECEIVING_OFF
static final int TEXT_RECEIVING_OFF
Definition: ComponentConstants.java:66
com.google.appinventor.components.runtime.util.SdkLevel.getLevel
static int getLevel()
Definition: SdkLevel.java:45
com.google.appinventor.components.runtime
Copyright 2009-2011 Google, All Rights reserved.
Definition: AccelerometerSensor.java:8
com.google.appinventor.components.common
Definition: ComponentCategory.java:7
com.google.appinventor.components.runtime.util.KitkatUtil.getMessagesFromIntent
static List< SmsMessage > getMessagesFromIntent(Intent intent)
Definition: KitkatUtil.java:36
com.google
com
com.google.appinventor.components.common.ComponentConstants
Definition: ComponentConstants.java:13
com.google.appinventor