/* * 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.messaging.ui.conversation; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.content.Intent; import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; import androidx.appcompat.app.ActionBar; import android.text.TextUtils; import android.view.MenuItem; import com.android.messaging.R; import com.android.messaging.datamodel.MessagingContentProvider; import com.android.messaging.datamodel.data.MessageData; import com.android.messaging.ui.BugleActionBarActivity; import com.android.messaging.ui.UIIntents; import com.android.messaging.ui.contact.ContactPickerFragment; import com.android.messaging.ui.contact.ContactPickerFragment.ContactPickerFragmentHost; import com.android.messaging.ui.conversation.ConversationActivityUiState.ConversationActivityUiStateHost; import com.android.messaging.ui.conversation.ConversationFragment.ConversationFragmentHost; import com.android.messaging.ui.conversationlist.ConversationListActivity; import com.android.messaging.util.Assert; import com.android.messaging.util.ContentType; import com.android.messaging.util.LogUtil; import com.android.messaging.util.OsUtil; import com.android.messaging.util.UiUtils; public class ConversationActivity extends BugleActionBarActivity implements ContactPickerFragmentHost, ConversationFragmentHost, ConversationActivityUiStateHost { public static final int FINISH_RESULT_CODE = 1; private static final String SAVED_INSTANCE_STATE_UI_STATE_KEY = "uistate"; private ConversationActivityUiState mUiState; // Fragment transactions cannot be performed after onSaveInstanceState() has been called since // it will cause state loss. We don't want to call commitAllowingStateLoss() since it's // dangerous. Therefore, we note when instance state is saved and avoid performing UI state // updates concerning fragments past that point. private boolean mInstanceStateSaved; // Tracks whether onPause is called. private boolean mIsPaused; @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.conversation_activity); final Intent intent = getIntent(); // Do our best to restore UI state from saved instance state. if (savedInstanceState != null) { mUiState = savedInstanceState.getParcelable(SAVED_INSTANCE_STATE_UI_STATE_KEY); } else { if (intent. getBooleanExtra(UIIntents.UI_INTENT_EXTRA_GOTO_CONVERSATION_LIST, false)) { // See the comment in BugleWidgetService.getViewMoreConversationsView() why this // is unfortunately necessary. The Bugle desktop widget can display a list of // conversations. When there are more conversations that can be displayed in // the widget, the last item is a "More conversations" item. The way widgets // are built, the list items can only go to a single fill-in intent which points // to this ConversationActivity. When the user taps on "More conversations", we // really want to go to the ConversationList. This code makes that possible. finish(); final Intent convListIntent = new Intent(this, ConversationListActivity.class); convListIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(convListIntent); return; } } // If saved instance state doesn't offer a clue, get the info from the intent. if (mUiState == null) { final String conversationId = intent.getStringExtra( UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID); mUiState = new ConversationActivityUiState(conversationId); } mUiState.setHost(this); mInstanceStateSaved = false; // Don't animate UI state change for initial setup. updateUiState(false /* animate */); // See if we're getting called from a widget to directly display an image or video final String extraToDisplay = intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_URI); if (!TextUtils.isEmpty(extraToDisplay)) { final String contentType = intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_TYPE); final Rect bounds = UiUtils.getMeasuredBoundsOnScreen( findViewById(R.id.conversation_and_compose_container)); if (ContentType.isImageType(contentType)) { final Uri imagesUri = MessagingContentProvider.buildConversationImagesUri( mUiState.getConversationId()); UIIntents.get().launchFullScreenPhotoViewer( this, Uri.parse(extraToDisplay), bounds, imagesUri); } else if (ContentType.isVideoType(contentType)) { UIIntents.get().launchFullScreenVideoViewer(this, Uri.parse(extraToDisplay)); } } } @Override protected void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); // After onSaveInstanceState() is called, future changes to mUiState won't update the UI // anymore, because fragment transactions are not allowed past this point. // For an activity recreation due to orientation change, the saved instance state keeps // using the in-memory copy of the UI state instead of writing it to parcel as an // optimization, so the UI state values may still change in response to, for example, // focus change from the framework, making mUiState and actual UI inconsistent. // Therefore, save an exact "snapshot" (clone) of the UI state object to make sure the // restored UI state ALWAYS matches the actual restored UI components. outState.putParcelable(SAVED_INSTANCE_STATE_UI_STATE_KEY, mUiState.clone()); mInstanceStateSaved = true; } @Override protected void onResume() { super.onResume(); // we need to reset the mInstanceStateSaved flag since we may have just been restored from // a previous onStop() instead of an onDestroy(). mInstanceStateSaved = false; mIsPaused = false; } @Override protected void onPause() { super.onPause(); mIsPaused = true; } @Override public void onWindowFocusChanged(final boolean hasFocus) { super.onWindowFocusChanged(hasFocus); final ConversationFragment conversationFragment = getConversationFragment(); // When the screen is turned on, the last used activity gets resumed, but it gets // window focus only after the lock screen is unlocked. if (hasFocus && conversationFragment != null) { conversationFragment.setConversationFocus(); } } @Override public void onDisplayHeightChanged(final int heightSpecification) { super.onDisplayHeightChanged(heightSpecification); invalidateActionBar(); } @Override protected void onDestroy() { super.onDestroy(); if (mUiState != null) { mUiState.setHost(null); } } @Override public void updateActionBar(final ActionBar actionBar) { super.updateActionBar(actionBar); final ConversationFragment conversation = getConversationFragment(); final ContactPickerFragment contactPicker = getContactPicker(); if (contactPicker != null && mUiState.shouldShowContactPickerFragment()) { contactPicker.updateActionBar(actionBar); } else if (conversation != null && mUiState.shouldShowConversationFragment()) { conversation.updateActionBar(actionBar); } } @Override public boolean onOptionsItemSelected(final MenuItem menuItem) { if (super.onOptionsItemSelected(menuItem)) { return true; } if (menuItem.getItemId() == android.R.id.home) { onNavigationUpPressed(); return true; } return false; } public void onNavigationUpPressed() { // Let the conversation fragment handle the navigation up press. final ConversationFragment conversationFragment = getConversationFragment(); if (conversationFragment != null && conversationFragment.onNavigationUpPressed()) { return; } onFinishCurrentConversation(); } @Override public void onBackPressed() { // If action mode is active dismiss it if (getActionMode() != null) { dismissActionMode(); return; } // Let the conversation fragment handle the back press. final ConversationFragment conversationFragment = getConversationFragment(); if (conversationFragment != null && conversationFragment.onBackPressed()) { return; } super.onBackPressed(); } private ContactPickerFragment getContactPicker() { return (ContactPickerFragment) getFragmentManager().findFragmentByTag( ContactPickerFragment.FRAGMENT_TAG); } private ConversationFragment getConversationFragment() { return (ConversationFragment) getFragmentManager().findFragmentByTag( ConversationFragment.FRAGMENT_TAG); } @Override // From ContactPickerFragmentHost public void onGetOrCreateNewConversation(final String conversationId) { Assert.isTrue(conversationId != null); mUiState.onGetOrCreateConversation(conversationId); } @Override // From ContactPickerFragmentHost public void onBackButtonPressed() { onBackPressed(); } @Override // From ContactPickerFragmentHost public void onInitiateAddMoreParticipants() { mUiState.onAddMoreParticipants(); } @Override public void onParticipantCountChanged(final boolean canAddMoreParticipants) { mUiState.onParticipantCountUpdated(canAddMoreParticipants); } @Override // From ConversationFragmentHost public void onStartComposeMessage() { mUiState.onStartMessageCompose(); } @Override // From ConversationFragmentHost public void onConversationMetadataUpdated() { invalidateActionBar(); } @Override // From ConversationFragmentHost public void onConversationMessagesUpdated(final int numberOfMessages) { } @Override // From ConversationFragmentHost public void onConversationParticipantDataLoaded(final int numberOfParticipants) { } @Override // From ConversationFragmentHost public boolean isActiveAndFocused() { return !mIsPaused && hasWindowFocus(); } @Override // From ConversationActivityUiStateListener public void onConversationContactPickerUiStateChanged(final int oldState, final int newState, final boolean animate) { Assert.isTrue(oldState != newState); updateUiState(animate); } private void updateUiState(final boolean animate) { if (mInstanceStateSaved || mIsPaused) { return; } Assert.notNull(mUiState); final Intent intent = getIntent(); final String conversationId = mUiState.getConversationId(); final FragmentManager fragmentManager = getFragmentManager(); final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); final boolean needConversationFragment = mUiState.shouldShowConversationFragment(); final boolean needContactPickerFragment = mUiState.shouldShowContactPickerFragment(); ConversationFragment conversationFragment = getConversationFragment(); // Set up the conversation fragment. if (needConversationFragment) { Assert.notNull(conversationId); if (conversationFragment == null) { conversationFragment = new ConversationFragment(); fragmentTransaction.add(R.id.conversation_fragment_container, conversationFragment, ConversationFragment.FRAGMENT_TAG); } final MessageData draftData = intent.getParcelableExtra( UIIntents.UI_INTENT_EXTRA_DRAFT_DATA); if (!needContactPickerFragment) { // Once the user has committed the audience,remove the draft data from the // intent to prevent reuse intent.removeExtra(UIIntents.UI_INTENT_EXTRA_DRAFT_DATA); } conversationFragment.setHost(this); conversationFragment.setConversationInfo(this, conversationId, draftData); } else if (conversationFragment != null) { // Don't save draft to DB when removing conversation fragment and switching to // contact picking mode. The draft is intended for the new group. conversationFragment.suppressWriteDraft(); fragmentTransaction.remove(conversationFragment); } // Set up the contact picker fragment. ContactPickerFragment contactPickerFragment = getContactPicker(); if (needContactPickerFragment) { if (contactPickerFragment == null) { contactPickerFragment = new ContactPickerFragment(); fragmentTransaction.add(R.id.contact_picker_fragment_container, contactPickerFragment, ContactPickerFragment.FRAGMENT_TAG); } contactPickerFragment.setHost(this); contactPickerFragment.setContactPickingMode(mUiState.getDesiredContactPickingMode(), animate); } else if (contactPickerFragment != null) { fragmentTransaction.remove(contactPickerFragment); } fragmentTransaction.commit(); invalidateActionBar(); } @Override public void onFinishCurrentConversation() { // Simply finish the current activity. The current design is to leave any empty // conversations as is. if (OsUtil.isAtLeastL()) { finishAfterTransition(); } else { finish(); } } @Override public boolean shouldResumeComposeMessage() { return mUiState.shouldResumeComposeMessage(); } @Override protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { if (requestCode == ConversationFragment.REQUEST_CHOOSE_ATTACHMENTS && resultCode == RESULT_OK) { final ConversationFragment conversationFragment = getConversationFragment(); if (conversationFragment != null) { conversationFragment.onAttachmentChoosen(); } else { LogUtil.e(LogUtil.BUGLE_TAG, "ConversationFragment is missing after launching " + "AttachmentChooserActivity!"); } } else if (resultCode == FINISH_RESULT_CODE) { finish(); } } }