Home | History | Annotate | Download | only in calllog
      1 /*
      2  * Copyright (C) 2016 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 package com.android.dialer.calllog;
     17 
     18 import android.app.Notification;
     19 import android.app.NotificationManager;
     20 import android.app.PendingIntent;
     21 import android.content.ContentValues;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.graphics.Bitmap;
     25 import android.os.AsyncTask;
     26 import android.provider.CallLog.Calls;
     27 import android.text.TextUtils;
     28 import android.util.Log;
     29 
     30 import com.android.contacts.common.ContactsUtils;
     31 import com.android.contacts.common.util.PhoneNumberHelper;
     32 import com.android.dialer.DialtactsActivity;
     33 import com.android.dialer.R;
     34 import com.android.dialer.calllog.CallLogNotificationsHelper.NewCall;
     35 import com.android.dialer.contactinfo.ContactPhotoLoader;
     36 import com.android.dialer.compat.UserManagerCompat;
     37 import com.android.dialer.list.ListsFragment;
     38 import com.android.dialer.util.DialerUtils;
     39 import com.android.dialer.util.IntentUtil;
     40 import com.android.dialer.util.IntentUtil.CallIntentBuilder;
     41 
     42 import java.util.List;
     43 
     44 /**
     45  * Creates a notification for calls that the user missed (neither answered nor rejected).
     46  *
     47  */
     48 public class MissedCallNotifier {
     49     public static final String TAG = "MissedCallNotifier";
     50 
     51     /** The tag used to identify notifications from this class. */
     52     private static final String NOTIFICATION_TAG = "MissedCallNotifier";
     53     /** The identifier of the notification of new missed calls. */
     54     private static final int NOTIFICATION_ID = 1;
     55     /** Preference file key for number of missed calls. */
     56     private static final String MISSED_CALL_COUNT = "missed_call_count";
     57 
     58     private static MissedCallNotifier sInstance;
     59     private Context mContext;
     60 
     61     /** Returns the singleton instance of the {@link MissedCallNotifier}. */
     62     public static MissedCallNotifier getInstance(Context context) {
     63         if (sInstance == null) {
     64             sInstance = new MissedCallNotifier(context);
     65         }
     66         return sInstance;
     67     }
     68 
     69     private MissedCallNotifier(Context context) {
     70         mContext = context;
     71     }
     72 
     73     public void updateMissedCallNotification(int count, String number) {
     74         final int titleResId;
     75         final String expandedText;  // The text in the notification's line 1 and 2.
     76 
     77         final List<NewCall> newCalls =
     78                 CallLogNotificationsHelper.getInstance(mContext).getNewMissedCalls();
     79 
     80         if (count == CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT) {
     81             if (newCalls == null) {
     82                 // If the intent did not contain a count, and we are unable to get a count from the
     83                 // call log, then no notification can be shown.
     84                 return;
     85             }
     86             count = newCalls.size();
     87         }
     88 
     89         if (count == 0) {
     90             // No voicemails to notify about: clear the notification.
     91             clearMissedCalls();
     92             return;
     93         }
     94 
     95         // The call log has been updated, use that information preferentially.
     96         boolean useCallLog = newCalls != null && newCalls.size() == count;
     97         NewCall newestCall = useCallLog ? newCalls.get(0) : null;
     98         long timeMs = useCallLog ? newestCall.dateMs : System.currentTimeMillis();
     99 
    100         Notification.Builder builder = new Notification.Builder(mContext);
    101         // Display the first line of the notification:
    102         // 1 missed call: <caller name || handle>
    103         // More than 1 missed call: <number of calls> + "missed calls"
    104         if (count == 1) {
    105             //TODO: look up caller ID that is not in contacts.
    106             ContactInfo contactInfo = CallLogNotificationsHelper.getInstance(mContext)
    107                     .getContactInfo(useCallLog ? newestCall.number : number,
    108                             useCallLog ? newestCall.numberPresentation
    109                                     : Calls.PRESENTATION_ALLOWED,
    110                             useCallLog ? newestCall.countryIso : null);
    111 
    112             titleResId = contactInfo.userType == ContactsUtils.USER_TYPE_WORK
    113                     ? R.string.notification_missedWorkCallTitle
    114                     : R.string.notification_missedCallTitle;
    115 
    116             expandedText = contactInfo.name;
    117             ContactPhotoLoader loader = new ContactPhotoLoader(mContext, contactInfo);
    118             Bitmap photoIcon = loader.loadPhotoIcon();
    119             if (photoIcon != null) {
    120                 builder.setLargeIcon(photoIcon);
    121             }
    122         } else {
    123             titleResId = R.string.notification_missedCallsTitle;
    124             expandedText =
    125                     mContext.getString(R.string.notification_missedCallsMsg, count);
    126         }
    127 
    128         // Create a public viewable version of the notification, suitable for display when sensitive
    129         // notification content is hidden.
    130         Notification.Builder publicBuilder = new Notification.Builder(mContext);
    131         publicBuilder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
    132                 .setColor(mContext.getResources().getColor(R.color.dialer_theme_color))
    133                 // Show "Phone" for notification title.
    134                 .setContentTitle(mContext.getText(R.string.userCallActivityLabel))
    135                 // Notification details shows that there are missed call(s), but does not reveal
    136                 // the missed caller information.
    137                 .setContentText(mContext.getText(titleResId))
    138                 .setContentIntent(createCallLogPendingIntent())
    139                 .setAutoCancel(true)
    140                 .setWhen(timeMs)
    141                 .setDeleteIntent(createClearMissedCallsPendingIntent());
    142 
    143         // Create the notification suitable for display when sensitive information is showing.
    144         builder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
    145                 .setColor(mContext.getResources().getColor(R.color.dialer_theme_color))
    146                 .setContentTitle(mContext.getText(titleResId))
    147                 .setContentText(expandedText)
    148                 .setContentIntent(createCallLogPendingIntent())
    149                 .setAutoCancel(true)
    150                 .setWhen(timeMs)
    151                 .setDeleteIntent(createClearMissedCallsPendingIntent())
    152                 // Include a public version of the notification to be shown when the missed call
    153                 // notification is shown on the user's lock screen and they have chosen to hide
    154                 // sensitive notification information.
    155                 .setPublicVersion(publicBuilder.build());
    156 
    157         // Add additional actions when there is only 1 missed call and the user isn't locked
    158         if (UserManagerCompat.isUserUnlocked(mContext) && count == 1) {
    159             if (!TextUtils.isEmpty(number)
    160                     && !TextUtils.equals(
    161                     number, mContext.getString(R.string.handle_restricted))) {
    162                 builder.addAction(R.drawable.ic_phone_24dp,
    163                         mContext.getString(R.string.notification_missedCall_call_back),
    164                         createCallBackPendingIntent(number));
    165 
    166                 if (!PhoneNumberHelper.isUriNumber(number)) {
    167                     builder.addAction(R.drawable.ic_message_24dp,
    168                             mContext.getString(R.string.notification_missedCall_message),
    169                             createSendSmsFromNotificationPendingIntent(number));
    170                 }
    171             }
    172         }
    173 
    174         Notification notification = builder.build();
    175         configureLedOnNotification(notification);
    176 
    177         Log.i(TAG, "Adding missed call notification.");
    178         getNotificationMgr().notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification);
    179     }
    180 
    181     private void clearMissedCalls() {
    182         AsyncTask.execute(new Runnable() {
    183             @Override
    184             public void run() {
    185                 // Call log is only accessible when unlocked. If that's the case, clear the list of
    186                 // new missed calls from the call log.
    187                 if (UserManagerCompat.isUserUnlocked(mContext)) {
    188                     ContentValues values = new ContentValues();
    189                     values.put(Calls.NEW, 0);
    190                     values.put(Calls.IS_READ, 1);
    191                     StringBuilder where = new StringBuilder();
    192                     where.append(Calls.NEW);
    193                     where.append(" = 1 AND ");
    194                     where.append(Calls.TYPE);
    195                     where.append(" = ?");
    196                     try {
    197                         mContext.getContentResolver().update(Calls.CONTENT_URI, values,
    198                                 where.toString(), new String[]{Integer.toString(Calls.
    199                                         MISSED_TYPE)});
    200                     } catch (IllegalArgumentException e) {
    201                         Log.w(TAG, "ContactsProvider update command failed", e);
    202                     }
    203                 }
    204                 getNotificationMgr().cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
    205             }
    206         });
    207     }
    208 
    209     /**
    210      * Trigger an intent to make a call from a missed call number.
    211      */
    212     public void callBackFromMissedCall(String number) {
    213         closeSystemDialogs(mContext);
    214         CallLogNotificationsHelper.removeMissedCallNotifications(mContext);
    215         DialerUtils.startActivityWithErrorToast(
    216                 mContext,
    217                 new CallIntentBuilder(number)
    218                         .build()
    219                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    220     }
    221 
    222     /**
    223      * Trigger an intent to send an sms from a missed call number.
    224      */
    225     public void sendSmsFromMissedCall(String number) {
    226         closeSystemDialogs(mContext);
    227         CallLogNotificationsHelper.removeMissedCallNotifications(mContext);
    228         DialerUtils.startActivityWithErrorToast(
    229                 mContext,
    230                 IntentUtil.getSendSmsIntent(number).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    231     }
    232 
    233     /**
    234      * Creates a new pending intent that sends the user to the call log.
    235      *
    236      * @return The pending intent.
    237      */
    238     private PendingIntent createCallLogPendingIntent() {
    239         Intent contentIntent = new Intent(mContext, DialtactsActivity.class);
    240         contentIntent.putExtra(DialtactsActivity.EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_HISTORY);
    241         return PendingIntent.getActivity(
    242                 mContext, 0, contentIntent,PendingIntent.FLAG_UPDATE_CURRENT);
    243     }
    244 
    245     /** Creates a pending intent that marks all new missed calls as old. */
    246     private PendingIntent createClearMissedCallsPendingIntent() {
    247         Intent intent = new Intent(mContext, CallLogNotificationsService.class);
    248         intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_MISSED_CALLS_AS_OLD);
    249         return PendingIntent.getService(mContext, 0, intent, 0);
    250     }
    251 
    252     private PendingIntent createCallBackPendingIntent(String number) {
    253         Intent intent = new Intent(mContext, CallLogNotificationsService.class);
    254         intent.setAction(
    255                 CallLogNotificationsService.ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION);
    256         intent.putExtra(CallLogNotificationsService.EXTRA_MISSED_CALL_NUMBER, number);
    257         // Use FLAG_UPDATE_CURRENT to make sure any previous pending intent is updated with the new
    258         // extra.
    259         return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    260     }
    261 
    262     private PendingIntent createSendSmsFromNotificationPendingIntent(String number) {
    263         Intent intent = new Intent(mContext, CallLogNotificationsService.class);
    264         intent.setAction(
    265                 CallLogNotificationsService.ACTION_SEND_SMS_FROM_MISSED_CALL_NOTIFICATION);
    266         intent.putExtra(CallLogNotificationsService.EXTRA_MISSED_CALL_NUMBER, number);
    267         // Use FLAG_UPDATE_CURRENT to make sure any previous pending intent is updated with the new
    268         // extra.
    269         return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    270     }
    271 
    272     /**
    273      * Configures a notification to emit the blinky notification light.
    274      */
    275     private void configureLedOnNotification(Notification notification) {
    276         notification.flags |= Notification.FLAG_SHOW_LIGHTS;
    277         notification.defaults |= Notification.DEFAULT_LIGHTS;
    278     }
    279 
    280     /**
    281      * Closes open system dialogs and the notification shade.
    282      */
    283     private void closeSystemDialogs(Context context) {
    284         context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
    285     }
    286 
    287     private NotificationManager getNotificationMgr() {
    288         return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
    289     }
    290 }
    291