1/*
2 * Copyright (C) 2011 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 com.android.dialer.calllog;
18
19import com.google.common.collect.Maps;
20
21import android.app.Notification;
22import android.app.NotificationManager;
23import android.app.PendingIntent;
24import android.content.ComponentName;
25import android.content.ContentResolver;
26import android.content.ContentUris;
27import android.content.Context;
28import android.content.Intent;
29import android.content.res.Resources;
30import android.net.Uri;
31import android.support.annotation.Nullable;
32import android.support.v4.util.Pair;
33import android.telecom.PhoneAccount;
34import android.telecom.PhoneAccountHandle;
35import android.telephony.TelephonyManager;
36import android.text.TextUtils;
37import android.util.Log;
38
39import com.android.contacts.common.ContactsUtils;
40import com.android.contacts.common.compat.TelephonyManagerCompat;
41import com.android.contacts.common.util.ContactDisplayUtils;
42import com.android.dialer.DialtactsActivity;
43import com.android.dialer.R;
44import com.android.dialer.calllog.CallLogNotificationsHelper.NewCall;
45import com.android.dialer.filterednumber.FilteredNumbersUtil;
46import com.android.dialer.list.ListsFragment;
47import com.android.dialer.util.TelecomUtil;
48
49import java.util.Iterator;
50import java.util.List;
51import java.util.Map;
52
53/**
54 * Shows a voicemail notification in the status bar.
55 */
56public class DefaultVoicemailNotifier {
57    public static final String TAG = "VoicemailNotifier";
58
59    /** The tag used to identify notifications from this class. */
60    private static final String NOTIFICATION_TAG = "DefaultVoicemailNotifier";
61    /** The identifier of the notification of new voicemails. */
62    private static final int NOTIFICATION_ID = 1;
63
64    /** The singleton instance of {@link DefaultVoicemailNotifier}. */
65    private static DefaultVoicemailNotifier sInstance;
66
67    private final Context mContext;
68
69    /** Returns the singleton instance of the {@link DefaultVoicemailNotifier}. */
70    public static DefaultVoicemailNotifier getInstance(Context context) {
71        if (sInstance == null) {
72            ContentResolver contentResolver = context.getContentResolver();
73            sInstance = new DefaultVoicemailNotifier(context);
74        }
75        return sInstance;
76    }
77
78    private DefaultVoicemailNotifier(Context context) {
79        mContext = context;
80    }
81
82    /**
83     * Updates the notification and notifies of the call with the given URI.
84     *
85     * Clears the notification if there are no new voicemails, and notifies if the given URI
86     * corresponds to a new voicemail.
87     *
88     * It is not safe to call this method from the main thread.
89     */
90    public void updateNotification(Uri newCallUri) {
91        // Lookup the list of new voicemails to include in the notification.
92        // TODO: Move this into a service, to avoid holding the receiver up.
93        final List<NewCall> newCalls =
94                CallLogNotificationsHelper.getInstance(mContext).getNewVoicemails();
95
96        if (newCalls == null) {
97            // Query failed, just return.
98            return;
99        }
100
101        if (newCalls.isEmpty()) {
102            // No voicemails to notify about: clear the notification.
103            getNotificationManager().cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
104            return;
105        }
106
107        Resources resources = mContext.getResources();
108
109        // This represents a list of names to include in the notification.
110        String callers = null;
111
112        // Maps each number into a name: if a number is in the map, it has already left a more
113        // recent voicemail.
114        final Map<String, String> names = Maps.newHashMap();
115
116        // Determine the call corresponding to the new voicemail we have to notify about.
117        NewCall callToNotify = null;
118
119        // Iterate over the new voicemails to determine all the information above.
120        Iterator<NewCall> itr = newCalls.iterator();
121        while (itr.hasNext()) {
122            NewCall newCall = itr.next();
123
124            // Skip notifying for numbers which are blocked.
125            if (FilteredNumbersUtil.shouldBlockVoicemail(
126                    mContext, newCall.number, newCall.countryIso, newCall.dateMs)) {
127                itr.remove();
128
129                // Delete the voicemail.
130                mContext.getContentResolver().delete(newCall.voicemailUri, null, null);
131                continue;
132            }
133
134            // Check if we already know the name associated with this number.
135            String name = names.get(newCall.number);
136            if (name == null) {
137                name = CallLogNotificationsHelper.getInstance(mContext).getName(newCall.number,
138                        newCall.numberPresentation, newCall.countryIso);
139                names.put(newCall.number, name);
140                // This is a new caller. Add it to the back of the list of callers.
141                if (TextUtils.isEmpty(callers)) {
142                    callers = name;
143                } else {
144                    callers = resources.getString(
145                            R.string.notification_voicemail_callers_list, callers, name);
146                }
147            }
148            // Check if this is the new call we need to notify about.
149            if (newCallUri != null && newCall.voicemailUri != null &&
150                    ContentUris.parseId(newCallUri) == ContentUris.parseId(newCall.voicemailUri)) {
151                callToNotify = newCall;
152            }
153        }
154
155        // All the potential new voicemails have been removed, e.g. if they were spam.
156        if (newCalls.isEmpty()) {
157            return;
158        }
159
160        // If there is only one voicemail, set its transcription as the "long text".
161        String transcription = null;
162        if (newCalls.size() == 1) {
163            transcription = newCalls.get(0).transcription;
164        }
165
166        if (newCallUri != null && callToNotify == null) {
167            Log.e(TAG, "The new call could not be found in the call log: " + newCallUri);
168        }
169
170        // Determine the title of the notification and the icon for it.
171        final String title = resources.getQuantityString(
172                R.plurals.notification_voicemail_title, newCalls.size(), newCalls.size());
173        // TODO: Use the photo of contact if all calls are from the same person.
174        final int icon = android.R.drawable.stat_notify_voicemail;
175
176        Pair<Uri, Integer> info = getNotificationInfo(callToNotify);
177
178        Notification.Builder notificationBuilder = new Notification.Builder(mContext)
179                .setSmallIcon(icon)
180                .setContentTitle(title)
181                .setContentText(callers)
182                .setStyle(new Notification.BigTextStyle().bigText(transcription))
183                .setColor(resources.getColor(R.color.dialer_theme_color))
184                .setSound(info.first)
185                .setDefaults(info.second)
186                .setDeleteIntent(createMarkNewVoicemailsAsOldIntent())
187                .setAutoCancel(true);
188
189        // Determine the intent to fire when the notification is clicked on.
190        final Intent contentIntent;
191        // Open the call log.
192        contentIntent = new Intent(mContext, DialtactsActivity.class);
193        contentIntent.putExtra(DialtactsActivity.EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_VOICEMAIL);
194        notificationBuilder.setContentIntent(PendingIntent.getActivity(
195                mContext, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT));
196
197        // The text to show in the ticker, describing the new event.
198        if (callToNotify != null) {
199            CharSequence msg = ContactDisplayUtils.getTtsSpannedPhoneNumber(
200                    resources,
201                    R.string.notification_new_voicemail_ticker,
202                    names.get(callToNotify.number));
203            notificationBuilder.setTicker(msg);
204        }
205        Log.i(TAG, "Creating voicemail notification");
206        getNotificationManager().notify(NOTIFICATION_TAG, NOTIFICATION_ID,
207                notificationBuilder.build());
208    }
209
210    /**
211     * Determines which ringtone Uri and Notification defaults to use when updating the notification
212     * for the given call.
213     */
214    private Pair<Uri, Integer> getNotificationInfo(@Nullable NewCall callToNotify) {
215        Log.v(TAG, "getNotificationInfo");
216        if (callToNotify == null) {
217            Log.i(TAG, "callToNotify == null");
218            return new Pair<>(null, 0);
219        }
220        PhoneAccountHandle accountHandle = null;
221        if (callToNotify.accountComponentName == null || callToNotify.accountId == null) {
222            Log.v(TAG, "accountComponentName == null || callToNotify.accountId == null");
223            accountHandle = TelecomUtil
224                .getDefaultOutgoingPhoneAccount(mContext, PhoneAccount.SCHEME_TEL);
225            if (accountHandle == null) {
226                Log.i(TAG, "No default phone account found, using default notification ringtone");
227                return new Pair<>(null, Notification.DEFAULT_ALL);
228            }
229
230        } else {
231            accountHandle = new PhoneAccountHandle(
232                ComponentName.unflattenFromString(callToNotify.accountComponentName),
233                callToNotify.accountId);
234        }
235        if (accountHandle.getComponentName() != null) {
236            Log.v(TAG, "PhoneAccountHandle.ComponentInfo:" + accountHandle.getComponentName());
237        } else {
238            Log.i(TAG, "PhoneAccountHandle.ComponentInfo: null");
239        }
240        return new Pair<>(
241                TelephonyManagerCompat.getVoicemailRingtoneUri(
242                        getTelephonyManager(), accountHandle),
243                getNotificationDefaults(accountHandle));
244    }
245
246    private int getNotificationDefaults(PhoneAccountHandle accountHandle) {
247        if (ContactsUtils.FLAG_N_FEATURE) {
248            return TelephonyManagerCompat.isVoicemailVibrationEnabled(getTelephonyManager(),
249                    accountHandle) ? Notification.DEFAULT_VIBRATE : 0;
250        }
251        return Notification.DEFAULT_ALL;
252    }
253
254    /** Creates a pending intent that marks all new voicemails as old. */
255    private PendingIntent createMarkNewVoicemailsAsOldIntent() {
256        Intent intent = new Intent(mContext, CallLogNotificationsService.class);
257        intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD);
258        return PendingIntent.getService(mContext, 0, intent, 0);
259    }
260
261    private NotificationManager getNotificationManager() {
262        return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
263    }
264
265    private TelephonyManager getTelephonyManager() {
266        return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
267    }
268
269}
270