Home | History | Annotate | Download | only in conversation
      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.messaging.ui.conversation;
     18 
     19 import android.app.FragmentManager;
     20 import android.app.FragmentTransaction;
     21 import android.content.Intent;
     22 import android.graphics.Rect;
     23 import android.net.Uri;
     24 import android.os.Bundle;
     25 import android.support.v7.app.ActionBar;
     26 import android.text.TextUtils;
     27 import android.view.MenuItem;
     28 
     29 import com.android.messaging.R;
     30 import com.android.messaging.datamodel.MessagingContentProvider;
     31 import com.android.messaging.datamodel.data.MessageData;
     32 import com.android.messaging.ui.BugleActionBarActivity;
     33 import com.android.messaging.ui.UIIntents;
     34 import com.android.messaging.ui.contact.ContactPickerFragment;
     35 import com.android.messaging.ui.contact.ContactPickerFragment.ContactPickerFragmentHost;
     36 import com.android.messaging.ui.conversation.ConversationActivityUiState.ConversationActivityUiStateHost;
     37 import com.android.messaging.ui.conversation.ConversationFragment.ConversationFragmentHost;
     38 import com.android.messaging.ui.conversationlist.ConversationListActivity;
     39 import com.android.messaging.util.Assert;
     40 import com.android.messaging.util.ContentType;
     41 import com.android.messaging.util.LogUtil;
     42 import com.android.messaging.util.OsUtil;
     43 import com.android.messaging.util.UiUtils;
     44 
     45 public class ConversationActivity extends BugleActionBarActivity
     46         implements ContactPickerFragmentHost, ConversationFragmentHost,
     47         ConversationActivityUiStateHost {
     48     public static final int FINISH_RESULT_CODE = 1;
     49     private static final String SAVED_INSTANCE_STATE_UI_STATE_KEY = "uistate";
     50 
     51     private ConversationActivityUiState mUiState;
     52 
     53     // Fragment transactions cannot be performed after onSaveInstanceState() has been called since
     54     // it will cause state loss. We don't want to call commitAllowingStateLoss() since it's
     55     // dangerous. Therefore, we note when instance state is saved and avoid performing UI state
     56     // updates concerning fragments past that point.
     57     private boolean mInstanceStateSaved;
     58 
     59     // Tracks whether onPause is called.
     60     private boolean mIsPaused;
     61 
     62     @Override
     63     protected void onCreate(final Bundle savedInstanceState) {
     64         super.onCreate(savedInstanceState);
     65 
     66         setContentView(R.layout.conversation_activity);
     67 
     68         final Intent intent = getIntent();
     69 
     70         // Do our best to restore UI state from saved instance state.
     71         if (savedInstanceState != null) {
     72             mUiState = savedInstanceState.getParcelable(SAVED_INSTANCE_STATE_UI_STATE_KEY);
     73         } else {
     74             if (intent.
     75                     getBooleanExtra(UIIntents.UI_INTENT_EXTRA_GOTO_CONVERSATION_LIST, false)) {
     76                 // See the comment in BugleWidgetService.getViewMoreConversationsView() why this
     77                 // is unfortunately necessary. The Bugle desktop widget can display a list of
     78                 // conversations. When there are more conversations that can be displayed in
     79                 // the widget, the last item is a "More conversations" item. The way widgets
     80                 // are built, the list items can only go to a single fill-in intent which points
     81                 // to this ConversationActivity. When the user taps on "More conversations", we
     82                 // really want to go to the ConversationList. This code makes that possible.
     83                 finish();
     84                 final Intent convListIntent = new Intent(this, ConversationListActivity.class);
     85                 convListIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
     86                 startActivity(convListIntent);
     87                 return;
     88             }
     89         }
     90 
     91         // If saved instance state doesn't offer a clue, get the info from the intent.
     92         if (mUiState == null) {
     93             final String conversationId = intent.getStringExtra(
     94                     UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
     95             mUiState = new ConversationActivityUiState(conversationId);
     96         }
     97         mUiState.setHost(this);
     98         mInstanceStateSaved = false;
     99 
    100         // Don't animate UI state change for initial setup.
    101         updateUiState(false /* animate */);
    102 
    103         // See if we're getting called from a widget to directly display an image or video
    104         final String extraToDisplay =
    105                 intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_URI);
    106         if (!TextUtils.isEmpty(extraToDisplay)) {
    107             final String contentType =
    108                     intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_TYPE);
    109             final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(
    110                     findViewById(R.id.conversation_and_compose_container));
    111             if (ContentType.isImageType(contentType)) {
    112                 final Uri imagesUri = MessagingContentProvider.buildConversationImagesUri(
    113                         mUiState.getConversationId());
    114                 UIIntents.get().launchFullScreenPhotoViewer(
    115                         this, Uri.parse(extraToDisplay), bounds, imagesUri);
    116             } else if (ContentType.isVideoType(contentType)) {
    117                 UIIntents.get().launchFullScreenVideoViewer(this, Uri.parse(extraToDisplay));
    118             }
    119         }
    120     }
    121 
    122     @Override
    123     protected void onSaveInstanceState(final Bundle outState) {
    124         super.onSaveInstanceState(outState);
    125         // After onSaveInstanceState() is called, future changes to mUiState won't update the UI
    126         // anymore, because fragment transactions are not allowed past this point.
    127         // For an activity recreation due to orientation change, the saved instance state keeps
    128         // using the in-memory copy of the UI state instead of writing it to parcel as an
    129         // optimization, so the UI state values may still change in response to, for example,
    130         // focus change from the framework, making mUiState and actual UI inconsistent.
    131         // Therefore, save an exact "snapshot" (clone) of the UI state object to make sure the
    132         // restored UI state ALWAYS matches the actual restored UI components.
    133         outState.putParcelable(SAVED_INSTANCE_STATE_UI_STATE_KEY, mUiState.clone());
    134         mInstanceStateSaved = true;
    135     }
    136 
    137     @Override
    138     protected void onResume() {
    139         super.onResume();
    140 
    141         // we need to reset the mInstanceStateSaved flag since we may have just been restored from
    142         // a previous onStop() instead of an onDestroy().
    143         mInstanceStateSaved = false;
    144         mIsPaused = false;
    145     }
    146 
    147     @Override
    148     protected void onPause() {
    149         super.onPause();
    150         mIsPaused = true;
    151     }
    152 
    153     @Override
    154     public void onWindowFocusChanged(final boolean hasFocus) {
    155         super.onWindowFocusChanged(hasFocus);
    156         final ConversationFragment conversationFragment = getConversationFragment();
    157         // When the screen is turned on, the last used activity gets resumed, but it gets
    158         // window focus only after the lock screen is unlocked.
    159         if (hasFocus && conversationFragment != null) {
    160             conversationFragment.setConversationFocus();
    161         }
    162     }
    163 
    164     @Override
    165     public void onDisplayHeightChanged(final int heightSpecification) {
    166         super.onDisplayHeightChanged(heightSpecification);
    167         invalidateActionBar();
    168     }
    169 
    170     @Override
    171     protected void onDestroy() {
    172         super.onDestroy();
    173         if (mUiState != null) {
    174             mUiState.setHost(null);
    175         }
    176     }
    177 
    178     @Override
    179     public void updateActionBar(final ActionBar actionBar) {
    180         super.updateActionBar(actionBar);
    181         final ConversationFragment conversation = getConversationFragment();
    182         final ContactPickerFragment contactPicker = getContactPicker();
    183         if (contactPicker != null && mUiState.shouldShowContactPickerFragment()) {
    184             contactPicker.updateActionBar(actionBar);
    185         } else if (conversation != null && mUiState.shouldShowConversationFragment()) {
    186             conversation.updateActionBar(actionBar);
    187         }
    188     }
    189 
    190     @Override
    191     public boolean onOptionsItemSelected(final MenuItem menuItem) {
    192         if (super.onOptionsItemSelected(menuItem)) {
    193             return true;
    194         }
    195         if (menuItem.getItemId() == android.R.id.home) {
    196             onNavigationUpPressed();
    197             return true;
    198         }
    199         return false;
    200     }
    201 
    202     public void onNavigationUpPressed() {
    203         // Let the conversation fragment handle the navigation up press.
    204         final ConversationFragment conversationFragment = getConversationFragment();
    205         if (conversationFragment != null && conversationFragment.onNavigationUpPressed()) {
    206             return;
    207         }
    208         onFinishCurrentConversation();
    209     }
    210 
    211     @Override
    212     public void onBackPressed() {
    213         // If action mode is active dismiss it
    214         if (getActionMode() != null) {
    215             dismissActionMode();
    216             return;
    217         }
    218 
    219         // Let the conversation fragment handle the back press.
    220         final ConversationFragment conversationFragment = getConversationFragment();
    221         if (conversationFragment != null && conversationFragment.onBackPressed()) {
    222             return;
    223         }
    224         super.onBackPressed();
    225     }
    226 
    227     private ContactPickerFragment getContactPicker() {
    228         return (ContactPickerFragment) getFragmentManager().findFragmentByTag(
    229                 ContactPickerFragment.FRAGMENT_TAG);
    230     }
    231 
    232     private ConversationFragment getConversationFragment() {
    233         return (ConversationFragment) getFragmentManager().findFragmentByTag(
    234                 ConversationFragment.FRAGMENT_TAG);
    235     }
    236 
    237     @Override // From ContactPickerFragmentHost
    238     public void onGetOrCreateNewConversation(final String conversationId) {
    239         Assert.isTrue(conversationId != null);
    240         mUiState.onGetOrCreateConversation(conversationId);
    241     }
    242 
    243     @Override // From ContactPickerFragmentHost
    244     public void onBackButtonPressed() {
    245         onBackPressed();
    246     }
    247 
    248     @Override // From ContactPickerFragmentHost
    249     public void onInitiateAddMoreParticipants() {
    250         mUiState.onAddMoreParticipants();
    251     }
    252 
    253 
    254     @Override
    255     public void onParticipantCountChanged(final boolean canAddMoreParticipants) {
    256         mUiState.onParticipantCountUpdated(canAddMoreParticipants);
    257     }
    258 
    259     @Override // From ConversationFragmentHost
    260     public void onStartComposeMessage() {
    261         mUiState.onStartMessageCompose();
    262     }
    263 
    264     @Override // From ConversationFragmentHost
    265     public void onConversationMetadataUpdated() {
    266         invalidateActionBar();
    267     }
    268 
    269     @Override // From ConversationFragmentHost
    270     public void onConversationMessagesUpdated(final int numberOfMessages) {
    271     }
    272 
    273     @Override // From ConversationFragmentHost
    274     public void onConversationParticipantDataLoaded(final int numberOfParticipants) {
    275     }
    276 
    277     @Override // From ConversationFragmentHost
    278     public boolean isActiveAndFocused() {
    279         return !mIsPaused && hasWindowFocus();
    280     }
    281 
    282     @Override // From ConversationActivityUiStateListener
    283     public void onConversationContactPickerUiStateChanged(final int oldState, final int newState,
    284             final boolean animate) {
    285         Assert.isTrue(oldState != newState);
    286         updateUiState(animate);
    287     }
    288 
    289     private void updateUiState(final boolean animate) {
    290         if (mInstanceStateSaved || mIsPaused) {
    291             return;
    292         }
    293         Assert.notNull(mUiState);
    294         final Intent intent = getIntent();
    295         final String conversationId = mUiState.getConversationId();
    296 
    297         final FragmentManager fragmentManager = getFragmentManager();
    298         final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    299 
    300         final boolean needConversationFragment = mUiState.shouldShowConversationFragment();
    301         final boolean needContactPickerFragment = mUiState.shouldShowContactPickerFragment();
    302         ConversationFragment conversationFragment = getConversationFragment();
    303 
    304         // Set up the conversation fragment.
    305         if (needConversationFragment) {
    306             Assert.notNull(conversationId);
    307             if (conversationFragment == null) {
    308                 conversationFragment = new ConversationFragment();
    309                 fragmentTransaction.add(R.id.conversation_fragment_container,
    310                         conversationFragment, ConversationFragment.FRAGMENT_TAG);
    311             }
    312             final MessageData draftData = intent.getParcelableExtra(
    313                     UIIntents.UI_INTENT_EXTRA_DRAFT_DATA);
    314             if (!needContactPickerFragment) {
    315                 // Once the user has committed the audience,remove the draft data from the
    316                 // intent to prevent reuse
    317                 intent.removeExtra(UIIntents.UI_INTENT_EXTRA_DRAFT_DATA);
    318             }
    319             conversationFragment.setHost(this);
    320             conversationFragment.setConversationInfo(this, conversationId, draftData);
    321         } else if (conversationFragment != null) {
    322             // Don't save draft to DB when removing conversation fragment and switching to
    323             // contact picking mode.  The draft is intended for the new group.
    324             conversationFragment.suppressWriteDraft();
    325             fragmentTransaction.remove(conversationFragment);
    326         }
    327 
    328         // Set up the contact picker fragment.
    329         ContactPickerFragment contactPickerFragment = getContactPicker();
    330         if (needContactPickerFragment) {
    331             if (contactPickerFragment == null) {
    332                 contactPickerFragment = new ContactPickerFragment();
    333                 fragmentTransaction.add(R.id.contact_picker_fragment_container,
    334                         contactPickerFragment, ContactPickerFragment.FRAGMENT_TAG);
    335             }
    336             contactPickerFragment.setHost(this);
    337             contactPickerFragment.setContactPickingMode(mUiState.getDesiredContactPickingMode(),
    338                     animate);
    339         } else if (contactPickerFragment != null) {
    340             fragmentTransaction.remove(contactPickerFragment);
    341         }
    342 
    343         fragmentTransaction.commit();
    344         invalidateActionBar();
    345     }
    346 
    347     @Override
    348     public void onFinishCurrentConversation() {
    349         // Simply finish the current activity. The current design is to leave any empty
    350         // conversations as is.
    351         if (OsUtil.isAtLeastL()) {
    352             finishAfterTransition();
    353         } else {
    354             finish();
    355         }
    356     }
    357 
    358     @Override
    359     public boolean shouldResumeComposeMessage() {
    360         return mUiState.shouldResumeComposeMessage();
    361     }
    362 
    363     @Override
    364     protected void onActivityResult(final int requestCode, final int resultCode,
    365             final Intent data) {
    366         if (requestCode == ConversationFragment.REQUEST_CHOOSE_ATTACHMENTS &&
    367                 resultCode == RESULT_OK) {
    368             final ConversationFragment conversationFragment = getConversationFragment();
    369             if (conversationFragment != null) {
    370                 conversationFragment.onAttachmentChoosen();
    371             } else {
    372                 LogUtil.e(LogUtil.BUGLE_TAG, "ConversationFragment is missing after launching " +
    373                         "AttachmentChooserActivity!");
    374             }
    375         } else if (resultCode == FINISH_RESULT_CODE) {
    376             finish();
    377         }
    378     }
    379 }
    380