Home | History | Annotate | Download | only in activities
      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 
     17 package com.android.contacts.activities;
     18 
     19 import android.app.Dialog;
     20 import android.app.FragmentTransaction;
     21 import android.content.ComponentName;
     22 import android.content.ContentValues;
     23 import android.content.Intent;
     24 import android.net.Uri;
     25 import android.os.Bundle;
     26 import android.provider.ContactsContract.QuickContact;
     27 import android.support.v7.widget.Toolbar;
     28 import android.util.Log;
     29 import android.view.View;
     30 import android.view.inputmethod.InputMethodManager;
     31 
     32 import com.android.contacts.AppCompatContactsActivity;
     33 import com.android.contacts.ContactSaveService;
     34 import com.android.contacts.DynamicShortcuts;
     35 import com.android.contacts.R;
     36 import com.android.contacts.detail.PhotoSelectionHandler;
     37 import com.android.contacts.editor.ContactEditorFragment;
     38 import com.android.contacts.editor.EditorIntents;
     39 import com.android.contacts.editor.PhotoSourceDialogFragment;
     40 import com.android.contacts.interactions.ContactDeletionInteraction;
     41 import com.android.contacts.model.RawContactDeltaList;
     42 import com.android.contacts.util.DialogManager;
     43 import com.android.contacts.util.ImplicitIntentsUtil;
     44 
     45 import java.io.FileNotFoundException;
     46 import java.util.ArrayList;
     47 
     48 /**
     49  * Contact editor with only the most important fields displayed initially.
     50  */
     51 public class ContactEditorActivity extends AppCompatContactsActivity implements
     52         PhotoSourceDialogFragment.Listener,
     53         DialogManager.DialogShowingViewActivity {
     54     private static final String TAG = "ContactEditorActivity";
     55 
     56     public static final String ACTION_JOIN_COMPLETED = "joinCompleted";
     57     public static final String ACTION_SAVE_COMPLETED = "saveCompleted";
     58 
     59     public static final int RESULT_CODE_SPLIT = 2;
     60     // 3 used for ContactDeletionInteraction.RESULT_CODE_DELETED
     61     public static final int RESULT_CODE_EDITED = 4;
     62 
     63     /**
     64      * The contact will be saved to this account when this is set for an insert. This
     65      * is necessary because {@link android.accounts.Account} cannot be created with null values
     66      * for the name and type and an Account is needed for
     67      * {@link android.provider.ContactsContract.Intents.Insert#EXTRA_ACCOUNT}
     68      */
     69     public static final String EXTRA_ACCOUNT_WITH_DATA_SET =
     70             "com.android.contacts.ACCOUNT_WITH_DATA_SET";
     71 
     72     private static final String TAG_EDITOR_FRAGMENT = "editor_fragment";
     73 
     74     private static final String STATE_PHOTO_MODE = "photo_mode";
     75     private static final String STATE_ACTION_BAR_TITLE = "action_bar_title";
     76     private static final String STATE_PHOTO_URI = "photo_uri";
     77 
     78     /**
     79      * Boolean intent key that specifies that this activity should finish itself
     80      * (instead of launching a new view intent) after the editor changes have been
     81      * saved.
     82      */
     83     public static final String INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED =
     84             "finishActivityOnSaveCompleted";
     85 
     86     /**
     87      * Contract for contact editors Fragments that are managed by this Activity.
     88      */
     89     public interface ContactEditor {
     90 
     91         /**
     92          * Modes that specify what the AsyncTask has to perform after saving
     93          */
     94         interface SaveMode {
     95             /**
     96              * Close the editor after saving
     97              */
     98             int CLOSE = 0;
     99 
    100             /**
    101              * Reload the data so that the user can continue editing
    102              */
    103             int RELOAD = 1;
    104 
    105             /**
    106              * Split the contact after saving
    107              */
    108             int SPLIT = 2;
    109 
    110             /**
    111              * Join another contact after saving
    112              */
    113             int JOIN = 3;
    114 
    115             /**
    116              * Navigate to the editor view after saving.
    117              */
    118             int EDITOR = 4;
    119         }
    120 
    121         /**
    122          * The status of the contact editor.
    123          */
    124         interface Status {
    125             /**
    126              * The loader is fetching data
    127              */
    128             int LOADING = 0;
    129 
    130             /**
    131              * Not currently busy. We are waiting for the user to enter data
    132              */
    133             int EDITING = 1;
    134 
    135             /**
    136              * The data is currently being saved. This is used to prevent more
    137              * auto-saves (they shouldn't overlap)
    138              */
    139             int SAVING = 2;
    140 
    141             /**
    142              * Prevents any more saves. This is used if in the following cases:
    143              * - After Save/Close
    144              * - After Revert
    145              * - After the user has accepted an edit suggestion
    146              * - After the user chooses to expand the editor
    147              */
    148             int CLOSING = 3;
    149 
    150             /**
    151              * Prevents saving while running a child activity.
    152              */
    153             int SUB_ACTIVITY = 4;
    154         }
    155 
    156         /**
    157          * Sets the hosting Activity that will receive callbacks from the contact editor.
    158          */
    159         void setListener(ContactEditorFragment.Listener listener);
    160 
    161         /**
    162          * Initialize the contact editor.
    163          */
    164         void load(String action, Uri lookupUri, Bundle intentExtras);
    165 
    166         /**
    167          * Applies extras from the hosting Activity to the writable raw contact.
    168          */
    169         void setIntentExtras(Bundle extras);
    170 
    171         /**
    172          * Saves or creates the contact based on the mode, and if successful
    173          * finishes the activity.
    174          */
    175         boolean save(int saveMode);
    176 
    177         /**
    178          * If there are no unsaved changes, just close the editor, otherwise the user is prompted
    179          * before discarding unsaved changes.
    180          */
    181         boolean revert();
    182 
    183         /**
    184          * Invoked after the contact is saved.
    185          */
    186         void onSaveCompleted(boolean hadChanges, int saveMode, boolean saveSucceeded,
    187                 Uri contactLookupUri, Long joinContactId);
    188 
    189         /**
    190          * Invoked after the contact is joined.
    191          */
    192         void onJoinCompleted(Uri uri);
    193     }
    194 
    195     /**
    196      * Displays a PopupWindow with photo edit options.
    197      */
    198     private final class EditorPhotoSelectionHandler extends PhotoSelectionHandler {
    199 
    200         /**
    201          * Receiver of photo edit option callbacks.
    202          */
    203         private final class EditorPhotoActionListener extends PhotoActionListener {
    204 
    205             @Override
    206             public void onRemovePictureChosen() {
    207                 getEditorFragment().removePhoto();
    208             }
    209 
    210             @Override
    211             public void onPhotoSelected(Uri uri) throws FileNotFoundException {
    212                 mPhotoUri = uri;
    213                 getEditorFragment().updatePhoto(uri);
    214 
    215                 // Re-create the photo handler the next time we need it so that additional photo
    216                 // selections create a new temp file (and don't hit the one that was just added
    217                 // to the cache).
    218                 mPhotoSelectionHandler = null;
    219             }
    220 
    221             @Override
    222             public Uri getCurrentPhotoUri() {
    223                 return mPhotoUri;
    224             }
    225 
    226             @Override
    227             public void onPhotoSelectionDismissed() {
    228             }
    229         }
    230 
    231         private final EditorPhotoActionListener mPhotoActionListener;
    232 
    233         public EditorPhotoSelectionHandler(int photoMode) {
    234             // We pass a null changeAnchorView since we are overriding onClick so that we
    235             // can show the photo options in a dialog instead of a ListPopupWindow (which would
    236             // be anchored at changeAnchorView).
    237 
    238             // TODO: empty raw contact delta list
    239             super(ContactEditorActivity.this, /* changeAnchorView =*/ null, photoMode,
    240                     /* isDirectoryContact =*/ false, new RawContactDeltaList());
    241             mPhotoActionListener = new EditorPhotoActionListener();
    242         }
    243 
    244         @Override
    245         public PhotoActionListener getListener() {
    246             return mPhotoActionListener;
    247         }
    248 
    249         @Override
    250         protected void startPhotoActivity(Intent intent, int requestCode, Uri photoUri) {
    251             mPhotoUri = photoUri;
    252             startActivityForResult(intent, requestCode);
    253         }
    254     }
    255 
    256     private int mActionBarTitleResId;
    257     private ContactEditor mFragment;
    258     private Toolbar mToolbar;
    259     private boolean mFinishActivityOnSaveCompleted;
    260     private DialogManager mDialogManager = new DialogManager(this);
    261 
    262     private EditorPhotoSelectionHandler mPhotoSelectionHandler;
    263     private Uri mPhotoUri;
    264     private int mPhotoMode;
    265 
    266     private final ContactEditorFragment.Listener  mFragmentListener =
    267             new ContactEditorFragment.Listener() {
    268 
    269                 @Override
    270                 public void onDeleteRequested(Uri contactUri) {
    271                     ContactDeletionInteraction.start(
    272                             ContactEditorActivity.this, contactUri, true);
    273                 }
    274 
    275                 @Override
    276                 public void onReverted() {
    277                     finish();
    278                 }
    279 
    280                 @Override
    281                 public void onSaveFinished(Intent resultIntent) {
    282                     if (mFinishActivityOnSaveCompleted) {
    283                         setResult(resultIntent == null ? RESULT_CANCELED : RESULT_OK, resultIntent);
    284                     } else if (resultIntent != null) {
    285                         ImplicitIntentsUtil.startActivityInApp(
    286                                 ContactEditorActivity.this, resultIntent);
    287                     }
    288                     finish();
    289                 }
    290 
    291                 @Override
    292                 public void onContactSplit(Uri newLookupUri) {
    293                     setResult(RESULT_CODE_SPLIT, /* data */ null);
    294                     finish();
    295                 }
    296 
    297                 @Override
    298                 public void onContactNotFound() {
    299                     finish();
    300                 }
    301 
    302                 @Override
    303                 public void onEditOtherRawContactRequested(
    304                         Uri contactLookupUri, long rawContactId, ArrayList<ContentValues> values) {
    305                     final Intent intent = EditorIntents.createEditOtherRawContactIntent(
    306                             ContactEditorActivity.this, contactLookupUri, rawContactId, values);
    307                     ImplicitIntentsUtil.startActivityInApp(
    308                             ContactEditorActivity.this, intent);
    309                     finish();
    310                 }
    311             };
    312 
    313     @Override
    314     public void onCreate(Bundle savedState) {
    315         super.onCreate(savedState);
    316 
    317         RequestPermissionsActivity.startPermissionActivityIfNeeded(this);
    318 
    319         final Intent intent = getIntent();
    320         final String action = intent.getAction();
    321 
    322         // Update the component name of our intent to be this class to clear out any activity
    323         // aliases. Otherwise ContactSaveService won't notify this activity once a save is finished.
    324         // See b/34154706 for more info.
    325         intent.setComponent(new ComponentName(this, ContactEditorActivity.class));
    326 
    327         // Determine whether or not this activity should be finished after the user is done
    328         // editing the contact or if this activity should launch another activity to view the
    329         // contact's details.
    330         mFinishActivityOnSaveCompleted = intent.getBooleanExtra(
    331                 INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, false);
    332 
    333         // The only situation where action could be ACTION_JOIN_COMPLETED is if the
    334         // user joined the contact with another and closed the activity before
    335         // the save operation was completed.  The activity should remain closed then.
    336         if (ACTION_JOIN_COMPLETED.equals(action)) {
    337             finish();
    338             return;
    339         }
    340 
    341         if (ACTION_SAVE_COMPLETED.equals(action)) {
    342             finish();
    343             return;
    344         }
    345 
    346         setContentView(R.layout.contact_editor_activity);
    347         mToolbar = (Toolbar) findViewById(R.id.toolbar);
    348         setSupportActionBar(mToolbar);
    349         if (Intent.ACTION_EDIT.equals(action)) {
    350             mActionBarTitleResId = R.string.contact_editor_title_existing_contact;
    351         } else {
    352             mActionBarTitleResId = R.string.contact_editor_title_new_contact;
    353         }
    354         mToolbar.setTitle(mActionBarTitleResId);
    355         // Set activity title for Talkback
    356         setTitle(mActionBarTitleResId);
    357 
    358         if (savedState == null) {
    359             // Create the editor and photo selection fragments
    360             mFragment = new ContactEditorFragment();
    361             getFragmentManager().beginTransaction()
    362                     .add(R.id.fragment_container, getEditorFragment(), TAG_EDITOR_FRAGMENT)
    363                     .commit();
    364         } else {
    365             // Restore state
    366             mPhotoMode = savedState.getInt(STATE_PHOTO_MODE);
    367             mActionBarTitleResId = savedState.getInt(STATE_ACTION_BAR_TITLE);
    368             mPhotoUri = Uri.parse(savedState.getString(STATE_PHOTO_URI));
    369 
    370             // Show/hide the editor and photo selection fragments (w/o animations)
    371             mFragment = (ContactEditorFragment) getFragmentManager()
    372                     .findFragmentByTag(TAG_EDITOR_FRAGMENT);
    373             final FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
    374             fragmentTransaction.show(getEditorFragment()).commit();
    375             mToolbar.setTitle(mActionBarTitleResId);
    376         }
    377 
    378         // Set listeners
    379         mFragment.setListener(mFragmentListener);
    380 
    381         // Load editor data (even if it's hidden)
    382         final Uri uri = Intent.ACTION_EDIT.equals(action) ? getIntent().getData() : null;
    383         mFragment.load(action, uri, getIntent().getExtras());
    384 
    385         if (Intent.ACTION_INSERT.equals(action)) {
    386             DynamicShortcuts.reportShortcutUsed(this, DynamicShortcuts.SHORTCUT_ADD_CONTACT);
    387         }
    388     }
    389 
    390     @Override
    391     protected void onPause() {
    392         super.onPause();
    393         final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
    394         final View currentFocus = getCurrentFocus();
    395         if (imm != null && currentFocus != null) {
    396             imm.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0);
    397         }
    398     }
    399 
    400     @Override
    401     protected void onNewIntent(Intent intent) {
    402         super.onNewIntent(intent);
    403 
    404         if (mFragment == null) {
    405             return;
    406         }
    407 
    408         final String action = intent.getAction();
    409         if (Intent.ACTION_EDIT.equals(action)) {
    410             mFragment.setIntentExtras(intent.getExtras());
    411         } else if (ACTION_SAVE_COMPLETED.equals(action)) {
    412             mFragment.onSaveCompleted(true,
    413                     intent.getIntExtra(ContactEditorFragment.SAVE_MODE_EXTRA_KEY,
    414                             ContactEditor.SaveMode.CLOSE),
    415                     intent.getBooleanExtra(ContactSaveService.EXTRA_SAVE_SUCCEEDED, false),
    416                     intent.getData(),
    417                     intent.getLongExtra(ContactEditorFragment.JOIN_CONTACT_ID_EXTRA_KEY, -1));
    418         } else if (ACTION_JOIN_COMPLETED.equals(action)) {
    419             mFragment.onJoinCompleted(intent.getData());
    420         }
    421     }
    422 
    423     @Override
    424     protected Dialog onCreateDialog(int id, Bundle args) {
    425         if (DialogManager.isManagedId(id)) return mDialogManager.onCreateDialog(id, args);
    426 
    427         // Nobody knows about the Dialog
    428         Log.w(TAG, "Unknown dialog requested, id: " + id + ", args: " + args);
    429         return null;
    430     }
    431 
    432     @Override
    433     public DialogManager getDialogManager() {
    434         return mDialogManager;
    435     }
    436 
    437     @Override
    438     protected void onSaveInstanceState(Bundle outState) {
    439         super.onSaveInstanceState(outState);
    440         outState.putInt(STATE_PHOTO_MODE, mPhotoMode);
    441         outState.putInt(STATE_ACTION_BAR_TITLE, mActionBarTitleResId);
    442         outState.putString(STATE_PHOTO_URI,
    443                 mPhotoUri != null ? mPhotoUri.toString() : Uri.EMPTY.toString());
    444     }
    445 
    446     @Override
    447     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    448         if (mPhotoSelectionHandler == null) {
    449             mPhotoSelectionHandler = (EditorPhotoSelectionHandler) getPhotoSelectionHandler();
    450         }
    451         if (mPhotoSelectionHandler.handlePhotoActivityResult(requestCode, resultCode, data)) {
    452             return;
    453         }
    454         super.onActivityResult(requestCode, resultCode, data);
    455     }
    456 
    457     @Override
    458     public void onBackPressed() {
    459         if (mFragment != null) {
    460             mFragment.revert();
    461         }
    462     }
    463 
    464     /**
    465      * Opens a dialog showing options for the user to change their photo (take, choose, or remove
    466      * photo).
    467      */
    468     public void changePhoto(int photoMode) {
    469         mPhotoMode = photoMode;
    470         // This method is called from an onClick handler in the PhotoEditorView. It's possible for
    471         // onClick methods to run after onSaveInstanceState is called for the activity, so check
    472         // if it's safe to commit transactions before trying.
    473         if (isSafeToCommitTransactions()) {
    474             PhotoSourceDialogFragment.show(this, mPhotoMode);
    475         }
    476     }
    477 
    478     public Toolbar getToolbar() {
    479         return mToolbar;
    480     }
    481 
    482     @Override
    483     public void onRemovePictureChosen() {
    484         getPhotoSelectionHandler().getListener().onRemovePictureChosen();
    485     }
    486 
    487     @Override
    488     public void onTakePhotoChosen() {
    489         getPhotoSelectionHandler().getListener().onTakePhotoChosen();
    490     }
    491 
    492     @Override
    493     public void onPickFromGalleryChosen() {
    494         getPhotoSelectionHandler().getListener().onPickFromGalleryChosen();
    495     }
    496 
    497     private PhotoSelectionHandler getPhotoSelectionHandler() {
    498         if (mPhotoSelectionHandler == null) {
    499             mPhotoSelectionHandler = new EditorPhotoSelectionHandler(mPhotoMode);
    500         }
    501         return mPhotoSelectionHandler;
    502     }
    503 
    504     private ContactEditorFragment getEditorFragment() {
    505         return (ContactEditorFragment) mFragment;
    506     }
    507 }
    508