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  */
16 package com.android.messaging.ui.conversationsettings;
17 
18 import android.app.Activity;
19 import android.app.AlertDialog;
20 import android.app.Fragment;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.Intent;
24 import android.content.res.Resources;
25 import android.database.Cursor;
26 import android.media.RingtoneManager;
27 import android.os.Bundle;
28 import android.os.Parcelable;
29 import android.provider.Settings;
30 import android.view.LayoutInflater;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.widget.ArrayAdapter;
34 import android.widget.BaseAdapter;
35 import android.widget.ListView;
36 import android.widget.TextView;
37 
38 import com.android.messaging.R;
39 import com.android.messaging.datamodel.DataModel;
40 import com.android.messaging.datamodel.binding.Binding;
41 import com.android.messaging.datamodel.binding.BindingBase;
42 import com.android.messaging.datamodel.data.ParticipantData;
43 import com.android.messaging.datamodel.data.ParticipantListItemData;
44 import com.android.messaging.datamodel.data.PeopleAndOptionsData;
45 import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener;
46 import com.android.messaging.datamodel.data.PeopleOptionsItemData;
47 import com.android.messaging.datamodel.data.PersonItemData;
48 import com.android.messaging.ui.CompositeAdapter;
49 import com.android.messaging.ui.PersonItemView;
50 import com.android.messaging.ui.UIIntents;
51 import com.android.messaging.ui.conversation.ConversationActivity;
52 import com.android.messaging.util.Assert;
53 
54 import java.util.ArrayList;
55 import java.util.List;
56 
57 /**
58  * Shows a list of participants of a conversation and displays options.
59  */
60 public class PeopleAndOptionsFragment extends Fragment
61         implements PeopleAndOptionsDataListener, PeopleOptionsItemView.HostInterface {
62     private ListView mListView;
63     private OptionsListAdapter mOptionsListAdapter;
64     private PeopleListAdapter mPeopleListAdapter;
65     private final Binding<PeopleAndOptionsData> mBinding =
66             BindingBase.createBinding(this);
67 
68     private static final int REQUEST_CODE_RINGTONE_PICKER = 1000;
69 
70     @Override
onCreate(final Bundle savedInstanceState)71     public void onCreate(final Bundle savedInstanceState) {
72         super.onCreate(savedInstanceState);
73         mBinding.getData().init(getLoaderManager(), mBinding);
74     }
75 
76     @Override
onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState)77     public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
78             final Bundle savedInstanceState) {
79         final View view = inflater.inflate(R.layout.people_and_options_fragment, container, false);
80         mListView = (ListView) view.findViewById(android.R.id.list);
81         mPeopleListAdapter = new PeopleListAdapter(getActivity());
82         mOptionsListAdapter = new OptionsListAdapter();
83         final CompositeAdapter compositeAdapter = new CompositeAdapter(getActivity());
84         compositeAdapter.addPartition(new PeopleAndOptionsPartition(mOptionsListAdapter,
85                 R.string.general_settings_title, false));
86         compositeAdapter.addPartition(new PeopleAndOptionsPartition(mPeopleListAdapter,
87                 R.string.participant_list_title, true));
88         mListView.setAdapter(compositeAdapter);
89         return view;
90     }
91 
92     @Override
onActivityResult(final int requestCode, final int resultCode, final Intent data)93     public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
94         super.onActivityResult(requestCode, resultCode, data);
95         if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_RINGTONE_PICKER) {
96             final Parcelable pick = data.getParcelableExtra(
97                     RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
98             final String pickedUri = pick == null ? "" : pick.toString();
99             mBinding.getData().setConversationNotificationSound(mBinding, pickedUri);
100         }
101     }
102 
103     @Override
onDestroy()104     public void onDestroy() {
105         super.onDestroy();
106         mBinding.unbind();
107     }
108 
setConversationId(final String conversationId)109     public void setConversationId(final String conversationId) {
110         Assert.isTrue(getView() == null);
111         Assert.notNull(conversationId);
112         mBinding.bind(DataModel.get().createPeopleAndOptionsData(conversationId, getActivity(),
113                 this));
114     }
115 
116     @Override
onOptionsCursorUpdated(final PeopleAndOptionsData data, final Cursor cursor)117     public void onOptionsCursorUpdated(final PeopleAndOptionsData data, final Cursor cursor) {
118         Assert.isTrue(cursor == null || cursor.getCount() == 1);
119         mBinding.ensureBound(data);
120         mOptionsListAdapter.swapCursor(cursor);
121     }
122 
123     @Override
onParticipantsListLoaded(final PeopleAndOptionsData data, final List<ParticipantData> participants)124     public void onParticipantsListLoaded(final PeopleAndOptionsData data,
125             final List<ParticipantData> participants) {
126         mBinding.ensureBound(data);
127         mPeopleListAdapter.updateParticipants(participants);
128         final ParticipantData otherParticipant = participants.size() == 1 ?
129                 participants.get(0) : null;
130         mOptionsListAdapter.setOtherParticipant(otherParticipant);
131     }
132 
133     @Override
onOptionsItemViewClicked(final PeopleOptionsItemData item, final boolean isChecked)134     public void onOptionsItemViewClicked(final PeopleOptionsItemData item,
135             final boolean isChecked) {
136         switch (item.getItemId()) {
137             case PeopleOptionsItemData.SETTING_NOTIFICATION_ENABLED:
138                 mBinding.getData().enableConversationNotifications(mBinding, isChecked);
139                 break;
140 
141             case PeopleOptionsItemData.SETTING_NOTIFICATION_SOUND_URI:
142                 final Intent ringtonePickerIntent = UIIntents.get().getRingtonePickerIntent(
143                         getString(R.string.notification_sound_pref_title),
144                         item.getRingtoneUri(), Settings.System.DEFAULT_NOTIFICATION_URI,
145                         RingtoneManager.TYPE_NOTIFICATION);
146                 startActivityForResult(ringtonePickerIntent, REQUEST_CODE_RINGTONE_PICKER);
147                 break;
148 
149             case PeopleOptionsItemData.SETTING_NOTIFICATION_VIBRATION:
150                 mBinding.getData().enableConversationNotificationVibration(mBinding,
151                         isChecked);
152                 break;
153 
154             case PeopleOptionsItemData.SETTING_BLOCKED:
155                 if (item.getOtherParticipant().isBlocked()) {
156                     mBinding.getData().setDestinationBlocked(mBinding, false);
157                     break;
158                 }
159                 final Resources res = getResources();
160                 final Activity activity = getActivity();
161                 new AlertDialog.Builder(activity)
162                         .setTitle(res.getString(R.string.block_confirmation_title,
163                                 item.getOtherParticipant().getDisplayDestination()))
164                         .setMessage(res.getString(R.string.block_confirmation_message))
165                         .setNegativeButton(android.R.string.cancel, null)
166                         .setPositiveButton(android.R.string.ok,
167                                 new DialogInterface.OnClickListener() {
168                             @Override
169                             public void onClick(DialogInterface arg0, int arg1) {
170                                 mBinding.getData().setDestinationBlocked(mBinding, true);
171                                 activity.setResult(ConversationActivity.FINISH_RESULT_CODE);
172                                 activity.finish();
173                             }
174                         })
175                         .create()
176                         .show();
177                 break;
178         }
179     }
180 
181     /**
182      * A simple adapter that takes a conversation metadata cursor and binds
183      * PeopleAndOptionsItemViews to individual COLUMNS of the first cursor record. (Note
184      * that this is not a CursorAdapter because it treats individual columns of the cursor as
185      * separate options to display for the conversation, e.g. notification settings).
186      */
187     private class OptionsListAdapter extends BaseAdapter {
188         private Cursor mOptionsCursor;
189         private ParticipantData mOtherParticipantData;
190 
swapCursor(final Cursor newCursor)191         public Cursor swapCursor(final Cursor newCursor) {
192             final Cursor oldCursor = mOptionsCursor;
193             if (newCursor != oldCursor) {
194                 mOptionsCursor = newCursor;
195                 notifyDataSetChanged();
196             }
197             return oldCursor;
198         }
199 
setOtherParticipant(final ParticipantData participantData)200         public void setOtherParticipant(final ParticipantData participantData) {
201             if (mOtherParticipantData != participantData) {
202                 mOtherParticipantData = participantData;
203                 notifyDataSetChanged();
204             }
205         }
206 
207         @Override
getCount()208         public int getCount() {
209             int count = PeopleOptionsItemData.SETTINGS_COUNT;
210             if (mOtherParticipantData == null) {
211                 count--;
212             }
213             return mOptionsCursor == null ? 0 : count;
214         }
215 
216         @Override
getItem(final int position)217         public Object getItem(final int position) {
218             return null;
219         }
220 
221         @Override
getItemId(final int position)222         public long getItemId(final int position) {
223             return 0;
224         }
225 
226         @Override
getView(final int position, final View convertView, final ViewGroup parent)227         public View getView(final int position, final View convertView, final ViewGroup parent) {
228             final PeopleOptionsItemView itemView;
229             if (convertView != null && convertView instanceof PeopleOptionsItemView) {
230                 itemView = (PeopleOptionsItemView) convertView;
231             } else {
232                 final LayoutInflater inflater = (LayoutInflater) getActivity()
233                         .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
234                 itemView = (PeopleOptionsItemView)
235                         inflater.inflate(R.layout.people_options_item_view, parent, false);
236             }
237             mOptionsCursor.moveToFirst();
238             itemView.bind(mOptionsCursor, position, mOtherParticipantData,
239                     PeopleAndOptionsFragment.this);
240             return itemView;
241         }
242     }
243 
244     /**
245      * An adapter that takes a list of ParticipantData and displays them as a list of
246      * ParticipantListItemViews.
247      */
248     private class PeopleListAdapter extends ArrayAdapter<ParticipantData> {
PeopleListAdapter(final Context context)249         public PeopleListAdapter(final Context context) {
250             super(context, R.layout.people_list_item_view, new ArrayList<ParticipantData>());
251         }
252 
updateParticipants(final List<ParticipantData> newList)253         public void updateParticipants(final List<ParticipantData> newList) {
254             clear();
255             addAll(newList);
256             notifyDataSetChanged();
257         }
258 
259         @Override
getView(final int position, final View convertView, final ViewGroup parent)260         public View getView(final int position, final View convertView, final ViewGroup parent) {
261             PersonItemView itemView;
262             final ParticipantData item = getItem(position);
263             if (convertView != null && convertView instanceof PersonItemView) {
264                 itemView = (PersonItemView) convertView;
265             } else {
266                 final LayoutInflater inflater = (LayoutInflater) getContext()
267                         .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
268                 itemView = (PersonItemView) inflater.inflate(R.layout.people_list_item_view, parent,
269                         false);
270             }
271             final ParticipantListItemData itemData =
272                     DataModel.get().createParticipantListItemData(item);
273             itemView.bind(itemData);
274 
275             // Any click on the row should have the same effect as clicking the avatar icon
276             final PersonItemView itemViewClosure = itemView;
277             itemView.setListener(new PersonItemView.PersonItemViewListener() {
278                 @Override
279                 public void onPersonClicked(final PersonItemData data) {
280                     itemViewClosure.performClickOnAvatar();
281                 }
282 
283                 @Override
284                 public boolean onPersonLongClicked(final PersonItemData data) {
285                     if (mBinding.isBound()) {
286                         final CopyContactDetailDialog dialog = new CopyContactDetailDialog(
287                                 getContext(), data.getDetails());
288                         dialog.show();
289                         return true;
290                     }
291                     return false;
292                 }
293             });
294             return itemView;
295         }
296     }
297 
298     /**
299      * Represents a partition/section in the People & Options list (e.g. "general options" and
300      * "people in this conversation" sections).
301      */
302     private class PeopleAndOptionsPartition extends CompositeAdapter.Partition {
303         private final int mHeaderResId;
304         private final boolean mNeedDivider;
305 
PeopleAndOptionsPartition(final BaseAdapter adapter, final int headerResId, final boolean needDivider)306         public PeopleAndOptionsPartition(final BaseAdapter adapter, final int headerResId,
307                 final boolean needDivider) {
308             super(true /* showIfEmpty */, true /* hasHeader */, adapter);
309             mHeaderResId = headerResId;
310             mNeedDivider = needDivider;
311         }
312 
313         @Override
getHeaderView(final View convertView, final ViewGroup parentView)314         public View getHeaderView(final View convertView, final ViewGroup parentView) {
315             View view = null;
316             if (convertView != null && convertView.getId() == R.id.people_and_options_header) {
317                 view = convertView;
318             } else {
319                 view = LayoutInflater.from(getActivity()).inflate(
320                         R.layout.people_and_options_section_header, parentView, false);
321             }
322             final TextView text = (TextView) view.findViewById(R.id.header_text);
323             final View divider = view.findViewById(R.id.divider);
324             text.setText(mHeaderResId);
325             divider.setVisibility(mNeedDivider ? View.VISIBLE : View.GONE);
326             return view;
327         }
328     }
329 }
330