blob: ec49d48b564e4c9a8a32c8c75e85c3df67bb1bc4 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.contacts.activities;
import android.app.Dialog;
import android.app.FragmentTransaction;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract.QuickContact;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import com.android.contacts.AppCompatContactsActivity;
import com.android.contacts.ContactSaveService;
import com.android.contacts.DynamicShortcuts;
import com.android.contacts.R;
import com.android.contacts.detail.PhotoSelectionHandler;
import com.android.contacts.editor.ContactEditorFragment;
import com.android.contacts.editor.EditorIntents;
import com.android.contacts.editor.PhotoSourceDialogFragment;
import com.android.contacts.interactions.ContactDeletionInteraction;
import com.android.contacts.model.RawContactDeltaList;
import com.android.contacts.util.DialogManager;
import com.android.contacts.util.ImplicitIntentsUtil;
import java.io.FileNotFoundException;
import java.util.ArrayList;
/**
* Contact editor with only the most important fields displayed initially.
*/
public class ContactEditorActivity extends AppCompatContactsActivity implements
PhotoSourceDialogFragment.Listener,
DialogManager.DialogShowingViewActivity {
private static final String TAG = "ContactEditorActivity";
public static final String ACTION_JOIN_COMPLETED = "joinCompleted";
public static final String ACTION_SAVE_COMPLETED = "saveCompleted";
public static final int RESULT_CODE_SPLIT = 2;
// 3 used for ContactDeletionInteraction.RESULT_CODE_DELETED
public static final int RESULT_CODE_EDITED = 4;
/**
* The contact will be saved to this account when this is set for an insert. This
* is necessary because {@link android.accounts.Account} cannot be created with null values
* for the name and type and an Account is needed for
* {@link android.provider.ContactsContract.Intents.Insert#EXTRA_ACCOUNT}
*/
public static final String EXTRA_ACCOUNT_WITH_DATA_SET =
"com.android.contacts.ACCOUNT_WITH_DATA_SET";
private static final String TAG_EDITOR_FRAGMENT = "editor_fragment";
private static final String STATE_PHOTO_MODE = "photo_mode";
private static final String STATE_ACTION_BAR_TITLE = "action_bar_title";
private static final String STATE_PHOTO_URI = "photo_uri";
/**
* Boolean intent key that specifies that this activity should finish itself
* (instead of launching a new view intent) after the editor changes have been
* saved.
*/
public static final String INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED =
"finishActivityOnSaveCompleted";
/**
* Contract for contact editors Fragments that are managed by this Activity.
*/
public interface ContactEditor {
/**
* Modes that specify what the AsyncTask has to perform after saving
*/
interface SaveMode {
/**
* Close the editor after saving
*/
int CLOSE = 0;
/**
* Reload the data so that the user can continue editing
*/
int RELOAD = 1;
/**
* Split the contact after saving
*/
int SPLIT = 2;
/**
* Join another contact after saving
*/
int JOIN = 3;
/**
* Navigate to the editor view after saving.
*/
int EDITOR = 4;
}
/**
* The status of the contact editor.
*/
interface Status {
/**
* The loader is fetching data
*/
int LOADING = 0;
/**
* Not currently busy. We are waiting for the user to enter data
*/
int EDITING = 1;
/**
* The data is currently being saved. This is used to prevent more
* auto-saves (they shouldn't overlap)
*/
int SAVING = 2;
/**
* Prevents any more saves. This is used if in the following cases:
* - After Save/Close
* - After Revert
* - After the user has accepted an edit suggestion
* - After the user chooses to expand the editor
*/
int CLOSING = 3;
/**
* Prevents saving while running a child activity.
*/
int SUB_ACTIVITY = 4;
}
/**
* Sets the hosting Activity that will receive callbacks from the contact editor.
*/
void setListener(ContactEditorFragment.Listener listener);
/**
* Initialize the contact editor.
*/
void load(String action, Uri lookupUri, Bundle intentExtras);
/**
* Applies extras from the hosting Activity to the writable raw contact.
*/
void setIntentExtras(Bundle extras);
/**
* Saves or creates the contact based on the mode, and if successful
* finishes the activity.
*/
boolean save(int saveMode);
/**
* If there are no unsaved changes, just close the editor, otherwise the user is prompted
* before discarding unsaved changes.
*/
boolean revert();
/**
* Invoked after the contact is saved.
*/
void onSaveCompleted(boolean hadChanges, int saveMode, boolean saveSucceeded,
Uri contactLookupUri, Long joinContactId);
/**
* Invoked after the contact is joined.
*/
void onJoinCompleted(Uri uri);
}
/**
* Displays a PopupWindow with photo edit options.
*/
private final class EditorPhotoSelectionHandler extends PhotoSelectionHandler {
/**
* Receiver of photo edit option callbacks.
*/
private final class EditorPhotoActionListener extends PhotoActionListener {
@Override
public void onRemovePictureChosen() {
getEditorFragment().removePhoto();
}
@Override
public void onPhotoSelected(Uri uri) throws FileNotFoundException {
mPhotoUri = uri;
getEditorFragment().updatePhoto(uri);
// Re-create the photo handler the next time we need it so that additional photo
// selections create a new temp file (and don't hit the one that was just added
// to the cache).
mPhotoSelectionHandler = null;
}
@Override
public Uri getCurrentPhotoUri() {
return mPhotoUri;
}
@Override
public void onPhotoSelectionDismissed() {
}
}
private final EditorPhotoActionListener mPhotoActionListener;
public EditorPhotoSelectionHandler(int photoMode) {
// We pass a null changeAnchorView since we are overriding onClick so that we
// can show the photo options in a dialog instead of a ListPopupWindow (which would
// be anchored at changeAnchorView).
// TODO: empty raw contact delta list
super(ContactEditorActivity.this, /* changeAnchorView =*/ null, photoMode,
/* isDirectoryContact =*/ false, new RawContactDeltaList());
mPhotoActionListener = new EditorPhotoActionListener();
}
@Override
public PhotoActionListener getListener() {
return mPhotoActionListener;
}
@Override
protected void startPhotoActivity(Intent intent, int requestCode, Uri photoUri) {
mPhotoUri = photoUri;
startActivityForResult(intent, requestCode);
}
}
private int mActionBarTitleResId;
private ContactEditor mFragment;
private Toolbar mToolbar;
private boolean mFinishActivityOnSaveCompleted;
private DialogManager mDialogManager = new DialogManager(this);
private EditorPhotoSelectionHandler mPhotoSelectionHandler;
private Uri mPhotoUri;
private int mPhotoMode;
private final ContactEditorFragment.Listener mFragmentListener =
new ContactEditorFragment.Listener() {
@Override
public void onDeleteRequested(Uri contactUri) {
ContactDeletionInteraction.start(
ContactEditorActivity.this, contactUri, true);
}
@Override
public void onReverted() {
finish();
}
@Override
public void onSaveFinished(Intent resultIntent) {
if (mFinishActivityOnSaveCompleted) {
setResult(resultIntent == null ? RESULT_CANCELED : RESULT_OK, resultIntent);
} else if (resultIntent != null) {
ImplicitIntentsUtil.startActivityInApp(
ContactEditorActivity.this, resultIntent);
}
finish();
}
@Override
public void onContactSplit(Uri newLookupUri) {
setResult(RESULT_CODE_SPLIT, /* data */ null);
finish();
}
@Override
public void onContactNotFound() {
finish();
}
@Override
public void onEditOtherRawContactRequested(
Uri contactLookupUri, long rawContactId, ArrayList<ContentValues> values) {
final Intent intent = EditorIntents.createEditOtherRawContactIntent(
ContactEditorActivity.this, contactLookupUri, rawContactId, values);
ImplicitIntentsUtil.startActivityInApp(
ContactEditorActivity.this, intent);
finish();
}
};
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
RequestPermissionsActivity.startPermissionActivityIfNeeded(this);
final Intent intent = getIntent();
final String action = intent.getAction();
// Update the component name of our intent to be this class to clear out any activity
// aliases. Otherwise ContactSaveService won't notify this activity once a save is finished.
// See b/34154706 for more info.
intent.setComponent(new ComponentName(this, ContactEditorActivity.class));
// Determine whether or not this activity should be finished after the user is done
// editing the contact or if this activity should launch another activity to view the
// contact's details.
mFinishActivityOnSaveCompleted = intent.getBooleanExtra(
INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, false);
// The only situation where action could be ACTION_JOIN_COMPLETED is if the
// user joined the contact with another and closed the activity before
// the save operation was completed. The activity should remain closed then.
if (ACTION_JOIN_COMPLETED.equals(action)) {
finish();
return;
}
if (ACTION_SAVE_COMPLETED.equals(action)) {
finish();
return;
}
setContentView(R.layout.contact_editor_activity);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
if (Intent.ACTION_EDIT.equals(action)) {
mActionBarTitleResId = R.string.contact_editor_title_existing_contact;
} else {
mActionBarTitleResId = R.string.contact_editor_title_new_contact;
}
mToolbar.setTitle(mActionBarTitleResId);
// Set activity title for Talkback
setTitle(mActionBarTitleResId);
if (savedState == null) {
// Create the editor and photo selection fragments
mFragment = new ContactEditorFragment();
getFragmentManager().beginTransaction()
.add(R.id.fragment_container, getEditorFragment(), TAG_EDITOR_FRAGMENT)
.commit();
} else {
// Restore state
mPhotoMode = savedState.getInt(STATE_PHOTO_MODE);
mActionBarTitleResId = savedState.getInt(STATE_ACTION_BAR_TITLE);
mPhotoUri = Uri.parse(savedState.getString(STATE_PHOTO_URI));
// Show/hide the editor and photo selection fragments (w/o animations)
mFragment = (ContactEditorFragment) getFragmentManager()
.findFragmentByTag(TAG_EDITOR_FRAGMENT);
final FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.show(getEditorFragment()).commit();
mToolbar.setTitle(mActionBarTitleResId);
}
// Set listeners
mFragment.setListener(mFragmentListener);
// Load editor data (even if it's hidden)
final Uri uri = Intent.ACTION_EDIT.equals(action) ? getIntent().getData() : null;
mFragment.load(action, uri, getIntent().getExtras());
if (Intent.ACTION_INSERT.equals(action)) {
DynamicShortcuts.reportShortcutUsed(this, DynamicShortcuts.SHORTCUT_ADD_CONTACT);
}
}
@Override
protected void onPause() {
super.onPause();
final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
final View currentFocus = getCurrentFocus();
if (imm != null && currentFocus != null) {
imm.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0);
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (mFragment == null) {
return;
}
final String action = intent.getAction();
if (Intent.ACTION_EDIT.equals(action)) {
mFragment.setIntentExtras(intent.getExtras());
} else if (ACTION_SAVE_COMPLETED.equals(action)) {
mFragment.onSaveCompleted(true,
intent.getIntExtra(ContactEditorFragment.SAVE_MODE_EXTRA_KEY,
ContactEditor.SaveMode.CLOSE),
intent.getBooleanExtra(ContactSaveService.EXTRA_SAVE_SUCCEEDED, false),
intent.getData(),
intent.getLongExtra(ContactEditorFragment.JOIN_CONTACT_ID_EXTRA_KEY, -1));
} else if (ACTION_JOIN_COMPLETED.equals(action)) {
mFragment.onJoinCompleted(intent.getData());
}
}
@Override
protected Dialog onCreateDialog(int id, Bundle args) {
if (DialogManager.isManagedId(id)) return mDialogManager.onCreateDialog(id, args);
// Nobody knows about the Dialog
Log.w(TAG, "Unknown dialog requested, id: " + id + ", args: " + args);
return null;
}
@Override
public DialogManager getDialogManager() {
return mDialogManager;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(STATE_PHOTO_MODE, mPhotoMode);
outState.putInt(STATE_ACTION_BAR_TITLE, mActionBarTitleResId);
outState.putString(STATE_PHOTO_URI,
mPhotoUri != null ? mPhotoUri.toString() : Uri.EMPTY.toString());
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mPhotoSelectionHandler == null) {
mPhotoSelectionHandler = (EditorPhotoSelectionHandler) getPhotoSelectionHandler();
}
if (mPhotoSelectionHandler.handlePhotoActivityResult(requestCode, resultCode, data)) {
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onBackPressed() {
if (mFragment != null) {
mFragment.revert();
}
}
/**
* Opens a dialog showing options for the user to change their photo (take, choose, or remove
* photo).
*/
public void changePhoto(int photoMode) {
mPhotoMode = photoMode;
// This method is called from an onClick handler in the PhotoEditorView. It's possible for
// onClick methods to run after onSaveInstanceState is called for the activity, so check
// if it's safe to commit transactions before trying.
if (isSafeToCommitTransactions()) {
PhotoSourceDialogFragment.show(this, mPhotoMode);
}
}
public Toolbar getToolbar() {
return mToolbar;
}
@Override
public void onRemovePictureChosen() {
getPhotoSelectionHandler().getListener().onRemovePictureChosen();
}
@Override
public void onTakePhotoChosen() {
getPhotoSelectionHandler().getListener().onTakePhotoChosen();
}
@Override
public void onPickFromGalleryChosen() {
getPhotoSelectionHandler().getListener().onPickFromGalleryChosen();
}
private PhotoSelectionHandler getPhotoSelectionHandler() {
if (mPhotoSelectionHandler == null) {
mPhotoSelectionHandler = new EditorPhotoSelectionHandler(mPhotoMode);
}
return mPhotoSelectionHandler;
}
private ContactEditorFragment getEditorFragment() {
return (ContactEditorFragment) mFragment;
}
}