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