1/*
2 * Copyright (C) 2015 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 */
16package com.android.messaging.ui.conversationlist;
17
18import android.app.Activity;
19import android.app.AlertDialog;
20import android.app.Fragment;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.content.Intent;
24import android.content.res.Resources;
25import android.net.Uri;
26import android.os.Bundle;
27import android.support.annotation.NonNull;
28import android.view.View;
29
30import com.android.messaging.R;
31import com.android.messaging.datamodel.action.DeleteConversationAction;
32import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
33import com.android.messaging.datamodel.action.UpdateConversationOptionsAction;
34import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction;
35import com.android.messaging.datamodel.data.ConversationListData;
36import com.android.messaging.datamodel.data.ConversationListItemData;
37import com.android.messaging.ui.BugleActionBarActivity;
38import com.android.messaging.ui.SnackBar;
39import com.android.messaging.ui.SnackBarInteraction;
40import com.android.messaging.ui.UIIntents;
41import com.android.messaging.ui.contact.AddContactsConfirmationDialog;
42import com.android.messaging.ui.conversationlist.ConversationListFragment.ConversationListFragmentHost;
43import com.android.messaging.ui.conversationlist.MultiSelectActionModeCallback.SelectedConversation;
44import com.android.messaging.util.BugleGservices;
45import com.android.messaging.util.BugleGservicesKeys;
46import com.android.messaging.util.DebugUtils;
47import com.android.messaging.util.PhoneUtils;
48import com.android.messaging.util.Trace;
49import com.android.messaging.util.UiUtils;
50
51import java.util.ArrayList;
52import java.util.Collection;
53import java.util.List;
54
55import javax.annotation.Nullable;
56
57/**
58 * Base class for many Conversation List activities. This will handle the common actions of multi
59 * select and common launching of intents.
60 */
61public abstract class AbstractConversationListActivity  extends BugleActionBarActivity
62    implements ConversationListFragmentHost, MultiSelectActionModeCallback.Listener {
63
64    private static final int REQUEST_SET_DEFAULT_SMS_APP = 1;
65
66    protected ConversationListFragment mConversationListFragment;
67
68    @Override
69    public void onAttachFragment(final Fragment fragment) {
70        Trace.beginSection("AbstractConversationListActivity.onAttachFragment");
71        // Fragment could be debug dialog
72        if (fragment instanceof ConversationListFragment) {
73            mConversationListFragment = (ConversationListFragment) fragment;
74            mConversationListFragment.setHost(this);
75        }
76        Trace.endSection();
77    }
78
79    @Override
80    public void onBackPressed() {
81        // If action mode is active dismiss it
82        if (getActionMode() != null) {
83            dismissActionMode();
84            return;
85        }
86        super.onBackPressed();
87    }
88
89    protected void startMultiSelectActionMode() {
90        startActionMode(new MultiSelectActionModeCallback(this));
91    }
92
93    protected void exitMultiSelectState() {
94        mConversationListFragment.showFab();
95        dismissActionMode();
96        mConversationListFragment.updateUi();
97    }
98
99    protected boolean isInConversationListSelectMode() {
100        return getActionModeCallback() instanceof MultiSelectActionModeCallback;
101    }
102
103    @Override
104    public boolean isSelectionMode() {
105        return isInConversationListSelectMode();
106    }
107
108    @Override
109    public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
110    }
111
112    @Override
113    public void onActionBarDelete(final Collection<SelectedConversation> conversations) {
114        if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
115            // TODO: figure out a good way to combine this with the implementation in
116            // ConversationFragment doing similar things
117            final Activity activity = this;
118            UiUtils.showSnackBarWithCustomAction(this,
119                    getWindow().getDecorView().getRootView(),
120                    getString(R.string.requires_default_sms_app),
121                    SnackBar.Action.createCustomAction(new Runnable() {
122                            @Override
123                            public void run() {
124                                final Intent intent =
125                                        UIIntents.get().getChangeDefaultSmsAppIntent(activity);
126                                startActivityForResult(intent, REQUEST_SET_DEFAULT_SMS_APP);
127                            }
128                        },
129                        getString(R.string.requires_default_sms_change_button)),
130                    null /* interactions */,
131                    null /* placement */);
132            return;
133        }
134
135        new AlertDialog.Builder(this)
136                .setTitle(getResources().getQuantityString(
137                        R.plurals.delete_conversations_confirmation_dialog_title,
138                        conversations.size()))
139                .setPositiveButton(R.string.delete_conversation_confirmation_button,
140                        new DialogInterface.OnClickListener() {
141                            @Override
142                            public void onClick(final DialogInterface dialog,
143                                    final int button) {
144                                for (final SelectedConversation conversation : conversations) {
145                                    DeleteConversationAction.deleteConversation(
146                                            conversation.conversationId,
147                                            conversation.timestamp);
148                                }
149                                exitMultiSelectState();
150                            }
151                })
152                .setNegativeButton(R.string.delete_conversation_decline_button, null)
153                .show();
154    }
155
156    @Override
157    public void onActionBarArchive(final Iterable<SelectedConversation> conversations,
158            final boolean isToArchive) {
159        final ArrayList<String> conversationIds = new ArrayList<String>();
160        for (final SelectedConversation conversation : conversations) {
161            final String conversationId = conversation.conversationId;
162            conversationIds.add(conversationId);
163            if (isToArchive) {
164                UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
165            } else {
166                UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
167            }
168        }
169
170        final Runnable undoRunnable = new Runnable() {
171            @Override
172            public void run() {
173                for (final String conversationId : conversationIds) {
174                    if (isToArchive) {
175                        UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
176                    } else {
177                        UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
178                    }
179                }
180            }
181        };
182
183        final int textId =
184                isToArchive ? R.string.archived_toast_message : R.string.unarchived_toast_message;
185        final String message = getResources().getString(textId, conversationIds.size());
186        UiUtils.showSnackBar(this, findViewById(android.R.id.list), message, undoRunnable,
187                SnackBar.Action.SNACK_BAR_UNDO,
188                mConversationListFragment.getSnackBarInteractions());
189        exitMultiSelectState();
190    }
191
192    @Override
193    public void onActionBarNotification(final Iterable<SelectedConversation> conversations,
194            final boolean isNotificationOn) {
195        for (final SelectedConversation conversation : conversations) {
196            UpdateConversationOptionsAction.enableConversationNotifications(
197                    conversation.conversationId, isNotificationOn);
198        }
199
200        final int textId = isNotificationOn ?
201                R.string.notification_on_toast_message : R.string.notification_off_toast_message;
202        final String message = getResources().getString(textId, 1);
203        UiUtils.showSnackBar(this, findViewById(android.R.id.list), message,
204            null /* undoRunnable */,
205            SnackBar.Action.SNACK_BAR_UNDO, mConversationListFragment.getSnackBarInteractions());
206        exitMultiSelectState();
207    }
208
209    @Override
210    public void onActionBarAddContact(final SelectedConversation conversation) {
211        final Uri avatarUri;
212        if (conversation.icon != null) {
213            avatarUri = Uri.parse(conversation.icon);
214        } else {
215            avatarUri = null;
216        }
217        final AddContactsConfirmationDialog dialog = new AddContactsConfirmationDialog(
218                this, avatarUri, conversation.otherParticipantNormalizedDestination);
219        dialog.show();
220        exitMultiSelectState();
221    }
222
223    @Override
224    public void onActionBarBlock(final SelectedConversation conversation) {
225        final Resources res = getResources();
226        new AlertDialog.Builder(this)
227                .setTitle(res.getString(R.string.block_confirmation_title,
228                        conversation.otherParticipantNormalizedDestination))
229                .setMessage(res.getString(R.string.block_confirmation_message))
230                .setNegativeButton(android.R.string.cancel, null)
231                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
232                    @Override
233                    public void onClick(final DialogInterface arg0, final int arg1) {
234                        final Context context = AbstractConversationListActivity.this;
235                        final View listView = findViewById(android.R.id.list);
236                        final List<SnackBarInteraction> interactions =
237                                mConversationListFragment.getSnackBarInteractions();
238                        final UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener
239                                undoListener =
240                                        new UpdateDestinationBlockedActionSnackBar(
241                                                context, listView, null /* undoRunnable */,
242                                                interactions);
243                        final Runnable undoRunnable = new Runnable() {
244                            @Override
245                            public void run() {
246                                UpdateDestinationBlockedAction.updateDestinationBlocked(
247                                        conversation.otherParticipantNormalizedDestination, false,
248                                        conversation.conversationId,
249                                        undoListener);
250                            }
251                        };
252                        final UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener
253                              listener = new UpdateDestinationBlockedActionSnackBar(
254                                      context, listView, undoRunnable, interactions);
255                        UpdateDestinationBlockedAction.updateDestinationBlocked(
256                                conversation.otherParticipantNormalizedDestination, true,
257                                conversation.conversationId,
258                                listener);
259                        exitMultiSelectState();
260                    }
261                })
262                .create()
263                .show();
264    }
265
266    @Override
267    public void onConversationClick(final ConversationListData listData,
268                                    final ConversationListItemData conversationListItemData,
269                                    final boolean isLongClick,
270                                    final ConversationListItemView conversationView) {
271        if (isLongClick && !isInConversationListSelectMode()) {
272            startMultiSelectActionMode();
273        }
274
275        if (isInConversationListSelectMode()) {
276            final MultiSelectActionModeCallback multiSelectActionMode =
277                    (MultiSelectActionModeCallback) getActionModeCallback();
278            multiSelectActionMode.toggleSelect(listData, conversationListItemData);
279            mConversationListFragment.updateUi();
280        } else {
281            final String conversationId = conversationListItemData.getConversationId();
282            Bundle sceneTransitionAnimationOptions = null;
283            boolean hasCustomTransitions = false;
284
285            UIIntents.get().launchConversationActivity(
286                    this, conversationId, null,
287                    sceneTransitionAnimationOptions,
288                    hasCustomTransitions);
289        }
290    }
291
292    @Override
293    public void onCreateConversationClick() {
294        UIIntents.get().launchCreateNewConversationActivity(this, null);
295    }
296
297
298    @Override
299    public boolean isConversationSelected(final String conversationId) {
300        return isInConversationListSelectMode() &&
301                ((MultiSelectActionModeCallback) getActionModeCallback()).isSelected(
302                        conversationId);
303    }
304
305    public void onActionBarDebug() {
306        DebugUtils.showDebugOptions(this);
307    }
308
309    private static class UpdateDestinationBlockedActionSnackBar
310            implements UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener {
311        private final Context mContext;
312        private final View mParentView;
313        private final Runnable mUndoRunnable;
314        private final List<SnackBarInteraction> mInteractions;
315
316        UpdateDestinationBlockedActionSnackBar(final Context context,
317                @NonNull final View parentView, @Nullable final Runnable undoRunnable,
318                @Nullable List<SnackBarInteraction> interactions) {
319            mContext = context;
320            mParentView = parentView;
321            mUndoRunnable = undoRunnable;
322            mInteractions = interactions;
323        }
324
325        @Override
326        public void onUpdateDestinationBlockedAction(
327            final UpdateDestinationBlockedAction action,
328            final boolean success, final boolean block,
329            final String destination) {
330            if (success) {
331                final int messageId = block ? R.string.blocked_toast_message
332                        : R.string.unblocked_toast_message;
333                final String message = mContext.getResources().getString(messageId, 1);
334                UiUtils.showSnackBar(mContext, mParentView, message, mUndoRunnable,
335                        SnackBar.Action.SNACK_BAR_UNDO, mInteractions);
336            }
337        }
338    }
339}
340