blob: 80d184bdee9c0e2d64c7ac1c4cdc20af7599d279 [file] [log] [blame]
Walter Jang44e91b12016-05-22 12:37:00 -07001/*
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, softwareateCre
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.contacts.group;
17
Walter Jang44e91b12016-05-22 12:37:00 -070018import android.app.Dialog;
19import android.app.DialogFragment;
Marcus Hagerottbc66bba2016-08-01 17:31:38 -070020import android.app.LoaderManager;
Walter Jang72f99882016-05-26 09:01:31 -070021import android.content.Context;
Marcus Hagerottbc66bba2016-08-01 17:31:38 -070022import android.content.CursorLoader;
Walter Jang44e91b12016-05-22 12:37:00 -070023import android.content.DialogInterface;
24import android.content.DialogInterface.OnClickListener;
Marcus Hagerottbc66bba2016-08-01 17:31:38 -070025import android.content.Intent;
26import android.content.Loader;
27import android.database.Cursor;
Walter Jang44e91b12016-05-22 12:37:00 -070028import android.os.Bundle;
Marcus Hagerott2d97b5f2016-08-10 15:28:30 -070029import android.provider.ContactsContract.Groups;
Marcus Hagerottbc66bba2016-08-01 17:31:38 -070030import android.support.design.widget.TextInputLayout;
Wenyi Wang2c0790a2016-06-14 14:08:29 -070031import android.support.v7.app.AlertDialog;
Walter Jang44e91b12016-05-22 12:37:00 -070032import android.text.Editable;
33import android.text.TextUtils;
34import android.text.TextWatcher;
Walter Jang72f99882016-05-26 09:01:31 -070035import android.view.View;
Walter Jang76931b42016-07-09 14:59:16 -070036import android.view.WindowManager;
Walter Jang72f99882016-05-26 09:01:31 -070037import android.view.inputmethod.InputMethodManager;
Walter Jang44e91b12016-05-22 12:37:00 -070038import android.widget.Button;
39import android.widget.EditText;
guanxiongliu4e69d9c2016-08-01 14:14:11 -070040import android.widget.TextView;
Walter Jang44e91b12016-05-22 12:37:00 -070041
Marcus Hagerottbc66bba2016-08-01 17:31:38 -070042import com.android.contacts.ContactSaveService;
Walter Jang44e91b12016-05-22 12:37:00 -070043import com.android.contacts.R;
Gary Mai69c182a2016-12-05 13:07:03 -080044import com.android.contacts.model.account.AccountWithDataSet;
Gary Mai0a49afa2016-12-05 15:53:58 -080045
Marcus Hagerottbc66bba2016-08-01 17:31:38 -070046import com.google.common.base.Strings;
47
48import java.util.Collections;
49import java.util.HashSet;
50import java.util.Set;
Walter Jang44e91b12016-05-22 12:37:00 -070051
52/**
53 * Edits the name of a group.
54 */
Marcus Hagerottbc66bba2016-08-01 17:31:38 -070055public final class GroupNameEditDialogFragment extends DialogFragment implements
56 LoaderManager.LoaderCallbacks<Cursor> {
Walter Jang44e91b12016-05-22 12:37:00 -070057
Walter Jang44e91b12016-05-22 12:37:00 -070058 private static final String KEY_GROUP_NAME = "groupName";
59
60 private static final String ARG_IS_INSERT = "isInsert";
61 private static final String ARG_GROUP_NAME = "groupName";
Marcus Hagerottbc66bba2016-08-01 17:31:38 -070062 private static final String ARG_ACCOUNT = "account";
63 private static final String ARG_CALLBACK_ACTION = "callbackAction";
64 private static final String ARG_GROUP_ID = "groupId";
65
66 private static final long NO_GROUP_ID = -1;
67
Walter Jang44e91b12016-05-22 12:37:00 -070068
69 /** Callbacks for hosts of the {@link GroupNameEditDialogFragment}. */
70 public interface Listener {
Walter Jang44e91b12016-05-22 12:37:00 -070071 void onGroupNameEditCancelled();
James Laskeyb2d2e422016-11-17 15:30:41 -080072 void onGroupNameEditCompleted(String name);
Marcus Hagerottbc66bba2016-08-01 17:31:38 -070073
74 public static final Listener None = new Listener() {
75 @Override
James Laskeyb2d2e422016-11-17 15:30:41 -080076 public void onGroupNameEditCancelled() { }
Marcus Hagerottbc66bba2016-08-01 17:31:38 -070077
78 @Override
James Laskeyb2d2e422016-11-17 15:30:41 -080079 public void onGroupNameEditCompleted(String name) { }
Marcus Hagerottbc66bba2016-08-01 17:31:38 -070080 };
Walter Jang44e91b12016-05-22 12:37:00 -070081 }
82
83 private boolean mIsInsert;
84 private String mGroupName;
Marcus Hagerottbc66bba2016-08-01 17:31:38 -070085 private long mGroupId;
86 private Listener mListener;
87 private AccountWithDataSet mAccount;
Walter Jang44e91b12016-05-22 12:37:00 -070088 private EditText mGroupNameEditText;
Marcus Hagerottbc66bba2016-08-01 17:31:38 -070089 private TextInputLayout mGroupNameTextLayout;
90 private Set<String> mExistingGroups = Collections.emptySet();
Walter Jang44e91b12016-05-22 12:37:00 -070091
Marcus Hagerottbc66bba2016-08-01 17:31:38 -070092 public static GroupNameEditDialogFragment newInstanceForCreation(
93 AccountWithDataSet account, String callbackAction) {
94 return newInstance(account, callbackAction, NO_GROUP_ID, null);
Walter Jang44e91b12016-05-22 12:37:00 -070095 }
96
Marcus Hagerottbc66bba2016-08-01 17:31:38 -070097 public static GroupNameEditDialogFragment newInstanceForUpdate(
98 AccountWithDataSet account, String callbackAction, long groupId, String groupName) {
99 return newInstance(account, callbackAction, groupId, groupName);
Walter Jang44e91b12016-05-22 12:37:00 -0700100 }
101
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700102 private static GroupNameEditDialogFragment newInstance(
103 AccountWithDataSet account, String callbackAction, long groupId, String groupName) {
104 if (account == null || account.name == null || account.type == null) {
105 throw new IllegalArgumentException("Invalid account");
106 }
107 final boolean isInsert = groupId == NO_GROUP_ID;
Walter Jang44e91b12016-05-22 12:37:00 -0700108 final Bundle args = new Bundle();
109 args.putBoolean(ARG_IS_INSERT, isInsert);
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700110 args.putLong(ARG_GROUP_ID, groupId);
Walter Jang44e91b12016-05-22 12:37:00 -0700111 args.putString(ARG_GROUP_NAME, groupName);
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700112 args.putParcelable(ARG_ACCOUNT, account);
113 args.putString(ARG_CALLBACK_ACTION, callbackAction);
Walter Jang44e91b12016-05-22 12:37:00 -0700114
115 final GroupNameEditDialogFragment dialog = new GroupNameEditDialogFragment();
116 dialog.setArguments(args);
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700117 return dialog;
Walter Jang44e91b12016-05-22 12:37:00 -0700118 }
119
120 @Override
121 public void onCreate(Bundle savedInstanceState) {
122 super.onCreate(savedInstanceState);
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700123 setStyle(STYLE_NORMAL, R.style.ContactsAlertDialogThemeAppCompat);
124 final Bundle args = getArguments();
Walter Jang44e91b12016-05-22 12:37:00 -0700125 if (savedInstanceState == null) {
Walter Jang44e91b12016-05-22 12:37:00 -0700126 mGroupName = args.getString(KEY_GROUP_NAME);
127 } else {
Walter Jang44e91b12016-05-22 12:37:00 -0700128 mGroupName = savedInstanceState.getString(ARG_GROUP_NAME);
129 }
James Laskeyb2d2e422016-11-17 15:30:41 -0800130
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700131 mGroupId = args.getLong(ARG_GROUP_ID, NO_GROUP_ID);
132 mIsInsert = args.getBoolean(ARG_IS_INSERT, true);
133 mAccount = getArguments().getParcelable(ARG_ACCOUNT);
134
135 // There is only one loader so the id arg doesn't matter.
136 getLoaderManager().initLoader(0, null, this);
Walter Jang44e91b12016-05-22 12:37:00 -0700137 }
138
139 @Override
140 public Dialog onCreateDialog(Bundle savedInstanceState) {
141 // Build a dialog with two buttons and a view of a single EditText input field
guanxiongliu4e69d9c2016-08-01 14:14:11 -0700142 final TextView title = (TextView) View.inflate(getActivity(), R.layout.dialog_title, null);
143 title.setText(mIsInsert
144 ? R.string.group_name_dialog_insert_title
145 : R.string.group_name_dialog_update_title);
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700146 final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), getTheme())
guanxiongliu4e69d9c2016-08-01 14:14:11 -0700147 .setCustomTitle(title)
Walter Jang44e91b12016-05-22 12:37:00 -0700148 .setView(R.layout.group_name_edit_dialog)
149 .setNegativeButton(android.R.string.cancel, new OnClickListener() {
150 @Override
151 public void onClick(DialogInterface dialog, int which) {
Walter Jang76931b42016-07-09 14:59:16 -0700152 hideInputMethod();
Walter Jang44e91b12016-05-22 12:37:00 -0700153 getListener().onGroupNameEditCancelled();
154 dismiss();
155 }
156 })
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700157 // The Positive button listener is defined below in the OnShowListener to
158 // allow for input validation
159 .setPositiveButton(android.R.string.ok, null);
Walter Jang44e91b12016-05-22 12:37:00 -0700160
161 // Disable the create button when the name is empty
162 final AlertDialog alertDialog = builder.create();
Walter Jang76931b42016-07-09 14:59:16 -0700163 alertDialog.getWindow().setSoftInputMode(
164 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
Walter Jang44e91b12016-05-22 12:37:00 -0700165 alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
166 @Override
167 public void onShow(DialogInterface dialog) {
168 mGroupNameEditText = (EditText) alertDialog.findViewById(android.R.id.text1);
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700169 mGroupNameTextLayout =
170 (TextInputLayout) alertDialog.findViewById(R.id.text_input_layout);
Walter Jang44e91b12016-05-22 12:37:00 -0700171 if (!TextUtils.isEmpty(mGroupName)) {
172 mGroupNameEditText.setText(mGroupName);
Walter Jangf1bb74d2016-06-23 12:43:26 -0700173 // Guard against already created group names that are longer than the max
174 final int maxLength = getResources().getInteger(
175 R.integer.group_name_max_length);
176 mGroupNameEditText.setSelection(
177 mGroupName.length() > maxLength ? maxLength : mGroupName.length());
Walter Jang44e91b12016-05-22 12:37:00 -0700178 }
Walter Jang76931b42016-07-09 14:59:16 -0700179 showInputMethod(mGroupNameEditText);
Walter Jang44e91b12016-05-22 12:37:00 -0700180
181 final Button createButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
182 createButton.setEnabled(!TextUtils.isEmpty(getGroupName()));
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700183
184 // Override the click listener to prevent dismissal if creating a duplicate group.
185 createButton.setOnClickListener(new View.OnClickListener() {
186 @Override
187 public void onClick(View v) {
188 maybePersistCurrentGroupName(v);
189 }
190 });
Walter Jang44e91b12016-05-22 12:37:00 -0700191 mGroupNameEditText.addTextChangedListener(new TextWatcher() {
192 @Override
193 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
194 }
195
196 @Override
197 public void onTextChanged(CharSequence s, int start, int before, int count) {
198 }
199
200 @Override
201 public void afterTextChanged(Editable s) {
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700202 mGroupNameTextLayout.setError(null);
Walter Jang44e91b12016-05-22 12:37:00 -0700203 createButton.setEnabled(!TextUtils.isEmpty(s));
204 }
205 });
206 }
207 });
Walter Jang76931b42016-07-09 14:59:16 -0700208
Walter Jang44e91b12016-05-22 12:37:00 -0700209 return alertDialog;
210 }
211
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700212 /**
213 * Sets the listener for the rename
214 *
215 * Setting a listener on a fragment is error prone since it will be lost if the fragment
216 * is recreated. This exists because it is used from a view class (GroupMembersView) which
217 * needs to modify it's state when this fragment updates the name.
218 *
219 * @param listener the listener. can be null
220 */
221 public void setListener(Listener listener) {
222 mListener = listener;
223 }
224
225 private boolean hasNameChanged() {
226 final String name = Strings.nullToEmpty(getGroupName());
227 final String originalName = getArguments().getString(ARG_GROUP_NAME);
228 return (mIsInsert && !name.isEmpty()) || !name.equals(originalName);
229 }
230
231 private void maybePersistCurrentGroupName(View button) {
232 if (!hasNameChanged()) {
233 dismiss();
234 return;
235 }
236 final String name = getGroupName();
237 // Note we don't check if the loader finished populating mExistingGroups. It's not the
238 // end of the world if the user ends up with a duplicate group and in practice it should
239 // never really happen (the query should complete much sooner than the user can edit the
240 // label)
241 if (mExistingGroups.contains(name)) {
242 mGroupNameTextLayout.setError(
243 getString(R.string.groupExistsErrorMessage));
244 button.setEnabled(false);
245 return;
246 }
247 final String callbackAction = getArguments().getString(ARG_CALLBACK_ACTION);
248 final Intent serviceIntent;
249 if (mIsInsert) {
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700250 serviceIntent = ContactSaveService.createNewGroupIntent(getActivity(), mAccount,
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700251 name, null, getActivity().getClass(), callbackAction);
252 } else {
253 serviceIntent = ContactSaveService.createGroupRenameIntent(getActivity(), mGroupId,
254 name, getActivity().getClass(), callbackAction);
255 }
256 ContactSaveService.startService(getActivity(), serviceIntent);
James Laskeyb2d2e422016-11-17 15:30:41 -0800257 getListener().onGroupNameEditCompleted(mGroupName);
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700258 dismiss();
259 }
260
Walter Jang44e91b12016-05-22 12:37:00 -0700261 @Override
262 public void onCancel(DialogInterface dialog) {
263 super.onCancel(dialog);
264 getListener().onGroupNameEditCancelled();
265 }
266
267 @Override
268 public void onSaveInstanceState(Bundle outState) {
269 super.onSaveInstanceState(outState);
Walter Jang44e91b12016-05-22 12:37:00 -0700270 outState.putString(KEY_GROUP_NAME, getGroupName());
271 }
272
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700273 @Override
274 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
275 // Only a single loader so id is ignored.
Marcus Hagerott2d97b5f2016-08-10 15:28:30 -0700276 return new CursorLoader(getActivity(), Groups.CONTENT_SUMMARY_URI,
277 new String[] { Groups.TITLE, Groups.SYSTEM_ID, Groups.ACCOUNT_TYPE,
278 Groups.SUMMARY_COUNT, Groups.GROUP_IS_READ_ONLY},
279 getSelection(), getSelectionArgs(), null);
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700280 }
281
282 @Override
283 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
284 mExistingGroups = new HashSet<>();
Marcus Hagerott2d97b5f2016-08-10 15:28:30 -0700285 final GroupUtil.GroupsProjection projection = new GroupUtil.GroupsProjection(data);
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700286 while (data.moveToNext()) {
Marcus Hagerott2d97b5f2016-08-10 15:28:30 -0700287 final String title = projection.getTitle(data);
288 // Empty system groups aren't shown in the nav drawer so it would be confusing to tell
289 // the user that they already exist. Instead we allow them to create a duplicate
290 // group in this case. This is how the web handles this case as well (it creates a
291 // new non-system group if a new group with a title that matches a system group is
292 // create).
293 if (projection.isEmptyFFCGroup(data)) {
294 continue;
295 }
296 mExistingGroups.add(title);
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700297 }
298 }
299
300 @Override
301 public void onLoaderReset(Loader<Cursor> loader) {
302 }
303
Walter Jang72f99882016-05-26 09:01:31 -0700304 private void showInputMethod(View view) {
305 final InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(
306 Context.INPUT_METHOD_SERVICE);
307 if (imm != null) {
308 imm.showSoftInput(view, /* flags */ 0);
309 }
310 }
311
Walter Jang76931b42016-07-09 14:59:16 -0700312 private void hideInputMethod() {
313 final InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(
314 Context.INPUT_METHOD_SERVICE);
315 if (imm != null && mGroupNameEditText != null) {
316 imm.hideSoftInputFromWindow(mGroupNameEditText.getWindowToken(), /* flags */ 0);
317 }
318 }
319
Walter Jang44e91b12016-05-22 12:37:00 -0700320 private Listener getListener() {
Marcus Hagerottbc66bba2016-08-01 17:31:38 -0700321 if (mListener != null) {
322 return mListener;
323 } else if (getActivity() instanceof Listener) {
324 return (Listener) getActivity();
325 } else {
326 return Listener.None;
Walter Jang44e91b12016-05-22 12:37:00 -0700327 }
Walter Jang44e91b12016-05-22 12:37:00 -0700328 }
329
330 private String getGroupName() {
331 return mGroupNameEditText == null || mGroupNameEditText.getText() == null
332 ? null : mGroupNameEditText.getText().toString();
333 }
Marcus Hagerott2d97b5f2016-08-10 15:28:30 -0700334
335 private String getSelection() {
336 final StringBuilder builder = new StringBuilder();
337 builder.append(Groups.ACCOUNT_NAME).append("=? AND ")
338 .append(Groups.ACCOUNT_TYPE).append("=? AND ")
339 .append(Groups.DELETED).append("=?");
340 if (mAccount.dataSet != null) {
341 builder.append(" AND ").append(Groups.DATA_SET).append("=?");
342 }
343 return builder.toString();
344 }
345
346 private String[] getSelectionArgs() {
347 final int len = mAccount.dataSet == null ? 3 : 4;
348 final String[] args = new String[len];
349 args[0] = mAccount.name;
350 args[1] = mAccount.type;
351 args[2] = "0"; // Not deleted
352 if (mAccount.dataSet != null) {
353 args[3] = mAccount.dataSet;
354 }
355 return args;
356 }
Walter Jang44e91b12016-05-22 12:37:00 -0700357}